]>
Commit | Line | Data |
---|---|---|
959ef981 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
b364a9c0 DW |
2 | /* |
3 | * Copyright (C) 2018 Oracle. All Rights Reserved. | |
b364a9c0 | 4 | * Author: Darrick J. Wong <darrick.wong@oracle.com> |
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 DW |
11 | #include "handle.h" |
12 | #include "path.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; | |
33 | bool moveon; | |
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 | ||
46 | /* Scan a directory sub tree. */ | |
47 | static void | |
48 | scan_fs_dir( | |
49 | struct workqueue *wq, | |
50 | xfs_agnumber_t agno, | |
51 | void *arg) | |
52 | { | |
53 | struct scrub_ctx *ctx = (struct scrub_ctx *)wq->wq_ctx; | |
54 | struct scan_fs_tree_dir *sftd = arg; | |
55 | struct scan_fs_tree *sft = sftd->sft; | |
56 | DIR *dir; | |
57 | struct dirent *dirent; | |
58 | char newpath[PATH_MAX]; | |
59 | struct scan_fs_tree_dir *new_sftd; | |
60 | struct stat sb; | |
61 | int dir_fd; | |
62 | int error; | |
63 | ||
64 | /* Open the directory. */ | |
65 | dir_fd = open(sftd->path, O_RDONLY | O_NOATIME | O_NOFOLLOW | O_NOCTTY); | |
66 | if (dir_fd < 0) { | |
67 | if (errno != ENOENT) | |
68 | str_errno(ctx, sftd->path); | |
69 | goto out; | |
70 | } | |
71 | ||
72 | /* Caller-specific directory checks. */ | |
73 | if (!sft->dir_fn(ctx, sftd->path, dir_fd, sft->arg)) { | |
74 | sft->moveon = false; | |
6c05cc5d DW |
75 | error = close(dir_fd); |
76 | if (error) | |
77 | str_errno(ctx, sftd->path); | |
b364a9c0 DW |
78 | goto out; |
79 | } | |
80 | ||
81 | /* Iterate the directory entries. */ | |
82 | dir = fdopendir(dir_fd); | |
83 | if (!dir) { | |
84 | str_errno(ctx, sftd->path); | |
e91c285f | 85 | close(dir_fd); |
b364a9c0 DW |
86 | goto out; |
87 | } | |
88 | rewinddir(dir); | |
89 | for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) { | |
90 | snprintf(newpath, PATH_MAX, "%s/%s", sftd->path, | |
91 | dirent->d_name); | |
92 | ||
93 | /* Get the stat info for this directory entry. */ | |
94 | error = fstatat(dir_fd, dirent->d_name, &sb, | |
95 | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW); | |
96 | if (error) { | |
97 | str_errno(ctx, newpath); | |
98 | continue; | |
99 | } | |
100 | ||
101 | /* Ignore files on other filesystems. */ | |
102 | if (sb.st_dev != sft->root_sb.st_dev) | |
103 | continue; | |
104 | ||
105 | /* Caller-specific directory entry function. */ | |
106 | if (!sft->dirent_fn(ctx, newpath, dir_fd, dirent, &sb, | |
107 | sft->arg)) { | |
108 | sft->moveon = false; | |
109 | break; | |
110 | } | |
111 | ||
112 | if (xfs_scrub_excessive_errors(ctx)) { | |
113 | sft->moveon = false; | |
114 | break; | |
115 | } | |
116 | ||
117 | /* If directory, call ourselves recursively. */ | |
118 | if (S_ISDIR(sb.st_mode) && strcmp(".", dirent->d_name) && | |
119 | strcmp("..", dirent->d_name)) { | |
120 | new_sftd = malloc(sizeof(struct scan_fs_tree_dir)); | |
121 | if (!new_sftd) { | |
122 | str_errno(ctx, newpath); | |
123 | sft->moveon = false; | |
124 | break; | |
125 | } | |
126 | new_sftd->path = strdup(newpath); | |
127 | new_sftd->sft = sft; | |
128 | new_sftd->rootdir = false; | |
129 | pthread_mutex_lock(&sft->lock); | |
130 | sft->nr_dirs++; | |
131 | pthread_mutex_unlock(&sft->lock); | |
132 | error = workqueue_add(wq, scan_fs_dir, 0, new_sftd); | |
133 | if (error) { | |
82377bde | 134 | str_info(ctx, ctx->mntpoint, |
b364a9c0 DW |
135 | _("Could not queue subdirectory scan work.")); |
136 | sft->moveon = false; | |
137 | break; | |
138 | } | |
139 | } | |
140 | } | |
141 | ||
142 | /* Close dir, go away. */ | |
143 | error = closedir(dir); | |
144 | if (error) | |
145 | str_errno(ctx, sftd->path); | |
146 | ||
147 | out: | |
148 | pthread_mutex_lock(&sft->lock); | |
149 | sft->nr_dirs--; | |
150 | if (sft->nr_dirs == 0) | |
151 | pthread_cond_signal(&sft->wakeup); | |
152 | pthread_mutex_unlock(&sft->lock); | |
153 | ||
154 | free(sftd->path); | |
155 | free(sftd); | |
156 | } | |
157 | ||
158 | /* Scan the entire filesystem. */ | |
159 | bool | |
160 | scan_fs_tree( | |
161 | struct scrub_ctx *ctx, | |
162 | scan_fs_tree_dir_fn dir_fn, | |
163 | scan_fs_tree_dirent_fn dirent_fn, | |
164 | void *arg) | |
165 | { | |
166 | struct workqueue wq; | |
167 | struct scan_fs_tree sft; | |
168 | struct scan_fs_tree_dir *sftd; | |
169 | int ret; | |
170 | ||
171 | sft.moveon = true; | |
172 | sft.nr_dirs = 1; | |
173 | sft.root_sb = ctx->mnt_sb; | |
174 | sft.dir_fn = dir_fn; | |
175 | sft.dirent_fn = dirent_fn; | |
176 | sft.arg = arg; | |
177 | pthread_mutex_init(&sft.lock, NULL); | |
178 | pthread_cond_init(&sft.wakeup, NULL); | |
179 | ||
180 | sftd = malloc(sizeof(struct scan_fs_tree_dir)); | |
181 | if (!sftd) { | |
182 | str_errno(ctx, ctx->mntpoint); | |
183 | return false; | |
184 | } | |
185 | sftd->path = strdup(ctx->mntpoint); | |
186 | sftd->sft = &sft; | |
187 | sftd->rootdir = true; | |
188 | ||
189 | ret = workqueue_create(&wq, (struct xfs_mount *)ctx, | |
190 | scrub_nproc_workqueue(ctx)); | |
191 | if (ret) { | |
82377bde | 192 | str_info(ctx, ctx->mntpoint, _("Could not create workqueue.")); |
b364a9c0 DW |
193 | goto out_free; |
194 | } | |
195 | ret = workqueue_add(&wq, scan_fs_dir, 0, sftd); | |
196 | if (ret) { | |
82377bde | 197 | str_info(ctx, ctx->mntpoint, |
b364a9c0 | 198 | _("Could not queue directory scan work.")); |
224df902 | 199 | goto out_wq; |
b364a9c0 DW |
200 | } |
201 | ||
202 | pthread_mutex_lock(&sft.lock); | |
203 | pthread_cond_wait(&sft.wakeup, &sft.lock); | |
204 | assert(sft.nr_dirs == 0); | |
205 | pthread_mutex_unlock(&sft.lock); | |
206 | workqueue_destroy(&wq); | |
207 | ||
208 | return sft.moveon; | |
224df902 DW |
209 | out_wq: |
210 | workqueue_destroy(&wq); | |
b364a9c0 DW |
211 | out_free: |
212 | free(sftd->path); | |
213 | free(sftd); | |
214 | return false; | |
215 | } | |
7e36bc0f DW |
216 | |
217 | #ifndef FITRIM | |
218 | struct fstrim_range { | |
219 | __u64 start; | |
220 | __u64 len; | |
221 | __u64 minlen; | |
222 | }; | |
223 | #define FITRIM _IOWR('X', 121, struct fstrim_range) /* Trim */ | |
224 | #endif | |
225 | ||
226 | /* Call FITRIM to trim all the unused space in a filesystem. */ | |
227 | void | |
228 | fstrim( | |
229 | struct scrub_ctx *ctx) | |
230 | { | |
231 | struct fstrim_range range = {0}; | |
232 | int error; | |
233 | ||
234 | range.len = ULLONG_MAX; | |
3f9efb2e | 235 | error = ioctl(ctx->mnt.fd, FITRIM, &range); |
7e36bc0f DW |
236 | if (error && errno != EOPNOTSUPP && errno != ENOTTY) |
237 | perror(_("fstrim")); | |
238 | } |