| #include <stdio.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <libmount.h> |
| #include <blkid.h> |
| #include <sys/utsname.h> |
| |
| #include "nls.h" |
| #include "c.h" |
| #include "strutils.h" |
| #include "xalloc.h" |
| |
| #include "findmnt.h" |
| |
| struct verify_context { |
| struct libmnt_fs *fs; |
| struct libmnt_table *tb; |
| |
| char **fs_ary; |
| size_t fs_num; |
| size_t fs_alloc; |
| |
| int nwarnings; |
| int nerrors; |
| |
| unsigned int target_printed : 1; |
| }; |
| |
| static void verify_mesg(struct verify_context *vfy, char type, const char *fmt, va_list ap) |
| { |
| if (!vfy->target_printed) { |
| fprintf(stdout, "%s\n", mnt_fs_get_target(vfy->fs)); |
| vfy->target_printed = 1; |
| } |
| |
| fprintf(stdout, " [%c] ", type); |
| vfprintf(stdout, fmt, ap); |
| fputc('\n', stdout); |
| } |
| |
| static int verify_warn(struct verify_context *vfy, const char *fmt, ...) |
| { |
| va_list ap; |
| vfy->nwarnings++; |
| va_start(ap, fmt); |
| verify_mesg(vfy, 'W', fmt, ap); |
| va_end(ap); |
| return 0; |
| } |
| |
| static int verify_err(struct verify_context *vfy, const char *fmt, ...) |
| { |
| va_list ap; |
| vfy->nerrors++; |
| va_start(ap, fmt); |
| verify_mesg(vfy, 'E', fmt, ap); |
| va_end(ap); |
| return 0; |
| } |
| |
| static int verify_ok(struct verify_context *vfy __attribute__((unused)), |
| const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (!(flags & FL_VERBOSE)) |
| return 0; |
| |
| va_start(ap, fmt); |
| verify_mesg(vfy, ' ', fmt, ap); |
| va_end(ap); |
| return 0; |
| } |
| |
| static int verify_order(struct verify_context *vfy) |
| { |
| struct libmnt_iter *itr = NULL; |
| struct libmnt_fs *next; |
| const char *tgt; |
| |
| tgt = mnt_fs_get_target(vfy->fs); |
| if (tgt && !(flags & FL_NOCACHE)) |
| tgt = mnt_resolve_target(tgt, cache); |
| else if (!tgt) |
| return 0; |
| |
| itr = mnt_new_iter(MNT_ITER_FORWARD); |
| if (!itr) { |
| warn(_("failed to initialize libmount iterator")); |
| goto done; |
| } |
| |
| /* set iterator position to 'fs' */ |
| mnt_table_set_iter(vfy->tb, itr, vfy->fs); |
| mnt_table_next_fs(vfy->tb, itr, &next); |
| |
| /* scan all next filesystems */ |
| while (mnt_table_next_fs(vfy->tb, itr, &next) == 0) { |
| const char *n_tgt; |
| size_t len; |
| |
| n_tgt = mnt_fs_get_target(next); |
| if (n_tgt && !(flags & FL_NOCACHE)) |
| n_tgt = mnt_resolve_target(n_tgt, cache); |
| else if (!n_tgt) |
| continue; |
| len = strlen(n_tgt); |
| |
| if (strncmp(n_tgt, tgt, len) == 0) { |
| if (*(tgt + len) == '\0') |
| verify_warn(vfy, _("target specified more than once")); |
| else if (*(tgt + len) == '/') |
| verify_err(vfy, _("wrong order: %s specified before %s"), tgt, n_tgt); |
| } |
| } |
| done: |
| mnt_free_iter(itr); |
| return 0; |
| } |
| |
| static int verify_target(struct verify_context *vfy) |
| { |
| const char *tgt = mnt_fs_get_target(vfy->fs); |
| struct stat sb; |
| |
| if (!tgt) |
| return verify_err(vfy, _("undefined target (fs_file)")); |
| |
| if (!(flags & FL_NOCACHE)) { |
| const char *cn = mnt_resolve_target(tgt, cache); |
| if (!cn) |
| return -ENOMEM; |
| if (strcmp(cn, tgt) != 0) |
| verify_warn(vfy, _("non-canonical target path (real: %s)"), cn); |
| tgt = cn; |
| } |
| if (stat(tgt, &sb) != 0) { |
| if (mnt_fs_get_option(vfy->fs, "noauto", NULL, NULL) == 1) |
| verify_err(vfy, _("unreachable on boot required target: %m")); |
| else |
| verify_warn(vfy, _("unreachable target: %m")); |
| |
| } else if (!S_ISDIR(sb.st_mode) |
| && mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 1) { |
| verify_err(vfy, _("target is not a directory")); |
| } else |
| verify_ok(vfy, _("target exists")); |
| |
| return 0; |
| } |
| |
| static char *verify_tag(struct verify_context *vfy, const char *name, |
| const char *value) |
| { |
| char *src = mnt_resolve_tag(name, value, cache); |
| |
| if (!src) { |
| if (mnt_fs_get_option(vfy->fs, "noauto", NULL, NULL) == 1) |
| verify_err(vfy, _("unreachable on boot required source: %s=%s"), name, value); |
| else |
| verify_warn(vfy, _("unreachable: %s=%s"), name, value); |
| } else |
| verify_ok(vfy, _("%s=%s translated to %s"), name, value, src); |
| |
| return src; |
| } |
| |
| /* Note that mount source is very FS specific and we should not |
| * interpret unreachable source as error. The exception is only |
| * NAME=value, this has to be convertible to device name. |
| */ |
| static int verify_source(struct verify_context *vfy) |
| { |
| const char *src = mnt_fs_get_srcpath(vfy->fs); |
| char *t = NULL, *v = NULL; |
| struct stat sb; |
| int isbind, rc = 0; |
| |
| /* source is NAME=value tag */ |
| if (!src) { |
| const char *tag = NULL, *val = NULL; |
| |
| if (mnt_fs_get_tag(vfy->fs, &tag, &val) != 0) |
| return verify_err(vfy, _("undefined source (fs_spec)")); |
| |
| src = verify_tag(vfy, tag, val); |
| if (!src) |
| goto done; |
| |
| /* blkid is able to parse it, but libmount does not see it as a tag -- |
| * it means unsupported tag */ |
| } else if (blkid_parse_tag_string(src, &t, &v) == 0 && stat(src, &sb) != 0) { |
| rc = verify_err(vfy, _("unsupported source tag: %s"), src); |
| goto done; |
| } |
| isbind = mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 0; |
| |
| /* source is path */ |
| if (mnt_fs_is_pseudofs(vfy->fs) || mnt_fs_is_netfs(vfy->fs)) |
| verify_ok(vfy, _("do not check %s source (pseudo/net)"), src); |
| |
| else if (stat(src, &sb) != 0) |
| verify_warn(vfy, _("unreachable source: %s: %m"), src); |
| |
| else if ((S_ISDIR(sb.st_mode) || S_ISREG(sb.st_mode)) && !isbind) |
| verify_warn(vfy, _("non-bind mount source %s is a directory or regular file"), src); |
| |
| else if (!S_ISBLK(sb.st_mode) && !isbind) |
| verify_warn(vfy, _("source %s is not a block device"), src); |
| else |
| verify_ok(vfy, _("source %s exists"), src); |
| done: |
| free(t); |
| free(v); |
| return rc; |
| } |
| |
| static int verify_options(struct verify_context *vfy) |
| { |
| const char *opts; |
| |
| opts = mnt_fs_get_vfs_options(vfy->fs); |
| if (opts) |
| verify_ok(vfy, _("VFS options: %s"), opts); |
| |
| opts = mnt_fs_get_fs_options(vfy->fs); |
| if (opts) |
| verify_ok(vfy, _("FS options: %s"), opts); |
| |
| opts = mnt_fs_get_user_options(vfy->fs); |
| if (opts) |
| verify_ok(vfy, _("userspace options: %s"), opts); |
| |
| return 0; |
| } |
| |
| static int verify_swaparea(struct verify_context *vfy) |
| { |
| char *arg; |
| size_t argsz = 0; |
| |
| if (mnt_fs_get_option(vfy->fs, "discard", &arg, &argsz) == 0 |
| && arg |
| && strncmp(arg, "once", argsz) != 0 |
| && strncmp(arg, "pages", argsz) != 0) |
| verify_err(vfy, _("unsupported swaparea discard policy: %s"), arg); |
| |
| if (mnt_fs_get_option(vfy->fs, "pri", &arg, &argsz) == 0 && arg) { |
| char *p = arg; |
| if (*p == '-') |
| p++; |
| for (; p < arg + argsz; p++) { |
| if (!isdigit((unsigned char) *p)) { |
| verify_err(vfy, _("failed to parse swaparea priority option")); |
| break; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int is_supported_filesystem(struct verify_context *vfy, const char *name) |
| { |
| size_t n; |
| |
| if (!vfy->fs_num) |
| return 0; |
| |
| for (n = 0; n < vfy->fs_num; n++ ) { |
| if (strcmp(vfy->fs_ary[n], name) == 0) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int add_filesystem(struct verify_context *vfy, const char *name) |
| { |
| #define MYCHUNK 16 |
| |
| if (is_supported_filesystem(vfy, name)) |
| return 0; |
| |
| if (vfy->fs_alloc == 0 || vfy->fs_num + 1 <= vfy->fs_alloc) { |
| vfy->fs_alloc = ((vfy->fs_alloc + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK; |
| vfy->fs_ary = xrealloc(vfy->fs_ary, vfy->fs_alloc * sizeof(char *)); |
| } |
| |
| vfy->fs_ary[vfy->fs_num] = xstrdup(name); |
| vfy->fs_num++; |
| |
| return 0; |
| } |
| |
| static int read_proc_filesystems(struct verify_context *vfy) |
| { |
| int rc = 0; |
| FILE *f; |
| char buf[80], *cp, *t; |
| |
| f = fopen("/proc/filesystems", "r"); |
| if (!f) |
| return -errno; |
| |
| while (!feof(f)) { |
| if (!fgets(buf, sizeof(buf), f)) |
| break; |
| cp = buf; |
| if (!isspace(*cp)) { |
| while (*cp && !isspace(*cp)) |
| cp++; |
| } |
| while (*cp && isspace(*cp)) |
| cp++; |
| if ((t = strchr(cp, '\n')) != NULL) |
| *t = 0; |
| if ((t = strchr(cp, '\t')) != NULL) |
| *t = 0; |
| if ((t = strchr(cp, ' ')) != NULL) |
| *t = 0; |
| |
| rc = add_filesystem(vfy, cp); |
| if (rc) |
| break; |
| } |
| fclose(f); |
| return rc; |
| } |
| |
| static int read_kernel_filesystems(struct verify_context *vfy) |
| { |
| int rc = 0; |
| #ifdef __linux__ |
| struct utsname uts; |
| FILE *f; |
| char buf[1024]; |
| |
| if (uname(&uts)) |
| return 0; |
| snprintf(buf, sizeof(buf), "/lib/modules/%s/modules.dep", uts.release); |
| |
| f = fopen(buf, "r"); |
| if (!f) |
| return 0; |
| |
| while (!feof(f)) { |
| char *p, *name; |
| |
| if (!fgets(buf, sizeof(buf), f)) |
| break; |
| |
| if (strncmp("kernel/fs/", buf, 10) != 0 || |
| strncmp("kernel/fs/nls/", buf, 14) == 0) |
| continue; |
| |
| p = strchr(buf, ':'); |
| if (!p) |
| continue; |
| *p = '\0'; |
| |
| name = strrchr(buf, '/'); |
| if (!name) |
| continue; |
| name++; |
| |
| p = strstr(name, ".ko"); |
| if (!p) |
| continue; |
| *p = '\0'; |
| |
| rc = add_filesystem(vfy, name); |
| if (rc) |
| break; |
| } |
| fclose(f); |
| #endif /* __linux__ */ |
| return rc; |
| } |
| |
| static int verify_fstype(struct verify_context *vfy) |
| { |
| const char *src = mnt_resolve_spec(mnt_fs_get_source(vfy->fs), cache); |
| const char *type, *realtype; |
| int ambi = 0, isauto = 0, isswap = 0; |
| |
| if (!src) |
| return 0; |
| if (mnt_fs_is_pseudofs(vfy->fs) || mnt_fs_is_netfs(vfy->fs)) |
| return verify_ok(vfy, _("do not check %s FS type (pseudo/net)"), src); |
| |
| type = mnt_fs_get_fstype(vfy->fs); |
| |
| if (type) { |
| int none = strcmp(type, "none") == 0; |
| |
| if (none |
| && mnt_fs_get_option(vfy->fs, "bind", NULL, NULL) == 1 |
| && mnt_fs_get_option(vfy->fs, "move", NULL, NULL) == 1) |
| return verify_warn(vfy, _("\"none\" FS type is recommended for bind or move oprations only")); |
| |
| else if (strcmp(type, "auto") == 0) |
| isauto = 1; |
| else if (strcmp(type, "swap") == 0) |
| isswap = 1; |
| |
| if (!isswap && !isauto && !none && !is_supported_filesystem(vfy, type)) |
| verify_warn(vfy, _("%s seems unsupported by the current kernel"), type); |
| } |
| realtype = mnt_get_fstype(src, &ambi, cache); |
| |
| if (!realtype) { |
| if (isauto) |
| return verify_err(vfy, _("cannot detect on-disk filesystem type")); |
| return verify_warn(vfy, _("cannot detect on-disk filesystem type")); |
| } |
| |
| if (realtype) { |
| isswap = strcmp(realtype, "swap") == 0; |
| |
| if (type && !isauto && strcmp(type, realtype) != 0) |
| return verify_err(vfy, _("%s does not match with on-disk %s"), type, realtype); |
| |
| if (!isswap && !is_supported_filesystem(vfy, realtype)) |
| return verify_err(vfy, _("on-disk %s seems unsupported by the current kernel"), realtype); |
| |
| verify_ok(vfy, _("FS type is %s"), realtype); |
| } |
| |
| return 0; |
| } |
| |
| static int verify_passno(struct verify_context *vfy) |
| { |
| int passno = mnt_fs_get_passno(vfy->fs); |
| const char *tgt = mnt_fs_get_target(vfy->fs); |
| |
| if (tgt && strcmp("/", tgt) == 0 && passno != 1) |
| return verify_warn(vfy, _("recommended root FS passno is 1 (current is %d)"), passno); |
| |
| return 0; |
| } |
| |
| static int verify_filesystem(struct verify_context *vfy) |
| { |
| int rc = 0; |
| |
| if (mnt_fs_is_swaparea(vfy->fs)) |
| rc = verify_swaparea(vfy); |
| else { |
| rc = verify_target(vfy); |
| if (!rc) |
| rc = verify_options(vfy); |
| } |
| |
| if (!rc) |
| rc = verify_source(vfy); |
| if (!rc) |
| rc = verify_fstype(vfy); |
| if (!rc) |
| rc = verify_passno(vfy); |
| |
| return rc; |
| } |
| |
| int verify_table(struct libmnt_table *tb) |
| { |
| struct verify_context vfy = { .nerrors = 0 }; |
| struct libmnt_iter *itr; |
| int rc = 0; /* overall return code (alloc errors, etc.) */ |
| int check_order = is_listall_mode(); |
| static int has_read_fs = 0; |
| |
| itr = mnt_new_iter(MNT_ITER_FORWARD); |
| if (!itr) { |
| warn(_("failed to initialize libmount iterator")); |
| goto done; |
| } |
| |
| vfy.tb = tb; |
| |
| if (has_read_fs == 0) { |
| read_proc_filesystems(&vfy); |
| read_kernel_filesystems(&vfy); |
| has_read_fs = 1; |
| } |
| |
| while (rc == 0 && (vfy.fs = get_next_fs(tb, itr))) { |
| vfy.target_printed = 0; |
| if (check_order) |
| rc = verify_order(&vfy); |
| if (!rc) |
| rc = verify_filesystem(&vfy); |
| |
| if (flags & FL_FIRSTONLY) |
| break; |
| flags |= FL_NOSWAPMATCH; |
| } |
| |
| done: |
| mnt_free_iter(itr); |
| |
| /* summary */ |
| if (vfy.nerrors || parse_nerrors || vfy.nwarnings) { |
| fputc('\n', stderr); |
| fprintf(stderr, P_("%d parse error", "%d parse errors", parse_nerrors), parse_nerrors); |
| fprintf(stderr, P_(", %d error", ", %d errors", vfy.nerrors), vfy.nerrors); |
| fprintf(stderr, P_(", %d warning", ", %d warnings", vfy.nwarnings), vfy.nwarnings); |
| fputc('\n', stderr); |
| } else |
| fprintf(stdout, _("Success, no errors or warnings detected\n")); |
| |
| return rc != 0 ? rc : vfy.nerrors + parse_nerrors; |
| } |