]>
Commit | Line | Data |
---|---|---|
8d318d62 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
b364a9c0 | 2 | /* |
52520522 | 3 | * Copyright (C) 2018-2024 Oracle. All Rights Reserved. |
8d318d62 | 4 | * Author: Darrick J. Wong <djwong@kernel.org> |
b364a9c0 | 5 | */ |
a440f877 | 6 | #include "xfs.h" |
b364a9c0 | 7 | #include <stdint.h> |
b364a9c0 DW |
8 | #include <dirent.h> |
9 | #include <sys/types.h> | |
10 | #include <sys/statvfs.h> | |
b364a9c0 | 11 | #include "handle.h" |
42b4c8e8 | 12 | #include "libfrog/paths.h" |
56598728 | 13 | #include "libfrog/workqueue.h" |
b364a9c0 DW |
14 | #include "xfs_scrub.h" |
15 | #include "common.h" | |
16 | #include "vfs.h" | |
17 | ||
18 | #ifndef AT_NO_AUTOMOUNT | |
19 | # define AT_NO_AUTOMOUNT 0x800 | |
20 | #endif | |
21 | ||
22 | /* | |
23 | * Helper functions to assist in traversing a directory tree using regular | |
24 | * VFS calls. | |
25 | */ | |
26 | ||
27 | /* Scan a filesystem tree. */ | |
28 | struct scan_fs_tree { | |
29 | unsigned int nr_dirs; | |
30 | pthread_mutex_t lock; | |
31 | pthread_cond_t wakeup; | |
32 | struct stat root_sb; | |
f544ec31 | 33 | bool aborted; |
b364a9c0 DW |
34 | scan_fs_tree_dir_fn dir_fn; |
35 | scan_fs_tree_dirent_fn dirent_fn; | |
36 | void *arg; | |
37 | }; | |
38 | ||
39 | /* Per-work-item scan context. */ | |
40 | struct scan_fs_tree_dir { | |
41 | char *path; | |
42 | struct scan_fs_tree *sft; | |
43 | bool rootdir; | |
44 | }; | |
45 | ||
83630b7f DW |
46 | static void scan_fs_dir(struct workqueue *wq, xfs_agnumber_t agno, void *arg); |
47 | ||
4953e709 DW |
48 | /* Increment the number of directories that are queued for processing. */ |
49 | static void | |
50 | inc_nr_dirs( | |
51 | struct scan_fs_tree *sft) | |
52 | { | |
53 | pthread_mutex_lock(&sft->lock); | |
54 | sft->nr_dirs++; | |
55 | pthread_mutex_unlock(&sft->lock); | |
56 | } | |
57 | ||
58 | /* | |
59 | * Decrement the number of directories that are queued for processing and if | |
60 | * we ran out of dirs to process, wake up anyone who was waiting for processing | |
61 | * to finish. | |
62 | */ | |
63 | static void | |
64 | dec_nr_dirs( | |
65 | struct scan_fs_tree *sft) | |
66 | { | |
67 | pthread_mutex_lock(&sft->lock); | |
68 | sft->nr_dirs--; | |
69 | if (sft->nr_dirs == 0) | |
70 | pthread_cond_signal(&sft->wakeup); | |
71 | pthread_mutex_unlock(&sft->lock); | |
72 | } | |
73 | ||
83630b7f | 74 | /* Queue a directory for scanning. */ |
f0bbbd72 | 75 | static int |
83630b7f DW |
76 | queue_subdir( |
77 | struct scrub_ctx *ctx, | |
78 | struct scan_fs_tree *sft, | |
79 | struct workqueue *wq, | |
80 | const char *path, | |
81 | bool is_rootdir) | |
82 | { | |
83 | struct scan_fs_tree_dir *new_sftd; | |
84 | int error; | |
85 | ||
86 | new_sftd = malloc(sizeof(struct scan_fs_tree_dir)); | |
f0bbbd72 DW |
87 | if (!new_sftd) |
88 | return errno; | |
83630b7f DW |
89 | |
90 | new_sftd->path = strdup(path); | |
91 | if (!new_sftd->path) { | |
f0bbbd72 | 92 | error = errno; |
83630b7f DW |
93 | goto out_sftd; |
94 | } | |
95 | ||
96 | new_sftd->sft = sft; | |
97 | new_sftd->rootdir = is_rootdir; | |
98 | ||
4953e709 | 99 | inc_nr_dirs(sft); |
baed134d | 100 | error = -workqueue_add(wq, scan_fs_dir, 0, new_sftd); |
83630b7f | 101 | if (error) { |
4953e709 | 102 | dec_nr_dirs(sft); |
9d57cbfc | 103 | str_liberror(ctx, error, _("queueing directory scan work")); |
83630b7f DW |
104 | goto out_path; |
105 | } | |
106 | ||
f0bbbd72 | 107 | return 0; |
83630b7f DW |
108 | out_path: |
109 | free(new_sftd->path); | |
110 | out_sftd: | |
111 | free(new_sftd); | |
f0bbbd72 | 112 | return error; |
83630b7f DW |
113 | } |
114 | ||
b364a9c0 DW |
115 | /* Scan a directory sub tree. */ |
116 | static void | |
117 | scan_fs_dir( | |
118 | struct workqueue *wq, | |
119 | xfs_agnumber_t agno, | |
120 | void *arg) | |
121 | { | |
122 | struct scrub_ctx *ctx = (struct scrub_ctx *)wq->wq_ctx; | |
123 | struct scan_fs_tree_dir *sftd = arg; | |
124 | struct scan_fs_tree *sft = sftd->sft; | |
125 | DIR *dir; | |
126 | struct dirent *dirent; | |
127 | char newpath[PATH_MAX]; | |
b364a9c0 DW |
128 | struct stat sb; |
129 | int dir_fd; | |
130 | int error; | |
131 | ||
132 | /* Open the directory. */ | |
133 | dir_fd = open(sftd->path, O_RDONLY | O_NOATIME | O_NOFOLLOW | O_NOCTTY); | |
134 | if (dir_fd < 0) { | |
135 | if (errno != ENOENT) | |
136 | str_errno(ctx, sftd->path); | |
137 | goto out; | |
138 | } | |
139 | ||
140 | /* Caller-specific directory checks. */ | |
f544ec31 DW |
141 | error = sft->dir_fn(ctx, sftd->path, dir_fd, sft->arg); |
142 | if (error) { | |
143 | sft->aborted = true; | |
6c05cc5d DW |
144 | error = close(dir_fd); |
145 | if (error) | |
146 | str_errno(ctx, sftd->path); | |
b364a9c0 DW |
147 | goto out; |
148 | } | |
149 | ||
150 | /* Iterate the directory entries. */ | |
151 | dir = fdopendir(dir_fd); | |
152 | if (!dir) { | |
153 | str_errno(ctx, sftd->path); | |
f544ec31 | 154 | sft->aborted = true; |
e91c285f | 155 | close(dir_fd); |
b364a9c0 DW |
156 | goto out; |
157 | } | |
158 | rewinddir(dir); | |
f544ec31 DW |
159 | for (dirent = readdir(dir); |
160 | !sft->aborted && dirent != NULL; | |
161 | dirent = readdir(dir)) { | |
b364a9c0 DW |
162 | snprintf(newpath, PATH_MAX, "%s/%s", sftd->path, |
163 | dirent->d_name); | |
164 | ||
165 | /* Get the stat info for this directory entry. */ | |
166 | error = fstatat(dir_fd, dirent->d_name, &sb, | |
167 | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW); | |
168 | if (error) { | |
169 | str_errno(ctx, newpath); | |
170 | continue; | |
171 | } | |
172 | ||
173 | /* Ignore files on other filesystems. */ | |
174 | if (sb.st_dev != sft->root_sb.st_dev) | |
175 | continue; | |
176 | ||
177 | /* Caller-specific directory entry function. */ | |
f544ec31 DW |
178 | error = sft->dirent_fn(ctx, newpath, dir_fd, dirent, &sb, |
179 | sft->arg); | |
180 | if (error) { | |
181 | sft->aborted = true; | |
b364a9c0 DW |
182 | break; |
183 | } | |
184 | ||
273165cc | 185 | if (scrub_excessive_errors(ctx)) { |
f544ec31 | 186 | sft->aborted = true; |
b364a9c0 DW |
187 | break; |
188 | } | |
189 | ||
190 | /* If directory, call ourselves recursively. */ | |
191 | if (S_ISDIR(sb.st_mode) && strcmp(".", dirent->d_name) && | |
192 | strcmp("..", dirent->d_name)) { | |
f0bbbd72 DW |
193 | error = queue_subdir(ctx, sft, wq, newpath, false); |
194 | if (error) { | |
195 | str_liberror(ctx, error, | |
196 | _("queueing subdirectory scan")); | |
f544ec31 | 197 | sft->aborted = true; |
b364a9c0 | 198 | break; |
f0bbbd72 | 199 | } |
b364a9c0 DW |
200 | } |
201 | } | |
202 | ||
203 | /* Close dir, go away. */ | |
204 | error = closedir(dir); | |
205 | if (error) | |
206 | str_errno(ctx, sftd->path); | |
207 | ||
208 | out: | |
4953e709 | 209 | dec_nr_dirs(sft); |
b364a9c0 DW |
210 | free(sftd->path); |
211 | free(sftd); | |
212 | } | |
213 | ||
f544ec31 DW |
214 | /* |
215 | * Scan the entire filesystem. This function returns 0 on success; if there | |
216 | * are errors, this function will log them and returns nonzero. | |
217 | */ | |
218 | int | |
b364a9c0 DW |
219 | scan_fs_tree( |
220 | struct scrub_ctx *ctx, | |
221 | scan_fs_tree_dir_fn dir_fn, | |
222 | scan_fs_tree_dirent_fn dirent_fn, | |
223 | void *arg) | |
224 | { | |
225 | struct workqueue wq; | |
f544ec31 DW |
226 | struct scan_fs_tree sft = { |
227 | .root_sb = ctx->mnt_sb, | |
228 | .dir_fn = dir_fn, | |
229 | .dirent_fn = dirent_fn, | |
230 | .arg = arg, | |
231 | }; | |
b364a9c0 DW |
232 | int ret; |
233 | ||
499c104f DW |
234 | ret = pthread_mutex_init(&sft.lock, NULL); |
235 | if (ret) { | |
236 | str_liberror(ctx, ret, _("creating directory scan lock")); | |
f544ec31 | 237 | return ret; |
499c104f DW |
238 | } |
239 | ret = pthread_cond_init(&sft.wakeup, NULL); | |
240 | if (ret) { | |
241 | str_liberror(ctx, ret, _("creating directory scan signal")); | |
242 | goto out_mutex; | |
243 | } | |
b364a9c0 | 244 | |
baed134d | 245 | ret = -workqueue_create(&wq, (struct xfs_mount *)ctx, |
b364a9c0 DW |
246 | scrub_nproc_workqueue(ctx)); |
247 | if (ret) { | |
499c104f DW |
248 | str_liberror(ctx, ret, _("creating directory scan workqueue")); |
249 | goto out_cond; | |
b364a9c0 | 250 | } |
83630b7f | 251 | |
f0bbbd72 DW |
252 | ret = queue_subdir(ctx, &sft, &wq, ctx->mntpoint, true); |
253 | if (ret) { | |
254 | str_liberror(ctx, ret, _("queueing directory scan")); | |
224df902 | 255 | goto out_wq; |
f0bbbd72 | 256 | } |
b364a9c0 | 257 | |
83630b7f DW |
258 | /* |
259 | * Wait for the wakeup to trigger, which should only happen when the | |
260 | * last worker thread decrements nr_dirs to zero. Once the worker | |
261 | * triggers the wakeup and unlocks the sft lock, it's no longer safe | |
262 | * for any worker thread to access sft, as we now own the lock and are | |
263 | * about to tear everything down. | |
264 | */ | |
b364a9c0 | 265 | pthread_mutex_lock(&sft.lock); |
817d1b67 | 266 | while (sft.nr_dirs > 0) |
44012ab0 | 267 | pthread_cond_wait(&sft.wakeup, &sft.lock); |
b364a9c0 DW |
268 | assert(sft.nr_dirs == 0); |
269 | pthread_mutex_unlock(&sft.lock); | |
b364a9c0 | 270 | |
baed134d | 271 | ret = -workqueue_terminate(&wq); |
71296cf8 | 272 | if (ret) { |
71296cf8 | 273 | str_liberror(ctx, ret, _("finishing directory scan work")); |
499c104f | 274 | goto out_wq; |
71296cf8 | 275 | } |
499c104f | 276 | |
f544ec31 DW |
277 | if (!ret && sft.aborted) |
278 | ret = -1; | |
279 | ||
224df902 DW |
280 | out_wq: |
281 | workqueue_destroy(&wq); | |
499c104f DW |
282 | out_cond: |
283 | pthread_cond_destroy(&sft.wakeup); | |
284 | out_mutex: | |
285 | pthread_mutex_destroy(&sft.lock); | |
f544ec31 | 286 | return ret; |
b364a9c0 | 287 | } |
7e36bc0f DW |
288 | |
289 | #ifndef FITRIM | |
290 | struct fstrim_range { | |
291 | __u64 start; | |
292 | __u64 len; | |
293 | __u64 minlen; | |
294 | }; | |
295 | #define FITRIM _IOWR('X', 121, struct fstrim_range) /* Trim */ | |
296 | #endif | |
297 | ||
298 | /* Call FITRIM to trim all the unused space in a filesystem. */ | |
299 | void | |
300 | fstrim( | |
301 | struct scrub_ctx *ctx) | |
302 | { | |
303 | struct fstrim_range range = {0}; | |
304 | int error; | |
305 | ||
306 | range.len = ULLONG_MAX; | |
3f9efb2e | 307 | error = ioctl(ctx->mnt.fd, FITRIM, &range); |
7e36bc0f DW |
308 | if (error && errno != EOPNOTSUPP && errno != ENOTTY) |
309 | perror(_("fstrim")); | |
310 | } |