]>
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" |
4bbed4ec | 24 | #include "unicrash.h" |
c4892e76 DW |
25 | |
26 | /* Phase 5: Check directory connectivity. */ | |
27 | ||
396cd022 DW |
28 | /* |
29 | * Warn about problematic bytes in a directory/attribute name. That means | |
30 | * terminal control characters and escape sequences, since that could be used | |
31 | * to do something naughty to the user's computer and/or break scripts. XFS | |
32 | * doesn't consider any byte sequence invalid, so don't flag these as errors. | |
33 | */ | |
34 | static bool | |
35 | xfs_scrub_check_name( | |
36 | struct scrub_ctx *ctx, | |
37 | const char *descr, | |
38 | const char *namedescr, | |
39 | const char *name) | |
40 | { | |
41 | const char *p; | |
42 | bool bad = false; | |
43 | char *errname; | |
44 | ||
45 | /* Complain about zero length names. */ | |
46 | if (*name == '\0' && should_warn_about_name(ctx)) { | |
47 | str_warn(ctx, descr, _("Zero length name found.")); | |
48 | return true; | |
49 | } | |
50 | ||
51 | /* control characters */ | |
52 | for (p = name; *p; p++) { | |
53 | if ((*p >= 1 && *p <= 31) || *p == 127) { | |
54 | bad = true; | |
55 | break; | |
56 | } | |
57 | } | |
58 | ||
59 | if (bad && should_warn_about_name(ctx)) { | |
60 | errname = string_escape(name); | |
61 | if (!errname) { | |
62 | str_errno(ctx, descr); | |
63 | return false; | |
64 | } | |
65 | str_info(ctx, descr, | |
66 | _("Control character found in %s name \"%s\"."), | |
67 | namedescr, errname); | |
68 | free(errname); | |
69 | } | |
70 | ||
71 | return true; | |
72 | } | |
73 | ||
74 | /* | |
75 | * Iterate a directory looking for filenames with problematic | |
76 | * characters. | |
77 | */ | |
78 | static bool | |
79 | xfs_scrub_scan_dirents( | |
80 | struct scrub_ctx *ctx, | |
81 | const char *descr, | |
4bbed4ec | 82 | int *fd, |
4cca629d | 83 | struct xfs_bulkstat *bstat) |
396cd022 | 84 | { |
4bbed4ec | 85 | struct unicrash *uc = NULL; |
396cd022 DW |
86 | DIR *dir; |
87 | struct dirent *dentry; | |
88 | bool moveon = true; | |
89 | ||
90 | dir = fdopendir(*fd); | |
91 | if (!dir) { | |
92 | str_errno(ctx, descr); | |
93 | goto out; | |
94 | } | |
95 | *fd = -1; /* closedir will close *fd for us */ | |
96 | ||
4bbed4ec DW |
97 | moveon = unicrash_dir_init(&uc, ctx, bstat); |
98 | if (!moveon) | |
99 | goto out_unicrash; | |
100 | ||
396cd022 DW |
101 | dentry = readdir(dir); |
102 | while (dentry) { | |
55290cfc DW |
103 | if (uc) |
104 | moveon = unicrash_check_dir_name(uc, descr, dentry); | |
105 | else | |
106 | moveon = xfs_scrub_check_name(ctx, descr, | |
107 | _("directory"), dentry->d_name); | |
396cd022 DW |
108 | if (!moveon) |
109 | break; | |
110 | dentry = readdir(dir); | |
111 | } | |
4bbed4ec | 112 | unicrash_free(uc); |
396cd022 | 113 | |
4bbed4ec | 114 | out_unicrash: |
396cd022 DW |
115 | closedir(dir); |
116 | out: | |
117 | return moveon; | |
118 | } | |
119 | ||
120 | #ifdef HAVE_LIBATTR | |
121 | /* Routines to scan all of an inode's xattrs for name problems. */ | |
593f2ab8 | 122 | struct attrns_decode { |
396cd022 DW |
123 | int flags; |
124 | const char *name; | |
125 | }; | |
126 | ||
593f2ab8 | 127 | static const struct attrns_decode attr_ns[] = { |
396cd022 DW |
128 | {0, "user"}, |
129 | {ATTR_ROOT, "system"}, | |
130 | {ATTR_SECURE, "secure"}, | |
131 | {0, NULL}, | |
132 | }; | |
133 | ||
134 | /* | |
135 | * Check all the xattr names in a particular namespace of a file handle | |
136 | * for Unicode normalization problems or collisions. | |
137 | */ | |
138 | static bool | |
139 | xfs_scrub_scan_fhandle_namespace_xattrs( | |
140 | struct scrub_ctx *ctx, | |
141 | const char *descr, | |
142 | struct xfs_handle *handle, | |
4cca629d | 143 | struct xfs_bulkstat *bstat, |
593f2ab8 | 144 | const struct attrns_decode *attr_ns) |
396cd022 DW |
145 | { |
146 | struct attrlist_cursor cur; | |
147 | char attrbuf[XFS_XATTR_LIST_MAX]; | |
99ea3018 | 148 | char keybuf[XATTR_NAME_MAX + 1]; |
396cd022 DW |
149 | struct attrlist *attrlist = (struct attrlist *)attrbuf; |
150 | struct attrlist_ent *ent; | |
55290cfc | 151 | struct unicrash *uc = NULL; |
396cd022 DW |
152 | bool moveon = true; |
153 | int i; | |
154 | int error; | |
155 | ||
4bbed4ec DW |
156 | moveon = unicrash_xattr_init(&uc, ctx, bstat); |
157 | if (!moveon) | |
158 | return false; | |
159 | ||
396cd022 DW |
160 | memset(attrbuf, 0, XFS_XATTR_LIST_MAX); |
161 | memset(&cur, 0, sizeof(cur)); | |
99ea3018 | 162 | memset(keybuf, 0, XATTR_NAME_MAX + 1); |
396cd022 DW |
163 | error = attr_list_by_handle(handle, sizeof(*handle), attrbuf, |
164 | XFS_XATTR_LIST_MAX, attr_ns->flags, &cur); | |
165 | while (!error) { | |
166 | /* Examine the xattrs. */ | |
167 | for (i = 0; i < attrlist->al_count; i++) { | |
168 | ent = ATTR_ENTRY(attrlist, i); | |
99ea3018 | 169 | snprintf(keybuf, XATTR_NAME_MAX, "%s.%s", attr_ns->name, |
396cd022 | 170 | ent->a_name); |
55290cfc DW |
171 | if (uc) |
172 | moveon = unicrash_check_xattr_name(uc, descr, | |
173 | keybuf); | |
174 | else | |
175 | moveon = xfs_scrub_check_name(ctx, descr, | |
176 | _("extended attribute"), | |
177 | keybuf); | |
4bbed4ec DW |
178 | if (!moveon) |
179 | goto out; | |
396cd022 DW |
180 | } |
181 | ||
182 | if (!attrlist->al_more) | |
183 | break; | |
184 | error = attr_list_by_handle(handle, sizeof(*handle), attrbuf, | |
185 | XFS_XATTR_LIST_MAX, attr_ns->flags, &cur); | |
186 | } | |
187 | if (error && errno != ESTALE) | |
188 | str_errno(ctx, descr); | |
189 | out: | |
4bbed4ec | 190 | unicrash_free(uc); |
396cd022 DW |
191 | return moveon; |
192 | } | |
193 | ||
194 | /* | |
195 | * Check all the xattr names in all the xattr namespaces for problematic | |
196 | * characters. | |
197 | */ | |
198 | static bool | |
199 | xfs_scrub_scan_fhandle_xattrs( | |
200 | struct scrub_ctx *ctx, | |
201 | const char *descr, | |
4bbed4ec | 202 | struct xfs_handle *handle, |
4cca629d | 203 | struct xfs_bulkstat *bstat) |
396cd022 | 204 | { |
593f2ab8 | 205 | const struct attrns_decode *ns; |
396cd022 DW |
206 | bool moveon = true; |
207 | ||
208 | for (ns = attr_ns; ns->name; ns++) { | |
209 | moveon = xfs_scrub_scan_fhandle_namespace_xattrs(ctx, descr, | |
4bbed4ec | 210 | handle, bstat, ns); |
396cd022 DW |
211 | if (!moveon) |
212 | break; | |
213 | } | |
214 | return moveon; | |
215 | } | |
216 | #else | |
4bbed4ec | 217 | # define xfs_scrub_scan_fhandle_xattrs(c, d, h, b) (true) |
396cd022 DW |
218 | #endif /* HAVE_LIBATTR */ |
219 | ||
c4892e76 DW |
220 | /* |
221 | * Verify the connectivity of the directory tree. | |
222 | * We know that the kernel's open-by-handle function will try to reconnect | |
223 | * parents of an opened directory, so we'll accept that as sufficient. | |
4bbed4ec DW |
224 | * |
225 | * Check for potential Unicode collisions in names. | |
c4892e76 DW |
226 | */ |
227 | static int | |
228 | xfs_scrub_connections( | |
229 | struct scrub_ctx *ctx, | |
230 | struct xfs_handle *handle, | |
4cca629d | 231 | struct xfs_bulkstat *bstat, |
c4892e76 DW |
232 | void *arg) |
233 | { | |
234 | bool *pmoveon = arg; | |
235 | char descr[DESCR_BUFSZ]; | |
6d135e84 | 236 | bool moveon = true; |
c4892e76 | 237 | int fd = -1; |
6c05cc5d | 238 | int error; |
c4892e76 | 239 | |
15589f0a DW |
240 | scrub_render_ino_descr(ctx, descr, DESCR_BUFSZ, bstat->bs_ino, |
241 | bstat->bs_gen, NULL); | |
c4892e76 DW |
242 | background_sleep(); |
243 | ||
4bbed4ec | 244 | /* Warn about naming problems in xattrs. */ |
e24ee6b6 DW |
245 | if (bstat->bs_xflags & FS_XFLAG_HASATTR) { |
246 | moveon = xfs_scrub_scan_fhandle_xattrs(ctx, descr, handle, | |
247 | bstat); | |
248 | if (!moveon) | |
249 | goto out; | |
250 | } | |
396cd022 | 251 | |
c4892e76 DW |
252 | /* Open the dir, let the kernel try to reconnect it to the root. */ |
253 | if (S_ISDIR(bstat->bs_mode)) { | |
254 | fd = xfs_open_handle(handle); | |
255 | if (fd < 0) { | |
256 | if (errno == ESTALE) | |
257 | return ESTALE; | |
258 | str_errno(ctx, descr); | |
259 | goto out; | |
260 | } | |
261 | } | |
262 | ||
4bbed4ec DW |
263 | /* Warn about naming problems in the directory entries. */ |
264 | if (fd >= 0 && S_ISDIR(bstat->bs_mode)) { | |
265 | moveon = xfs_scrub_scan_dirents(ctx, descr, &fd, bstat); | |
266 | if (!moveon) | |
267 | goto out; | |
268 | } | |
396cd022 | 269 | |
c4892e76 | 270 | out: |
ed60d210 | 271 | progress_add(1); |
6c05cc5d DW |
272 | if (fd >= 0) { |
273 | error = close(fd); | |
274 | if (error) | |
275 | str_errno(ctx, descr); | |
276 | } | |
c4892e76 DW |
277 | if (!moveon) |
278 | *pmoveon = false; | |
279 | return *pmoveon ? 0 : XFS_ITERATE_INODES_ABORT; | |
280 | } | |
281 | ||
3baa69cd DW |
282 | #ifndef FS_IOC_GETFSLABEL |
283 | # define FSLABEL_MAX 256 | |
284 | # define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX]) | |
285 | #endif /* FS_IOC_GETFSLABEL */ | |
286 | ||
287 | /* | |
288 | * Check the filesystem label for Unicode normalization problems or misleading | |
289 | * sequences. | |
290 | */ | |
291 | static bool | |
292 | xfs_scrub_fs_label( | |
293 | struct scrub_ctx *ctx) | |
294 | { | |
295 | char label[FSLABEL_MAX]; | |
296 | struct unicrash *uc = NULL; | |
297 | bool moveon = true; | |
298 | int error; | |
299 | ||
300 | moveon = unicrash_fs_label_init(&uc, ctx); | |
301 | if (!moveon) | |
302 | return false; | |
303 | ||
304 | /* Retrieve label; quietly bail if we don't support that. */ | |
3f9efb2e | 305 | error = ioctl(ctx->mnt.fd, FS_IOC_GETFSLABEL, &label); |
3baa69cd DW |
306 | if (error) { |
307 | if (errno != EOPNOTSUPP && errno != ENOTTY) { | |
308 | moveon = false; | |
309 | perror(ctx->mntpoint); | |
310 | } | |
311 | goto out; | |
312 | } | |
313 | ||
314 | /* Ignore empty labels. */ | |
315 | if (label[0] == 0) | |
316 | goto out; | |
317 | ||
318 | /* Otherwise check for weirdness. */ | |
319 | if (uc) | |
320 | moveon = unicrash_check_fs_label(uc, ctx->mntpoint, label); | |
321 | else | |
322 | moveon = xfs_scrub_check_name(ctx, ctx->mntpoint, | |
323 | _("filesystem label"), label); | |
324 | if (!moveon) | |
325 | goto out; | |
326 | out: | |
327 | unicrash_free(uc); | |
328 | return moveon; | |
329 | } | |
330 | ||
c4892e76 DW |
331 | /* Check directory connectivity. */ |
332 | bool | |
333 | xfs_scan_connections( | |
334 | struct scrub_ctx *ctx) | |
335 | { | |
336 | bool moveon = true; | |
337 | bool ret; | |
338 | ||
abc2e70d | 339 | if (ctx->corruptions_found) { |
c4892e76 DW |
340 | str_info(ctx, ctx->mntpoint, |
341 | _("Filesystem has errors, skipping connectivity checks.")); | |
342 | return true; | |
343 | } | |
344 | ||
3baa69cd DW |
345 | moveon = xfs_scrub_fs_label(ctx); |
346 | if (!moveon) | |
347 | return false; | |
348 | ||
c4892e76 DW |
349 | ret = xfs_scan_all_inodes(ctx, xfs_scrub_connections, &moveon); |
350 | if (!ret) | |
351 | moveon = false; | |
352 | if (!moveon) | |
353 | return false; | |
354 | xfs_scrub_report_preen_triggers(ctx); | |
355 | return true; | |
356 | } |