summaryrefslogtreecommitdiffstats
path: root/misc-utils/findmnt-verify.c
diff options
context:
space:
mode:
Diffstat (limited to 'misc-utils/findmnt-verify.c')
-rw-r--r--misc-utils/findmnt-verify.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/misc-utils/findmnt-verify.c b/misc-utils/findmnt-verify.c
new file mode 100644
index 0000000..d9a59c1
--- /dev/null
+++ b/misc-utils/findmnt-verify.c
@@ -0,0 +1,585 @@
+#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 "pathnames.h"
+#include "match.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,
+ no_fsck : 1;
+};
+
+static void __attribute__ ((__format__ (__printf__, 3, 0)))
+ verify_mesg(struct verify_context *vfy, char type, const char *fmt, va_list ap)
+{
+ if (!vfy->target_printed && vfy->fs) {
+ 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 __attribute__ ((__format__ (__printf__, 2, 3)))
+ 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 __attribute__ ((__format__ (__printf__, 2, 3)))
+ 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 __attribute__ ((__format__ (__printf__, 2, 3)))
+ 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);
+
+ if (mnt_table_next_fs(vfy->tb, itr, &next) != 0)
+ goto done;
+
+ /* 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 = NULL;
+
+ if (!(flags & FL_NOCACHE))
+ 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 (match_fstype(vfy->fs_ary[n], name))
+ 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 void free_proc_filesystems(struct verify_context *vfy)
+{
+ size_t n;
+
+ if (!vfy->fs_ary)
+ return;
+
+ for (n = 0; n < vfy->fs_num; n++ )
+ free(vfy->fs_ary[n]);
+ free(vfy->fs_ary);
+}
+
+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)
+{
+ char *src = mnt_resolve_spec(mnt_fs_get_source(vfy->fs), cache);
+ char *realtype = NULL;
+ const char *type;
+ int ambi = 0, isauto = 0, isswap = 0;
+
+ if (!src)
+ return 0;
+
+ if (mnt_fs_is_pseudofs(vfy->fs) || mnt_fs_is_netfs(vfy->fs)) {
+ verify_ok(vfy, _("do not check %s FS type (pseudo/net)"), src);
+ goto done;
+ }
+
+ 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) {
+ verify_warn(vfy, _("\"none\" FS type is recommended for bind or move oprations only"));
+ goto done;
+ }
+
+ if (strcmp(type, "auto") == 0)
+ isauto = 1;
+ else if (strcmp(type, "swap") == 0)
+ isswap = 1;
+ else if (strcmp(type, "xfs") == 0 || strcmp(type, "btrfs") == 0)
+ vfy->no_fsck = 1;
+
+ if (!isswap && !isauto && !none && !is_supported_filesystem(vfy, type))
+ verify_warn(vfy, _("%s seems unsupported by the current kernel"), type);
+ }
+
+ errno = 0;
+ realtype = mnt_get_fstype(src, &ambi, cache);
+
+ if (!realtype) {
+ const char *reson = errno ? strerror(errno) : _("reason unknown");
+
+ if (isauto)
+ verify_err(vfy, _("cannot detect on-disk filesystem type (%s)"), reson);
+ else
+ verify_warn(vfy, _("cannot detect on-disk filesystem type (%s)"), reson);
+ goto done;
+ }
+
+ if (realtype) {
+ isswap = strcmp(realtype, "swap") == 0;
+ vfy->no_fsck = strcmp(realtype, "xfs") == 0
+ || strcmp(realtype, "btrfs") == 0;
+
+ if (type && !isauto && strcmp(type, realtype) != 0) {
+ verify_err(vfy, _("%s does not match with on-disk %s"), type, realtype);
+ goto done;
+ }
+ if (!isswap && !is_supported_filesystem(vfy, realtype)) {
+ verify_err(vfy, _("on-disk %s seems unsupported by the current kernel"), realtype);
+ goto done;
+ }
+
+ verify_ok(vfy, _("FS type is %s"), realtype);
+ }
+
+done:
+ if (!cache) {
+ free(src);
+ free(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 && !vfy->no_fsck)
+ 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); /* depends on verify_fstype() */
+
+ 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;
+ vfy.no_fsck = 0;
+
+ if (check_order)
+ rc = verify_order(&vfy);
+ if (!rc)
+ rc = verify_filesystem(&vfy);
+
+ if (flags & FL_FIRSTONLY)
+ break;
+ flags |= FL_NOSWAPMATCH;
+ }
+
+#ifdef USE_SYSTEMD
+ {
+ struct stat a, b;
+
+ if (stat(_PATH_SD_UNITSLOAD, &a) == 0 &&
+ stat(_PATH_MNTTAB, &b) == 0 &&
+ cmp_stat_mtime(&a, &b, <))
+ verify_warn(&vfy, _(
+ "your fstab has been modified, but systemd still uses the old version;\n"
+ " use 'systemctl daemon-reload' to reload"));
+ }
+#endif
+
+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"));
+
+
+ free_proc_filesystems(&vfy);
+
+ return rc != 0 ? rc : vfy.nerrors + parse_nerrors;
+}