]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blob - scrub/phase5.c
libfrog: move path.h to libfrog/
[thirdparty/xfsprogs-dev.git] / scrub / phase5.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (C) 2018 Oracle. All Rights Reserved.
4 * Author: Darrick J. Wong <darrick.wong@oracle.com>
5 */
6 #include "xfs.h"
7 #include <stdint.h>
8 #include <dirent.h>
9 #include <sys/types.h>
10 #include <sys/statvfs.h>
11 #ifdef HAVE_LIBATTR
12 # include <attr/attributes.h>
13 #endif
14 #include <linux/fs.h>
15 #include "handle.h"
16 #include "list.h"
17 #include "libfrog/paths.h"
18 #include "libfrog/workqueue.h"
19 #include "xfs_scrub.h"
20 #include "common.h"
21 #include "inodes.h"
22 #include "progress.h"
23 #include "scrub.h"
24 #include "unicrash.h"
25
26 /* Phase 5: Check directory connectivity. */
27
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,
82 int *fd,
83 struct xfs_bstat *bstat)
84 {
85 struct unicrash *uc = NULL;
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
97 moveon = unicrash_dir_init(&uc, ctx, bstat);
98 if (!moveon)
99 goto out_unicrash;
100
101 dentry = readdir(dir);
102 while (dentry) {
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);
108 if (!moveon)
109 break;
110 dentry = readdir(dir);
111 }
112 unicrash_free(uc);
113
114 out_unicrash:
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. */
122 struct attrns_decode {
123 int flags;
124 const char *name;
125 };
126
127 static const struct attrns_decode attr_ns[] = {
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,
143 struct xfs_bstat *bstat,
144 const struct attrns_decode *attr_ns)
145 {
146 struct attrlist_cursor cur;
147 char attrbuf[XFS_XATTR_LIST_MAX];
148 char keybuf[XATTR_NAME_MAX + 1];
149 struct attrlist *attrlist = (struct attrlist *)attrbuf;
150 struct attrlist_ent *ent;
151 struct unicrash *uc = NULL;
152 bool moveon = true;
153 int i;
154 int error;
155
156 moveon = unicrash_xattr_init(&uc, ctx, bstat);
157 if (!moveon)
158 return false;
159
160 memset(attrbuf, 0, XFS_XATTR_LIST_MAX);
161 memset(&cur, 0, sizeof(cur));
162 memset(keybuf, 0, XATTR_NAME_MAX + 1);
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);
169 snprintf(keybuf, XATTR_NAME_MAX, "%s.%s", attr_ns->name,
170 ent->a_name);
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);
178 if (!moveon)
179 goto out;
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:
190 unicrash_free(uc);
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,
202 struct xfs_handle *handle,
203 struct xfs_bstat *bstat)
204 {
205 const struct attrns_decode *ns;
206 bool moveon = true;
207
208 for (ns = attr_ns; ns->name; ns++) {
209 moveon = xfs_scrub_scan_fhandle_namespace_xattrs(ctx, descr,
210 handle, bstat, ns);
211 if (!moveon)
212 break;
213 }
214 return moveon;
215 }
216 #else
217 # define xfs_scrub_scan_fhandle_xattrs(c, d, h, b) (true)
218 #endif /* HAVE_LIBATTR */
219
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.
224 *
225 * Check for potential Unicode collisions in names.
226 */
227 static int
228 xfs_scrub_connections(
229 struct scrub_ctx *ctx,
230 struct xfs_handle *handle,
231 struct xfs_bstat *bstat,
232 void *arg)
233 {
234 bool *pmoveon = arg;
235 char descr[DESCR_BUFSZ];
236 bool moveon = true;
237 xfs_agnumber_t agno;
238 xfs_agino_t agino;
239 int fd = -1;
240 int error;
241
242 agno = cvt_ino_to_agno(&ctx->mnt, bstat->bs_ino);
243 agino = cvt_ino_to_agino(&ctx->mnt, bstat->bs_ino);
244 snprintf(descr, DESCR_BUFSZ, _("inode %"PRIu64" (%u/%u)"),
245 (uint64_t)bstat->bs_ino, agno, agino);
246 background_sleep();
247
248 /* Warn about naming problems in xattrs. */
249 if (bstat->bs_xflags & FS_XFLAG_HASATTR) {
250 moveon = xfs_scrub_scan_fhandle_xattrs(ctx, descr, handle,
251 bstat);
252 if (!moveon)
253 goto out;
254 }
255
256 /* Open the dir, let the kernel try to reconnect it to the root. */
257 if (S_ISDIR(bstat->bs_mode)) {
258 fd = xfs_open_handle(handle);
259 if (fd < 0) {
260 if (errno == ESTALE)
261 return ESTALE;
262 str_errno(ctx, descr);
263 goto out;
264 }
265 }
266
267 /* Warn about naming problems in the directory entries. */
268 if (fd >= 0 && S_ISDIR(bstat->bs_mode)) {
269 moveon = xfs_scrub_scan_dirents(ctx, descr, &fd, bstat);
270 if (!moveon)
271 goto out;
272 }
273
274 out:
275 progress_add(1);
276 if (fd >= 0) {
277 error = close(fd);
278 if (error)
279 str_errno(ctx, descr);
280 }
281 if (!moveon)
282 *pmoveon = false;
283 return *pmoveon ? 0 : XFS_ITERATE_INODES_ABORT;
284 }
285
286 #ifndef FS_IOC_GETFSLABEL
287 # define FSLABEL_MAX 256
288 # define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX])
289 #endif /* FS_IOC_GETFSLABEL */
290
291 /*
292 * Check the filesystem label for Unicode normalization problems or misleading
293 * sequences.
294 */
295 static bool
296 xfs_scrub_fs_label(
297 struct scrub_ctx *ctx)
298 {
299 char label[FSLABEL_MAX];
300 struct unicrash *uc = NULL;
301 bool moveon = true;
302 int error;
303
304 moveon = unicrash_fs_label_init(&uc, ctx);
305 if (!moveon)
306 return false;
307
308 /* Retrieve label; quietly bail if we don't support that. */
309 error = ioctl(ctx->mnt.fd, FS_IOC_GETFSLABEL, &label);
310 if (error) {
311 if (errno != EOPNOTSUPP && errno != ENOTTY) {
312 moveon = false;
313 perror(ctx->mntpoint);
314 }
315 goto out;
316 }
317
318 /* Ignore empty labels. */
319 if (label[0] == 0)
320 goto out;
321
322 /* Otherwise check for weirdness. */
323 if (uc)
324 moveon = unicrash_check_fs_label(uc, ctx->mntpoint, label);
325 else
326 moveon = xfs_scrub_check_name(ctx, ctx->mntpoint,
327 _("filesystem label"), label);
328 if (!moveon)
329 goto out;
330 out:
331 unicrash_free(uc);
332 return moveon;
333 }
334
335 /* Check directory connectivity. */
336 bool
337 xfs_scan_connections(
338 struct scrub_ctx *ctx)
339 {
340 bool moveon = true;
341 bool ret;
342
343 if (ctx->errors_found) {
344 str_info(ctx, ctx->mntpoint,
345 _("Filesystem has errors, skipping connectivity checks."));
346 return true;
347 }
348
349 moveon = xfs_scrub_fs_label(ctx);
350 if (!moveon)
351 return false;
352
353 ret = xfs_scan_all_inodes(ctx, xfs_scrub_connections, &moveon);
354 if (!ret)
355 moveon = false;
356 if (!moveon)
357 return false;
358 xfs_scrub_report_preen_triggers(ctx);
359 return true;
360 }