]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/findmnt-verify.c
more: Use ul_strtou16() in a robust way
[thirdparty/util-linux.git] / misc-utils / findmnt-verify.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <unistd.h>
5 #include <stdarg.h>
6 #include <string.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <libmount.h>
10 #include <blkid.h>
11 #include <sys/utsname.h>
12 #include <stdbool.h>
13
14 #include "nls.h"
15 #include "c.h"
16 #include "strutils.h"
17 #include "xalloc.h"
18 #include "pathnames.h"
19 #include "match.h"
20
21 #include "findmnt.h"
22
23 struct verify_context {
24 struct libmnt_fs *fs;
25 struct libmnt_table *tb;
26
27 char **fs_ary;
28 size_t fs_num;
29 size_t fs_alloc;
30
31 int nwarnings;
32 int nerrors;
33
34 unsigned int target_printed : 1,
35 no_fsck : 1;
36 };
37
38 static void __attribute__ ((__format__ (__printf__, 3, 0)))
39 verify_mesg(struct verify_context *vfy, char type, const char *fmt, va_list ap)
40 {
41 if (!vfy->target_printed && vfy->fs) {
42 fprintf(stdout, "%s\n", mnt_fs_get_target(vfy->fs));
43 vfy->target_printed = 1;
44 }
45
46 fprintf(stdout, " [%c] ", type);
47 vfprintf(stdout, fmt, ap);
48 fputc('\n', stdout);
49 }
50
51 static int __attribute__ ((__format__ (__printf__, 2, 3)))
52 verify_warn(struct verify_context *vfy, const char *fmt, ...)
53 {
54 va_list ap;
55 vfy->nwarnings++;
56 va_start(ap, fmt);
57 verify_mesg(vfy, 'W', fmt, ap);
58 va_end(ap);
59 return 0;
60 }
61
62 static int __attribute__ ((__format__ (__printf__, 2, 3)))
63 verify_err(struct verify_context *vfy, const char *fmt, ...)
64 {
65 va_list ap;
66 vfy->nerrors++;
67 va_start(ap, fmt);
68 verify_mesg(vfy, 'E', fmt, ap);
69 va_end(ap);
70 return 0;
71 }
72
73 static int __attribute__ ((__format__ (__printf__, 3, 4)))
74 verify_ok(struct verify_context *vfy __attribute__((unused)),
75 bool verbose,
76 const char *fmt, ...)
77 {
78 va_list ap;
79
80 if (!verbose)
81 return 0;
82
83 va_start(ap, fmt);
84 verify_mesg(vfy, ' ', fmt, ap);
85 va_end(ap);
86 return 0;
87 }
88
89 static int verify_order(struct verify_context *vfy, struct findmnt *findmnt)
90 {
91 struct libmnt_iter *itr = NULL;
92 struct libmnt_fs *next;
93 const char *tgt;
94
95 tgt = mnt_fs_get_target(vfy->fs);
96 if (tgt && !(findmnt->flags & FL_NOCACHE))
97 tgt = mnt_resolve_target(tgt, findmnt->cache);
98 else if (!tgt)
99 return 0;
100
101 itr = mnt_new_iter(MNT_ITER_FORWARD);
102 if (!itr) {
103 warn(_("failed to initialize libmount iterator"));
104 goto done;
105 }
106
107 /* set iterator position to 'fs' */
108 mnt_table_set_iter(vfy->tb, itr, vfy->fs);
109
110 if (mnt_table_next_fs(vfy->tb, itr, &next) != 0)
111 goto done;
112
113 /* scan all next filesystems */
114 while (mnt_table_next_fs(vfy->tb, itr, &next) == 0) {
115 const char *n_tgt;
116 size_t len;
117
118 n_tgt = mnt_fs_get_target(next);
119 if (n_tgt && !(findmnt->flags & FL_NOCACHE))
120 n_tgt = mnt_resolve_target(n_tgt, findmnt->cache);
121 else if (!n_tgt)
122 continue;
123 len = strlen(n_tgt);
124
125 if (strncmp(n_tgt, tgt, len) == 0) {
126 if (*(tgt + len) == '\0')
127 verify_warn(vfy, _("target specified more than once"));
128 else if (*(tgt + len) == '/')
129 verify_err(vfy, _("wrong order: %s specified before %s"), tgt, n_tgt);
130 }
131 }
132 done:
133 mnt_free_iter(itr);
134 return 0;
135 }
136
137 static int verify_target(struct verify_context *vfy, struct findmnt *findmnt)
138 {
139 const char *tgt = mnt_fs_get_target(vfy->fs);
140 struct stat sb;
141
142 if (!tgt)
143 return verify_err(vfy, _("undefined target (fs_file)"));
144
145 if (!(findmnt->flags & FL_NOCACHE)) {
146 const char *cn = mnt_resolve_target(tgt, findmnt->cache);
147 if (!cn)
148 return -ENOMEM;
149 if (strcmp(cn, tgt) != 0)
150 verify_warn(vfy, _("non-canonical target path (real: %s)"), cn);
151 tgt = cn;
152 }
153 if (stat(tgt, &sb) != 0) {
154 if (mnt_fs_get_option(vfy->fs, "noauto", NULL, NULL) == 1)
155 verify_err(vfy, _("unreachable on boot required target: %m"));
156 else
157 verify_warn(vfy, _("unreachable target: %m"));
158
159 } else if (!S_ISDIR(sb.st_mode)
160 && mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 1) {
161 verify_err(vfy, _("target is not a directory"));
162 } else
163 verify_ok(vfy, findmnt->flags & FL_VERBOSE, _("target exists"));
164
165 return 0;
166 }
167
168 static char *verify_tag(struct verify_context *vfy, const char *name,
169 const char *value, struct findmnt *findmnt)
170 {
171 char *src = NULL;
172
173 if (!(findmnt->flags & FL_NOCACHE))
174 src = mnt_resolve_tag(name, value, findmnt->cache);
175
176 if (!src) {
177 if (mnt_fs_get_option(vfy->fs, "noauto", NULL, NULL) == 1)
178 verify_err(vfy, _("unreachable on boot required source: %s=%s"), name, value);
179 else
180 verify_warn(vfy, _("unreachable: %s=%s"), name, value);
181 } else
182 verify_ok(vfy, findmnt->flags & FL_VERBOSE, _("%s=%s translated to %s"), name, value, src);
183
184 return src;
185 }
186
187 /* Note that mount source is very FS specific and we should not
188 * interpret unreachable source as error. The exception is only
189 * NAME=value, this has to be convertible to device name.
190 */
191 static int verify_source(struct verify_context *vfy, struct findmnt *findmnt)
192 {
193 const char *src = mnt_fs_get_srcpath(vfy->fs);
194 char *t = NULL, *v = NULL;
195 struct stat sb;
196 int isbind, rc = 0;
197
198 /* source is NAME=value tag */
199 if (!src) {
200 const char *tag = NULL, *val = NULL;
201
202 if (mnt_fs_get_tag(vfy->fs, &tag, &val) != 0)
203 return verify_err(vfy, _("undefined source (fs_spec)"));
204
205 src = verify_tag(vfy, tag, val, findmnt);
206 if (!src)
207 goto done;
208
209 /* blkid is able to parse it, but libmount does not see it as a tag --
210 * it means unsupported tag */
211 } else if (blkid_parse_tag_string(src, &t, &v) == 0 && stat(src, &sb) != 0) {
212 rc = verify_err(vfy, _("unsupported source tag: %s"), src);
213 goto done;
214 }
215 isbind = mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 0;
216
217 /* source is path */
218 if (mnt_fs_is_pseudofs(vfy->fs) || mnt_fs_is_netfs(vfy->fs))
219 verify_ok(vfy, findmnt->flags & FL_VERBOSE, _("do not check %s source (pseudo/net)"), src);
220
221 else if (stat(src, &sb) != 0)
222 verify_warn(vfy, _("unreachable source: %s: %m"), src);
223
224 else if ((S_ISDIR(sb.st_mode) || S_ISREG(sb.st_mode)) && !isbind)
225 verify_warn(vfy, _("non-bind mount source %s is a directory or regular file"), src);
226
227 else if (!S_ISBLK(sb.st_mode) && !isbind)
228 verify_warn(vfy, _("source %s is not a block device"), src);
229 else
230 verify_ok(vfy, findmnt->flags & FL_VERBOSE, _("source %s exists"), src);
231 done:
232 free(t);
233 free(v);
234 return rc;
235 }
236
237 static int verify_options(struct verify_context *vfy, bool verbose)
238 {
239 const char *opts;
240
241 opts = mnt_fs_get_vfs_options(vfy->fs);
242 if (opts)
243 verify_ok(vfy, verbose, _("VFS options: %s"), opts);
244
245 opts = mnt_fs_get_fs_options(vfy->fs);
246 if (opts)
247 verify_ok(vfy, verbose, _("FS options: %s"), opts);
248
249 opts = mnt_fs_get_user_options(vfy->fs);
250 if (opts)
251 verify_ok(vfy, verbose, _("userspace options: %s"), opts);
252
253 return 0;
254 }
255
256 static int verify_swaparea(struct verify_context *vfy)
257 {
258 char *arg;
259 size_t argsz = 0;
260
261 if (mnt_fs_get_option(vfy->fs, "discard", &arg, &argsz) == 0
262 && arg
263 && strncmp(arg, "once", argsz) != 0
264 && strncmp(arg, "pages", argsz) != 0)
265 verify_err(vfy, _("unsupported swaparea discard policy: %s"), arg);
266
267 if (mnt_fs_get_option(vfy->fs, "pri", &arg, &argsz) == 0 && arg) {
268 char *p = arg;
269 if (*p == '-')
270 p++;
271 for (; p < arg + argsz; p++) {
272 if (!isdigit((unsigned char) *p)) {
273 verify_err(vfy, _("failed to parse swaparea priority option"));
274 break;
275 }
276 }
277 }
278
279 return 0;
280 }
281
282 static int is_supported_filesystem(struct verify_context *vfy, const char *name)
283 {
284 size_t n;
285
286 if (!vfy->fs_num)
287 return 0;
288
289 for (n = 0; n < vfy->fs_num; n++ ) {
290 if (match_fstype(vfy->fs_ary[n], name))
291 return 1;
292 }
293
294 return 0;
295 }
296
297 static int add_filesystem(struct verify_context *vfy, const char *name)
298 {
299 #define MYCHUNK 16
300
301 if (is_supported_filesystem(vfy, name))
302 return 0;
303
304 if (vfy->fs_alloc == 0 || vfy->fs_num + 1 <= vfy->fs_alloc) {
305 vfy->fs_alloc = ((vfy->fs_alloc + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK;
306 vfy->fs_ary = xreallocarray(vfy->fs_ary, vfy->fs_alloc, sizeof(char *));
307 }
308
309 vfy->fs_ary[vfy->fs_num] = xstrdup(name);
310 vfy->fs_num++;
311
312 return 0;
313 }
314
315 static int read_proc_filesystems(struct verify_context *vfy)
316 {
317 int rc = 0;
318 FILE *f;
319 char buf[80], *cp, *t;
320
321 f = fopen("/proc/filesystems", "r");
322 if (!f)
323 return -errno;
324
325 while (!feof(f)) {
326 if (!fgets(buf, sizeof(buf), f))
327 break;
328 cp = buf;
329 if (!isspace(*cp)) {
330 while (*cp && !isspace(*cp))
331 cp++;
332 }
333 while (*cp && isspace(*cp))
334 cp++;
335 if ((t = strchr(cp, '\n')) != NULL)
336 *t = 0;
337 if ((t = strchr(cp, '\t')) != NULL)
338 *t = 0;
339 if ((t = strchr(cp, ' ')) != NULL)
340 *t = 0;
341
342 rc = add_filesystem(vfy, cp);
343 if (rc)
344 break;
345 }
346 fclose(f);
347 return rc;
348 }
349
350 static void free_proc_filesystems(struct verify_context *vfy)
351 {
352 size_t n;
353
354 if (!vfy->fs_ary)
355 return;
356
357 for (n = 0; n < vfy->fs_num; n++ )
358 free(vfy->fs_ary[n]);
359 free(vfy->fs_ary);
360 }
361
362 static int read_kernel_filesystems(struct verify_context *vfy)
363 {
364 int rc = 0;
365 #ifdef __linux__
366 struct utsname uts;
367 FILE *f;
368 char buf[1024];
369
370 if (uname(&uts))
371 return 0;
372 snprintf(buf, sizeof(buf), "/lib/modules/%s/modules.dep", uts.release);
373
374 f = fopen(buf, "r");
375 if (!f)
376 return 0;
377
378 while (!feof(f)) {
379 char *p, *name;
380
381 if (!fgets(buf, sizeof(buf), f))
382 break;
383
384 if (strncmp("kernel/fs/", buf, 10) != 0 ||
385 strncmp("kernel/fs/nls/", buf, 14) == 0)
386 continue;
387
388 p = strchr(buf, ':');
389 if (!p)
390 continue;
391 *p = '\0';
392
393 name = strrchr(buf, '/');
394 if (!name)
395 continue;
396 name++;
397
398 p = strstr(name, ".ko");
399 if (!p)
400 continue;
401 *p = '\0';
402
403 rc = add_filesystem(vfy, name);
404 if (rc)
405 break;
406 }
407 fclose(f);
408 #endif /* __linux__ */
409 return rc;
410 }
411
412 static int verify_fstype(struct verify_context *vfy, struct findmnt *findmnt)
413 {
414 char *src = mnt_resolve_spec(mnt_fs_get_source(vfy->fs), findmnt->cache);
415 char *realtype = NULL;
416 const char *type;
417 int ambi = 0, isauto = 0, isswap = 0;
418
419 if (!src)
420 return 0;
421
422 if (mnt_fs_is_pseudofs(vfy->fs) || mnt_fs_is_netfs(vfy->fs)) {
423 verify_ok(vfy, findmnt->flags & FL_VERBOSE, _("do not check %s FS type (pseudo/net)"), src);
424 goto done;
425 }
426
427 type = mnt_fs_get_fstype(vfy->fs);
428
429 if (type) {
430 int none = strcmp(type, "none") == 0;
431
432 if (none
433 && mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 1
434 && mnt_fs_get_option(vfy->fs, "move", NULL, NULL) == 1) {
435 verify_warn(vfy, _("\"none\" FS type is recommended for bind or move operations only"));
436 goto done;
437 }
438
439 if (strcmp(type, "auto") == 0)
440 isauto = 1;
441 else if (strcmp(type, "swap") == 0)
442 isswap = 1;
443 else if (strcmp(type, "xfs") == 0 || strcmp(type, "btrfs") == 0)
444 vfy->no_fsck = 1;
445
446 if (!isswap && !isauto && !none && !is_supported_filesystem(vfy, type))
447 verify_warn(vfy, _("%s seems unsupported by the current kernel"), type);
448 }
449
450 errno = 0;
451 realtype = mnt_get_fstype(src, &ambi, findmnt->cache);
452
453 if (!realtype) {
454 const char *reason = errno ? strerror(errno) : _("reason unknown");
455
456 if (isauto)
457 verify_err(vfy, _("cannot detect on-disk filesystem type (%s)"), reason);
458 else
459 verify_warn(vfy, _("cannot detect on-disk filesystem type (%s)"), reason);
460 goto done;
461 }
462
463 if (realtype) {
464 isswap = strcmp(realtype, "swap") == 0;
465 vfy->no_fsck = strcmp(realtype, "xfs") == 0
466 || strcmp(realtype, "btrfs") == 0;
467
468 if (type && !isauto && strcmp(type, realtype) != 0) {
469 verify_warn(vfy, _("%s does not match with on-disk %s"), type, realtype);
470 goto done;
471 }
472 if (!isswap && !is_supported_filesystem(vfy, realtype)) {
473 verify_warn(vfy, _("on-disk %s seems unsupported by the current kernel"), realtype);
474 goto done;
475 }
476
477 verify_ok(vfy, findmnt->flags & FL_VERBOSE, _("FS type is %s"), realtype);
478 }
479
480 done:
481 if (!findmnt->cache) {
482 free(src);
483 free(realtype);
484 }
485 return 0;
486 }
487
488 static int verify_passno(struct verify_context *vfy)
489 {
490 int passno = mnt_fs_get_passno(vfy->fs);
491 const char *tgt = mnt_fs_get_target(vfy->fs);
492
493 if (tgt && strcmp("/", tgt) == 0 && passno != 1 && !vfy->no_fsck)
494 return verify_warn(vfy, _("recommended root FS passno is 1 (current is %d)"), passno);
495
496 return 0;
497 }
498
499 static int verify_filesystem(struct verify_context *vfy, struct findmnt *findmnt)
500 {
501 int rc = 0;
502
503 if (mnt_fs_is_swaparea(vfy->fs))
504 rc = verify_swaparea(vfy);
505 else {
506 rc = verify_target(vfy, findmnt);
507 if (!rc)
508 rc = verify_options(vfy, findmnt->flags & FL_VERBOSE);
509 }
510
511 if (!rc)
512 rc = verify_source(vfy, findmnt);
513 if (!rc)
514 rc = verify_fstype(vfy, findmnt);
515 if (!rc)
516 rc = verify_passno(vfy); /* depends on verify_fstype() */
517
518 return rc;
519 }
520
521 int verify_table(struct libmnt_table *tb, struct findmnt *findmnt)
522 {
523 struct verify_context vfy = { .nerrors = 0 };
524 struct libmnt_iter *itr;
525 int rc = 0; /* overall return code (alloc errors, etc.) */
526 int check_order = is_listall_mode(findmnt->flags);
527 static int has_read_fs = 0;
528
529 itr = mnt_new_iter(MNT_ITER_FORWARD);
530 if (!itr) {
531 warn(_("failed to initialize libmount iterator"));
532 goto done;
533 }
534
535 vfy.tb = tb;
536
537 if (has_read_fs == 0) {
538 read_proc_filesystems(&vfy);
539 read_kernel_filesystems(&vfy);
540 has_read_fs = 1;
541 }
542
543 while (rc == 0 && (vfy.fs = get_next_fs(tb, itr, findmnt))) {
544 vfy.target_printed = 0;
545 vfy.no_fsck = 0;
546
547 if (check_order)
548 rc = verify_order(&vfy, findmnt);
549 if (!rc)
550 rc = verify_filesystem(&vfy, findmnt);
551
552 if (findmnt->flags & FL_FIRSTONLY)
553 break;
554 findmnt->flags |= FL_NOSWAPMATCH;
555 }
556
557 #ifdef USE_SYSTEMD
558 {
559 struct stat a, b;
560
561 if (stat(_PATH_SD_UNITSLOAD, &a) == 0 &&
562 stat(_PATH_MNTTAB, &b) == 0 &&
563 cmp_stat_mtime(&a, &b, <))
564 verify_warn(&vfy, _(
565 "your fstab has been modified, but systemd still uses the old version;\n"
566 " use 'systemctl daemon-reload' to reload"));
567 }
568 #endif
569
570 done:
571 mnt_free_iter(itr);
572
573 /* summary */
574 if (vfy.nerrors || findmnt->parse_nerrors || vfy.nwarnings) {
575 fputc('\n', stderr);
576 fprintf(stderr, P_("%d parse error", "%d parse errors", findmnt->parse_nerrors), findmnt->parse_nerrors);
577 fprintf(stderr, P_(", %d error", ", %d errors", vfy.nerrors), vfy.nerrors);
578 fprintf(stderr, P_(", %d warning", ", %d warnings", vfy.nwarnings), vfy.nwarnings);
579 fputc('\n', stderr);
580 } else
581 fprintf(stdout, _("Success, no errors or warnings detected\n"));
582
583
584 free_proc_filesystems(&vfy);
585
586 return rc != 0 ? rc : vfy.nerrors + findmnt->parse_nerrors;
587 }