]>
Commit | Line | Data |
---|---|---|
959ef981 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
c4892e76 DW |
2 | /* |
3 | * Copyright (C) 2018 Oracle. All Rights Reserved. | |
c4892e76 | 4 | * Author: Darrick J. Wong <darrick.wong@oracle.com> |
c4892e76 | 5 | */ |
a440f877 | 6 | #include "xfs.h" |
c4892e76 | 7 | #include <stdint.h> |
396cd022 | 8 | #include <dirent.h> |
c4892e76 | 9 | #include <sys/types.h> |
c4892e76 | 10 | #include <sys/statvfs.h> |
396cd022 DW |
11 | #ifdef HAVE_LIBATTR |
12 | # include <attr/attributes.h> | |
13 | #endif | |
3baa69cd | 14 | #include <linux/fs.h> |
396cd022 | 15 | #include "handle.h" |
19852474 | 16 | #include "list.h" |
42b4c8e8 | 17 | #include "libfrog/paths.h" |
56598728 | 18 | #include "libfrog/workqueue.h" |
c4892e76 DW |
19 | #include "xfs_scrub.h" |
20 | #include "common.h" | |
21 | #include "inodes.h" | |
ed60d210 | 22 | #include "progress.h" |
c4892e76 | 23 | #include "scrub.h" |
a3158a75 | 24 | #include "descr.h" |
4bbed4ec | 25 | #include "unicrash.h" |
c4892e76 DW |
26 | |
27 | /* Phase 5: Check directory connectivity. */ | |
28 | ||
396cd022 DW |
29 | /* |
30 | * Warn about problematic bytes in a directory/attribute name. That means | |
31 | * terminal control characters and escape sequences, since that could be used | |
32 | * to do something naughty to the user's computer and/or break scripts. XFS | |
33 | * doesn't consider any byte sequence invalid, so don't flag these as errors. | |
8142c597 DW |
34 | * |
35 | * Returns 0 for success or -1 for error. This function logs errors. | |
396cd022 | 36 | */ |
8142c597 DW |
37 | static int |
38 | simple_check_name( | |
396cd022 | 39 | struct scrub_ctx *ctx, |
a3158a75 | 40 | struct descr *dsc, |
396cd022 DW |
41 | const char *namedescr, |
42 | const char *name) | |
43 | { | |
44 | const char *p; | |
45 | bool bad = false; | |
46 | char *errname; | |
47 | ||
48 | /* Complain about zero length names. */ | |
49 | if (*name == '\0' && should_warn_about_name(ctx)) { | |
a3158a75 | 50 | str_warn(ctx, descr_render(dsc), _("Zero length name found.")); |
8142c597 | 51 | return 0; |
396cd022 DW |
52 | } |
53 | ||
54 | /* control characters */ | |
55 | for (p = name; *p; p++) { | |
56 | if ((*p >= 1 && *p <= 31) || *p == 127) { | |
57 | bad = true; | |
58 | break; | |
59 | } | |
60 | } | |
61 | ||
62 | if (bad && should_warn_about_name(ctx)) { | |
63 | errname = string_escape(name); | |
64 | if (!errname) { | |
a3158a75 | 65 | str_errno(ctx, descr_render(dsc)); |
8142c597 | 66 | return -1; |
396cd022 | 67 | } |
a3158a75 | 68 | str_info(ctx, descr_render(dsc), |
396cd022 DW |
69 | _("Control character found in %s name \"%s\"."), |
70 | namedescr, errname); | |
71 | free(errname); | |
72 | } | |
73 | ||
8142c597 | 74 | return 0; |
396cd022 DW |
75 | } |
76 | ||
77 | /* | |
78 | * Iterate a directory looking for filenames with problematic | |
79 | * characters. | |
80 | */ | |
8142c597 DW |
81 | static int |
82 | check_dirent_names( | |
396cd022 | 83 | struct scrub_ctx *ctx, |
a3158a75 | 84 | struct descr *dsc, |
4bbed4ec | 85 | int *fd, |
4cca629d | 86 | struct xfs_bulkstat *bstat) |
396cd022 | 87 | { |
4bbed4ec | 88 | struct unicrash *uc = NULL; |
396cd022 DW |
89 | DIR *dir; |
90 | struct dirent *dentry; | |
ac1c1f8e | 91 | int ret; |
396cd022 DW |
92 | |
93 | dir = fdopendir(*fd); | |
94 | if (!dir) { | |
a3158a75 | 95 | str_errno(ctx, descr_render(dsc)); |
8142c597 | 96 | return errno; |
396cd022 DW |
97 | } |
98 | *fd = -1; /* closedir will close *fd for us */ | |
99 | ||
ac1c1f8e DW |
100 | ret = unicrash_dir_init(&uc, ctx, bstat); |
101 | if (ret) { | |
102 | str_liberror(ctx, ret, descr_render(dsc)); | |
4bbed4ec | 103 | goto out_unicrash; |
ac1c1f8e | 104 | } |
4bbed4ec | 105 | |
8142c597 | 106 | errno = 0; |
396cd022 DW |
107 | dentry = readdir(dir); |
108 | while (dentry) { | |
8142c597 | 109 | if (uc) |
ac1c1f8e | 110 | ret = unicrash_check_dir_name(uc, dsc, dentry); |
8142c597 DW |
111 | else |
112 | ret = simple_check_name(ctx, dsc, _("directory"), | |
113 | dentry->d_name); | |
114 | if (ret) { | |
115 | str_liberror(ctx, ret, descr_render(dsc)); | |
396cd022 | 116 | break; |
8142c597 DW |
117 | } |
118 | errno = 0; | |
396cd022 DW |
119 | dentry = readdir(dir); |
120 | } | |
8142c597 DW |
121 | if (errno) { |
122 | ret = errno; | |
123 | str_liberror(ctx, ret, descr_render(dsc)); | |
124 | } | |
4bbed4ec | 125 | unicrash_free(uc); |
396cd022 | 126 | |
4bbed4ec | 127 | out_unicrash: |
396cd022 | 128 | closedir(dir); |
8142c597 | 129 | return ret; |
396cd022 DW |
130 | } |
131 | ||
132 | #ifdef HAVE_LIBATTR | |
133 | /* Routines to scan all of an inode's xattrs for name problems. */ | |
593f2ab8 | 134 | struct attrns_decode { |
396cd022 DW |
135 | int flags; |
136 | const char *name; | |
137 | }; | |
138 | ||
593f2ab8 | 139 | static const struct attrns_decode attr_ns[] = { |
396cd022 DW |
140 | {0, "user"}, |
141 | {ATTR_ROOT, "system"}, | |
142 | {ATTR_SECURE, "secure"}, | |
143 | {0, NULL}, | |
144 | }; | |
145 | ||
146 | /* | |
147 | * Check all the xattr names in a particular namespace of a file handle | |
148 | * for Unicode normalization problems or collisions. | |
149 | */ | |
8142c597 DW |
150 | static int |
151 | check_xattr_ns_names( | |
396cd022 | 152 | struct scrub_ctx *ctx, |
a3158a75 | 153 | struct descr *dsc, |
396cd022 | 154 | struct xfs_handle *handle, |
4cca629d | 155 | struct xfs_bulkstat *bstat, |
593f2ab8 | 156 | const struct attrns_decode *attr_ns) |
396cd022 DW |
157 | { |
158 | struct attrlist_cursor cur; | |
159 | char attrbuf[XFS_XATTR_LIST_MAX]; | |
99ea3018 | 160 | char keybuf[XATTR_NAME_MAX + 1]; |
396cd022 DW |
161 | struct attrlist *attrlist = (struct attrlist *)attrbuf; |
162 | struct attrlist_ent *ent; | |
55290cfc | 163 | struct unicrash *uc = NULL; |
396cd022 DW |
164 | int i; |
165 | int error; | |
166 | ||
ac1c1f8e DW |
167 | error = unicrash_xattr_init(&uc, ctx, bstat); |
168 | if (error) { | |
169 | str_liberror(ctx, error, descr_render(dsc)); | |
8142c597 | 170 | return error; |
ac1c1f8e | 171 | } |
4bbed4ec | 172 | |
396cd022 DW |
173 | memset(attrbuf, 0, XFS_XATTR_LIST_MAX); |
174 | memset(&cur, 0, sizeof(cur)); | |
99ea3018 | 175 | memset(keybuf, 0, XATTR_NAME_MAX + 1); |
396cd022 DW |
176 | error = attr_list_by_handle(handle, sizeof(*handle), attrbuf, |
177 | XFS_XATTR_LIST_MAX, attr_ns->flags, &cur); | |
178 | while (!error) { | |
179 | /* Examine the xattrs. */ | |
180 | for (i = 0; i < attrlist->al_count; i++) { | |
181 | ent = ATTR_ENTRY(attrlist, i); | |
99ea3018 | 182 | snprintf(keybuf, XATTR_NAME_MAX, "%s.%s", attr_ns->name, |
396cd022 | 183 | ent->a_name); |
8142c597 | 184 | if (uc) |
ac1c1f8e | 185 | error = unicrash_check_xattr_name(uc, dsc, |
55290cfc | 186 | keybuf); |
8142c597 DW |
187 | else |
188 | error = simple_check_name(ctx, dsc, | |
55290cfc DW |
189 | _("extended attribute"), |
190 | keybuf); | |
8142c597 DW |
191 | if (error) { |
192 | str_liberror(ctx, error, descr_render(dsc)); | |
4bbed4ec | 193 | goto out; |
8142c597 | 194 | } |
396cd022 DW |
195 | } |
196 | ||
197 | if (!attrlist->al_more) | |
198 | break; | |
199 | error = attr_list_by_handle(handle, sizeof(*handle), attrbuf, | |
200 | XFS_XATTR_LIST_MAX, attr_ns->flags, &cur); | |
201 | } | |
8142c597 DW |
202 | if (error) { |
203 | if (errno == ESTALE) | |
204 | errno = 0; | |
205 | if (errno) | |
206 | str_errno(ctx, descr_render(dsc)); | |
207 | } | |
396cd022 | 208 | out: |
4bbed4ec | 209 | unicrash_free(uc); |
8142c597 | 210 | return error; |
396cd022 DW |
211 | } |
212 | ||
213 | /* | |
214 | * Check all the xattr names in all the xattr namespaces for problematic | |
215 | * characters. | |
216 | */ | |
8142c597 DW |
217 | static int |
218 | check_xattr_names( | |
396cd022 | 219 | struct scrub_ctx *ctx, |
a3158a75 | 220 | struct descr *dsc, |
4bbed4ec | 221 | struct xfs_handle *handle, |
4cca629d | 222 | struct xfs_bulkstat *bstat) |
396cd022 | 223 | { |
593f2ab8 | 224 | const struct attrns_decode *ns; |
8142c597 | 225 | int ret; |
396cd022 DW |
226 | |
227 | for (ns = attr_ns; ns->name; ns++) { | |
8142c597 DW |
228 | ret = check_xattr_ns_names(ctx, dsc, handle, bstat, ns); |
229 | if (ret) | |
396cd022 DW |
230 | break; |
231 | } | |
8142c597 | 232 | return ret; |
396cd022 DW |
233 | } |
234 | #else | |
8142c597 | 235 | # define check_xattr_names(c, d, h, b) (0) |
396cd022 DW |
236 | #endif /* HAVE_LIBATTR */ |
237 | ||
a3158a75 DW |
238 | static int |
239 | render_ino_from_handle( | |
240 | struct scrub_ctx *ctx, | |
241 | char *buf, | |
242 | size_t buflen, | |
243 | void *data) | |
244 | { | |
245 | struct xfs_bstat *bstat = data; | |
246 | ||
247 | return scrub_render_ino_descr(ctx, buf, buflen, bstat->bs_ino, | |
248 | bstat->bs_gen, NULL); | |
249 | } | |
250 | ||
c4892e76 DW |
251 | /* |
252 | * Verify the connectivity of the directory tree. | |
253 | * We know that the kernel's open-by-handle function will try to reconnect | |
254 | * parents of an opened directory, so we'll accept that as sufficient. | |
4bbed4ec DW |
255 | * |
256 | * Check for potential Unicode collisions in names. | |
c4892e76 DW |
257 | */ |
258 | static int | |
8142c597 | 259 | check_inode_names( |
c4892e76 DW |
260 | struct scrub_ctx *ctx, |
261 | struct xfs_handle *handle, | |
4cca629d | 262 | struct xfs_bulkstat *bstat, |
c4892e76 DW |
263 | void *arg) |
264 | { | |
a3158a75 | 265 | DEFINE_DESCR(dsc, ctx, render_ino_from_handle); |
8142c597 | 266 | bool *aborted = arg; |
c4892e76 | 267 | int fd = -1; |
8142c597 DW |
268 | int error = 0; |
269 | int err2; | |
c4892e76 | 270 | |
a3158a75 | 271 | descr_set(&dsc, bstat); |
c4892e76 DW |
272 | background_sleep(); |
273 | ||
4bbed4ec | 274 | /* Warn about naming problems in xattrs. */ |
e24ee6b6 | 275 | if (bstat->bs_xflags & FS_XFLAG_HASATTR) { |
8142c597 DW |
276 | error = check_xattr_names(ctx, &dsc, handle, bstat); |
277 | if (error) | |
e24ee6b6 DW |
278 | goto out; |
279 | } | |
396cd022 | 280 | |
c4892e76 DW |
281 | /* Open the dir, let the kernel try to reconnect it to the root. */ |
282 | if (S_ISDIR(bstat->bs_mode)) { | |
59f79e0a | 283 | fd = scrub_open_handle(handle); |
c4892e76 | 284 | if (fd < 0) { |
8142c597 DW |
285 | error = errno; |
286 | if (error == ESTALE) | |
c4892e76 | 287 | return ESTALE; |
a3158a75 | 288 | str_errno(ctx, descr_render(&dsc)); |
c4892e76 DW |
289 | goto out; |
290 | } | |
291 | } | |
292 | ||
4bbed4ec DW |
293 | /* Warn about naming problems in the directory entries. */ |
294 | if (fd >= 0 && S_ISDIR(bstat->bs_mode)) { | |
8142c597 DW |
295 | error = check_dirent_names(ctx, &dsc, &fd, bstat); |
296 | if (error) | |
4bbed4ec DW |
297 | goto out; |
298 | } | |
396cd022 | 299 | |
c4892e76 | 300 | out: |
ed60d210 | 301 | progress_add(1); |
6c05cc5d | 302 | if (fd >= 0) { |
8142c597 DW |
303 | err2 = close(fd); |
304 | if (err2) | |
a3158a75 | 305 | str_errno(ctx, descr_render(&dsc)); |
8142c597 DW |
306 | if (!error && err2) |
307 | error = err2; | |
6c05cc5d | 308 | } |
8142c597 DW |
309 | |
310 | if (error) | |
311 | *aborted = true; | |
312 | if (!error && *aborted) | |
313 | error = ECANCELED; | |
314 | ||
315 | return error; | |
c4892e76 DW |
316 | } |
317 | ||
3baa69cd DW |
318 | #ifndef FS_IOC_GETFSLABEL |
319 | # define FSLABEL_MAX 256 | |
320 | # define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX]) | |
321 | #endif /* FS_IOC_GETFSLABEL */ | |
322 | ||
a3158a75 DW |
323 | static int |
324 | scrub_render_mountpoint( | |
325 | struct scrub_ctx *ctx, | |
326 | char *buf, | |
327 | size_t buflen, | |
328 | void *data) | |
329 | { | |
330 | return snprintf(buf, buflen, _("%s"), ctx->mntpoint); | |
331 | } | |
332 | ||
3baa69cd DW |
333 | /* |
334 | * Check the filesystem label for Unicode normalization problems or misleading | |
335 | * sequences. | |
336 | */ | |
8142c597 DW |
337 | static int |
338 | check_fs_label( | |
3baa69cd DW |
339 | struct scrub_ctx *ctx) |
340 | { | |
a3158a75 | 341 | DEFINE_DESCR(dsc, ctx, scrub_render_mountpoint); |
3baa69cd DW |
342 | char label[FSLABEL_MAX]; |
343 | struct unicrash *uc = NULL; | |
3baa69cd DW |
344 | int error; |
345 | ||
ac1c1f8e DW |
346 | error = unicrash_fs_label_init(&uc, ctx); |
347 | if (error) { | |
348 | str_liberror(ctx, error, descr_render(&dsc)); | |
8142c597 | 349 | return error; |
ac1c1f8e | 350 | } |
3baa69cd | 351 | |
a3158a75 DW |
352 | descr_set(&dsc, NULL); |
353 | ||
3baa69cd | 354 | /* Retrieve label; quietly bail if we don't support that. */ |
3f9efb2e | 355 | error = ioctl(ctx->mnt.fd, FS_IOC_GETFSLABEL, &label); |
3baa69cd DW |
356 | if (error) { |
357 | if (errno != EOPNOTSUPP && errno != ENOTTY) { | |
8142c597 | 358 | error = errno; |
3baa69cd DW |
359 | perror(ctx->mntpoint); |
360 | } | |
361 | goto out; | |
362 | } | |
363 | ||
364 | /* Ignore empty labels. */ | |
365 | if (label[0] == 0) | |
366 | goto out; | |
367 | ||
368 | /* Otherwise check for weirdness. */ | |
8142c597 | 369 | if (uc) |
ac1c1f8e | 370 | error = unicrash_check_fs_label(uc, &dsc, label); |
8142c597 DW |
371 | else |
372 | error = simple_check_name(ctx, &dsc, _("filesystem label"), | |
a3158a75 | 373 | label); |
8142c597 DW |
374 | if (error) |
375 | str_liberror(ctx, error, descr_render(&dsc)); | |
3baa69cd DW |
376 | out: |
377 | unicrash_free(uc); | |
8142c597 | 378 | return error; |
3baa69cd DW |
379 | } |
380 | ||
c4892e76 | 381 | /* Check directory connectivity. */ |
8142c597 DW |
382 | int |
383 | phase5_func( | |
c4892e76 DW |
384 | struct scrub_ctx *ctx) |
385 | { | |
8142c597 | 386 | bool aborted = false; |
59f79e0a | 387 | int ret; |
c4892e76 | 388 | |
49e05cb0 | 389 | if (ctx->corruptions_found || ctx->unfixable_errors) { |
c4892e76 DW |
390 | str_info(ctx, ctx->mntpoint, |
391 | _("Filesystem has errors, skipping connectivity checks.")); | |
8142c597 | 392 | return 0; |
c4892e76 DW |
393 | } |
394 | ||
8142c597 | 395 | ret = check_fs_label(ctx); |
d22f2471 | 396 | if (ret) |
8142c597 | 397 | return ret; |
3baa69cd | 398 | |
8142c597 | 399 | ret = scrub_scan_all_inodes(ctx, check_inode_names, &aborted); |
59f79e0a | 400 | if (ret) |
8142c597 DW |
401 | return ret; |
402 | if (aborted) | |
403 | return ECANCELED; | |
404 | ||
c4892e76 | 405 | xfs_scrub_report_preen_triggers(ctx); |
8142c597 DW |
406 | return 0; |
407 | } | |
408 | ||
64dabc9f DW |
409 | /* Estimate how much work we're going to do. */ |
410 | int | |
411 | phase5_estimate( | |
412 | struct scrub_ctx *ctx, | |
413 | uint64_t *items, | |
414 | unsigned int *nr_threads, | |
415 | int *rshift) | |
8142c597 | 416 | { |
64dabc9f DW |
417 | *items = ctx->mnt_sv.f_files - ctx->mnt_sv.f_ffree; |
418 | *nr_threads = scrub_nproc(ctx); | |
419 | *rshift = 0; | |
420 | return 0; | |
c4892e76 | 421 | } |