]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - libmount/src/utils.c
libmount: Recognize more FUSE pseudofs (avfsd, lxcfs, vmware-vmblock)
[thirdparty/util-linux.git] / libmount / src / utils.c
index 6f4d1a19f726fd2856026259fc7bd39abfa6242e..708da2e008b594161147046c97dfb9c58383a50b 100644 (file)
@@ -1,8 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
- * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ * This file is part of libmount from util-linux project.
  *
- * This file may be redistributed under the terms of the
- * GNU Lesser General Public License.
+ * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
  */
 
 /**
@@ -14,6 +19,7 @@
 #include <fcntl.h>
 #include <pwd.h>
 #include <grp.h>
+#include <poll.h>
 #include <blkid.h>
 
 #include "strutils.h"
@@ -25,6 +31,7 @@
 #include "match.h"
 #include "fileutils.h"
 #include "statfs_magic.h"
+#include "sysfs.h"
 
 int append_string(char **a, const char *b)
 {
@@ -111,12 +118,21 @@ int mnt_parse_offset(const char *str, size_t len, uintmax_t *res)
 /* used as a callback by bsearch in mnt_fstype_is_pseudofs() */
 static int fstype_cmp(const void *v1, const void *v2)
 {
-       const char *s1 = *(const char **)v1;
-       const char *s2 = *(const char **)v2;
+       const char *s1 = *(char * const *)v1;
+       const char *s2 = *(char * const *)v2;
 
        return strcmp(s1, s2);
 }
 
+int mnt_stat_mountpoint(const char *target, struct stat *st)
+{
+#ifdef AT_NO_AUTOMOUNT
+       return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT);
+#else
+       return stat(target, st);
+#endif
+}
+
 /*
  * Note that the @target has to be an absolute path (so at least "/").  The
  * @filename returns an allocated buffer with the last path component, for example:
@@ -206,6 +222,8 @@ int mnt_is_readonly(const char *path)
        {
                struct timespec times[2];
 
+               DBG(UTILS, ul_debug(" doing utimensat() based write test"));
+
                times[0].tv_nsec = UTIME_NOW;   /* atime */
                times[1].tv_nsec = UTIME_OMIT;  /* mtime */
 
@@ -256,7 +274,9 @@ int mnt_fstype_is_pseudofs(const char *type)
                "autofs",
                "bdev",
                "binfmt_misc",
+               "bpf",
                "cgroup",
+               "cgroup2",
                "configfs",
                "cpuset",
                "debugfs",
@@ -265,12 +285,24 @@ int mnt_fstype_is_pseudofs(const char *type)
                "devtmpfs",
                "dlmfs",
                "efivarfs",
+               "fuse", /* Fallback name of fuse used by many poorly written drivers. */
+               "fuse.archivemount", /* Not a true pseudofs (has source), but source is not reported. */
+               "fuse.avfsd", /* Not a true pseudofs (has source), but source is not reported. */
+               "fuse.dumpfs", /* In fact, it is a netfs, but source is not reported. */
+               "fuse.encfs", /* Not a true pseudofs (has source), but source is not reported. */
+               "fuse.gvfs-fuse-daemon", /* Old name, not used by gvfs any more. */
+               "fuse.gvfsd-fuse",
+               "fuse.lxcfs",
+               "fuse.rofiles-fuse",
+               "fuse.vmware-vmblock",
+               "fuse.xwmfs",
                "fusectl",
-               "fuse.gvfs-fuse-daemon",
                "hugetlbfs",
                "mqueue",
                "nfsd",
                "none",
+               "nsfs",
+               "overlay",
                "pipefs",
                "proc",
                "pstore",
@@ -278,6 +310,7 @@ int mnt_fstype_is_pseudofs(const char *type)
                "rootfs",
                "rpc_pipefs",
                "securityfs",
+               "selinuxfs",
                "sockfs",
                "spufs",
                "sysfs",
@@ -303,6 +336,8 @@ int mnt_fstype_is_netfs(const char *type)
            strncmp(type,"nfs", 3) == 0 ||
            strcmp(type, "afs")    == 0 ||
            strcmp(type, "ncpfs")  == 0 ||
+           strcmp(type, "fuse.curlftpfs") == 0 ||
+           strcmp(type, "fuse.sshfs") == 0 ||
            strncmp(type,"9p", 2)  == 0)
                return 1;
        return 0;
@@ -393,6 +428,12 @@ const char *mnt_statfs_get_fstype(struct statfs *vfs)
        return NULL;
 }
 
+int is_procfs_fd(int fd)
+{
+       struct statfs sfs;
+
+       return fstatfs(fd, &sfs) == 0 && sfs.f_type == STATFS_PROC_MAGIC;
+}
 
 /**
  * mnt_match_fstype:
@@ -418,100 +459,6 @@ int mnt_match_fstype(const char *type, const char *pattern)
        return match_fstype(type, pattern);
 }
 
-
-/* Returns 1 if needle found or noneedle not found in haystack
- * Otherwise returns 0
- */
-static int check_option(const char *haystack, size_t len,
-                       const char *needle, size_t needle_len)
-{
-       const char *p;
-       int no = 0;
-
-       if (needle_len >= 1 && *needle == '+') {
-               needle++;
-               needle_len--;
-       } else if (needle_len >= 2 && !strncmp(needle, "no", 2)) {
-               no = 1;
-               needle += 2;
-               needle_len -= 2;
-       }
-
-       for (p = haystack; p && p < haystack + len; p++) {
-               char *sep = strchr(p, ',');
-               size_t plen = sep ? (size_t) (sep - p) :
-                                   len - (p - haystack);
-
-               if (plen == needle_len) {
-                       if (!strncmp(p, needle, plen))
-                               return !no;     /* foo or nofoo was found */
-               }
-               p += plen;
-       }
-
-       return no;  /* foo or nofoo was not found */
-}
-
-/**
- * mnt_match_options:
- * @optstr: options string
- * @pattern: comma delimited list of options
- *
- * The "no" could be used for individual items in the @options list. The "no"
- * prefix does not have a global meaning.
- *
- * Unlike fs type matching, nonetdev,user and nonetdev,nouser have
- * DIFFERENT meanings; each option is matched explicitly as specified.
- *
- * The "no" prefix interpretation could be disabled by the "+" prefix, for example
- * "+noauto" matches if @optstr literally contains the "noauto" string.
- *
- * "xxx,yyy,zzz" : "nozzz"     -> False
- *
- * "xxx,yyy,zzz" : "xxx,noeee" -> True
- *
- * "bar,zzz"     : "nofoo"      -> True
- *
- * "nofoo,bar"   : "+nofoo"     -> True
- *
- * "bar,zzz"     : "+nofoo"     -> False
- *
- *
- * Returns: 1 if pattern is matching, else 0. This function also returns 0
- *          if @pattern is NULL and @optstr is non-NULL.
- */
-int mnt_match_options(const char *optstr, const char *pattern)
-{
-       const char *p;
-       size_t len, optstr_len = 0;
-
-       if (!pattern && !optstr)
-               return 1;
-       if (!pattern)
-               return 0;
-
-       len = strlen(pattern);
-       if (optstr)
-               optstr_len = strlen(optstr);
-
-       for (p = pattern; p < pattern + len; p++) {
-               char *sep = strchr(p, ',');
-               size_t plen = sep ? (size_t) (sep - p) :
-                                   len - (p - pattern);
-
-               if (!plen)
-                       continue; /* if two ',' appear in a row */
-
-               if (!check_option(optstr, optstr_len, p, plen))
-                       return 0; /* any match failure means failure */
-
-               p += plen;
-       }
-
-       /* no match failures in list means success */
-       return 1;
-}
-
 void mnt_free_filesystems(char **filesystems)
 {
        char **p;
@@ -549,10 +496,10 @@ static int add_filesystem(char ***filesystems, char *name)
                *filesystems = x;
        }
        name = strdup(name);
-       if (!name)
-               goto err;
        (*filesystems)[n] = name;
        (*filesystems)[n + 1] = NULL;
+       if (!name)
+               goto err;
        return 0;
 err:
        mnt_free_filesystems(*filesystems);
@@ -624,16 +571,6 @@ int mnt_get_filesystems(char ***filesystems, const char *pattern)
        return rc;
 }
 
-static size_t get_pw_record_size(void)
-{
-#ifdef _SC_GETPW_R_SIZE_MAX
-       long sz = sysconf(_SC_GETPW_R_SIZE_MAX);
-       if (sz > 0)
-               return sz;
-#endif
-       return 16384;
-}
-
 /*
  * Returns an allocated string with username or NULL.
  */
@@ -641,14 +578,13 @@ char *mnt_get_username(const uid_t uid)
 {
         struct passwd pwd;
        struct passwd *res;
-       size_t sz = get_pw_record_size();
        char *buf, *username = NULL;
 
-       buf = malloc(sz);
+       buf = malloc(UL_GETPW_BUFSIZ);
        if (!buf)
                return NULL;
 
-       if (!getpwuid_r(uid, &pwd, buf, sz, &res) && res)
+       if (!getpwuid_r(uid, &pwd, buf, UL_GETPW_BUFSIZ, &res) && res)
                username = strdup(pwd.pw_name);
 
        free(buf);
@@ -660,17 +596,16 @@ int mnt_get_uid(const char *username, uid_t *uid)
        int rc = -1;
         struct passwd pwd;
        struct passwd *pw;
-       size_t sz = get_pw_record_size();
        char *buf;
 
        if (!username || !uid)
                return -EINVAL;
 
-       buf = malloc(sz);
+       buf = malloc(UL_GETPW_BUFSIZ);
        if (!buf)
                return -ENOMEM;
 
-       if (!getpwnam_r(username, &pwd, buf, sz, &pw) && pw) {
+       if (!getpwnam_r(username, &pwd, buf, UL_GETPW_BUFSIZ, &pw) && pw) {
                *uid= pw->pw_uid;
                rc = 0;
        } else {
@@ -688,17 +623,16 @@ int mnt_get_gid(const char *groupname, gid_t *gid)
        int rc = -1;
         struct group grp;
        struct group *gr;
-       size_t sz = get_pw_record_size();
        char *buf;
 
        if (!groupname || !gid)
                return -EINVAL;
 
-       buf = malloc(sz);
+       buf = malloc(UL_GETPW_BUFSIZ);
        if (!buf)
                return -ENOMEM;
 
-       if (!getgrnam_r(groupname, &grp, buf, sz, &gr) && gr) {
+       if (!getgrnam_r(groupname, &grp, buf, UL_GETPW_BUFSIZ, &gr) && gr) {
                *gid= gr->gr_gid;
                rc = 0;
        } else {
@@ -740,20 +674,45 @@ done:
        return rc;
 }
 
-static int try_write(const char *filename)
+static int try_write(const char *filename, const char *directory)
 {
-       int fd;
+       int rc = 0;
 
        if (!filename)
                return -EINVAL;
 
-       fd = open(filename, O_RDWR|O_CREAT|O_CLOEXEC,
-                           S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
-       if (fd >= 0) {
-               close(fd);
+       DBG(UTILS, ul_debug("try write %s dir: %s", filename, directory));
+
+#ifdef HAVE_EACCESS
+       /* Try eaccess() first, because open() is overkill, may be monitored by
+        * audit and we don't want to fill logs by our checks...
+        */
+       if (eaccess(filename, R_OK|W_OK) == 0) {
+               DBG(UTILS, ul_debug(" access OK"));
                return 0;
+       } else if (errno != ENOENT) {
+               DBG(UTILS, ul_debug(" access FAILED"));
+               return -errno;
+       } else if (directory) {
+               /* file does not exist; try if directory is writable */
+               if (eaccess(directory, R_OK|W_OK) != 0)
+                       rc = -errno;
+
+               DBG(UTILS, ul_debug(" access %s [%s]", rc ? "FAILED" : "OK", directory));
+               return rc;
+       } else
+#endif
+       {
+               DBG(UTILS, ul_debug(" doing open-write test"));
+
+               int fd = open(filename, O_RDWR|O_CREAT|O_CLOEXEC,
+                           S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
+               if (fd < 0)
+                       rc = -errno;
+               else
+                       close(fd);
        }
-       return -errno;
+       return rc;
 }
 
 /**
@@ -786,7 +745,8 @@ int mnt_has_regular_mtab(const char **mtab, int *writable)
                /* file exists */
                if (S_ISREG(st.st_mode)) {
                        if (writable)
-                               *writable = !try_write(filename);
+                               *writable = !try_write(filename, NULL);
+                       DBG(UTILS, ul_debug("%s: writable", filename));
                        return 1;
                }
                goto done;
@@ -794,9 +754,11 @@ int mnt_has_regular_mtab(const char **mtab, int *writable)
 
        /* try to create the file */
        if (writable) {
-               *writable = !try_write(filename);
-               if (*writable)
+               *writable = !try_write(filename, NULL);
+               if (*writable) {
+                       DBG(UTILS, ul_debug("%s: writable", filename));
                        return 1;
+               }
        }
 
 done:
@@ -832,7 +794,7 @@ int mnt_has_regular_utab(const char **utab, int *writable)
                /* file exists */
                if (S_ISREG(st.st_mode)) {
                        if (writable)
-                               *writable = !try_write(filename);
+                               *writable = !try_write(filename, NULL);
                        return 1;
                }
                goto done;      /* it's not a regular file */
@@ -849,11 +811,13 @@ int mnt_has_regular_utab(const char **utab, int *writable)
                rc = mkdir(dirname, S_IWUSR|
                                    S_IRUSR|S_IRGRP|S_IROTH|
                                    S_IXUSR|S_IXGRP|S_IXOTH);
-               free(dirname);
-               if (rc && errno != EEXIST)
+               if (rc && errno != EEXIST) {
+                       free(dirname);
                        goto done;                      /* probably EACCES */
+               }
 
-               *writable = !try_write(filename);
+               *writable = !try_write(filename, dirname);
+               free(dirname);
                if (*writable)
                        return 1;
        }
@@ -961,7 +925,7 @@ int mnt_open_uniq_filename(const char *filename, char **name)
  * should be canonicalized. The returned pointer should be freed by the caller.
  *
  * WARNING: the function compares st_dev of the @path elements. This traditional
- * way maybe be insufficient on filesystems like Linux "overlay". See also
+ * way may be insufficient on filesystems like Linux "overlay". See also
  * mnt_table_find_target().
  *
  * Returns: allocated string with the target of the mounted device or NULL on error
@@ -981,7 +945,7 @@ char *mnt_get_mountpoint(const char *path)
        if (*mnt == '/' && *(mnt + 1) == '\0')
                goto done;
 
-       if (stat(mnt, &st))
+       if (mnt_stat_mountpoint(mnt, &st))
                goto err;
        base = st.st_dev;
 
@@ -990,7 +954,7 @@ char *mnt_get_mountpoint(const char *path)
 
                if (!p)
                        break;
-               if (stat(*mnt ? mnt : "/", &st))
+               if (mnt_stat_mountpoint(*mnt ? mnt : "/", &st))
                        goto err;
                dir = st.st_dev;
                if (dir != base) {
@@ -1010,30 +974,8 @@ err:
        return NULL;
 }
 
-char *mnt_get_fs_root(const char *path, const char *mnt)
-{
-       char *m = (char *) mnt, *res;
-       const char *p;
-       size_t sz;
-
-       if (!m)
-               m = mnt_get_mountpoint(path);
-       if (!m)
-               return NULL;
-
-       sz = strlen(m);
-       p = sz > 1 ? path + sz : path;
-
-       if (m != mnt)
-               free(m);
-
-       res = *p ? strdup(p) : strdup("/");
-       DBG(UTILS, ul_debug("%s fs-root is %s", path, res));
-       return res;
-}
-
 /*
- * Search for @name kernel command parametr.
+ * Search for @name kernel command parameter.
  *
  * Returns newly allocated string with a parameter argument if the @name is
  * specified as "name=" or returns pointer to @name or returns NULL if not
@@ -1116,8 +1058,257 @@ char *mnt_get_kernel_cmdline_option(const char *name)
        return res;
 }
 
+/**
+ * mnt_guess_system_root:
+ * @devno: device number or zero
+ * @cache: paths cache or NULL
+ * @path: returns allocated path
+ *
+ * Converts @devno to the real device name if devno major number is greater
+ * than zero, otherwise use root= kernel cmdline option to get device name.
+ *
+ * The function uses /sys to convert devno to device name.
+ *
+ * Returns: 0 = success, 1 = not found, <0 = error
+ *
+ * Since: 2.34
+ */
+int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path)
+{
+       char buf[PATH_MAX];
+       char *dev = NULL, *spec = NULL;
+       unsigned int x, y;
+       int allocated = 0;
+
+       assert(path);
+
+       DBG(UTILS, ul_debug("guessing system root [devno %u:%u]", major(devno), minor(devno)));
+
+       /* The pseudo-fs, net-fs or btrfs devno is useless, otherwise it
+        * usually matches with the source device, let's try to use it.
+        */
+       if (major(devno) > 0) {
+               dev = sysfs_devno_to_devpath(devno, buf, sizeof(buf));
+               if (dev) {
+                       DBG(UTILS, ul_debug("  devno converted to %s", dev));
+                       goto done;
+               }
+       }
+
+       /* Let's try to use root= kernel command line option
+        */
+       spec = mnt_get_kernel_cmdline_option("root=");
+       if (!spec)
+               goto done;
+
+       /* maj:min notation */
+       if (sscanf(spec, "%u:%u", &x, &y) == 2) {
+               dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf));
+               if (dev) {
+                       DBG(UTILS, ul_debug("  root=%s converted to %s", spec, dev));
+                       goto done;
+               }
+
+       /* hexhex notation */
+       } else if (isxdigit_string(spec)) {
+               char *end = NULL;
+               uint32_t n;
+
+               errno = 0;
+               n = strtoul(spec, &end, 16);
+
+               if (errno || spec == end || (end && *end))
+                       DBG(UTILS, ul_debug("  failed to parse root='%s'", spec));
+               else {
+                       /* kernel new_decode_dev() */
+                       x = (n & 0xfff00) >> 8;
+                       y = (n & 0xff) | ((n >> 12) & 0xfff00);
+                       dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf));
+                       if (dev) {
+                               DBG(UTILS, ul_debug("  root=%s converted to %s", spec, dev));
+                               goto done;
+                       }
+               }
+
+       /* devname or PARTUUID= etc. */
+       } else {
+               DBG(UTILS, ul_debug("  converting root='%s'", spec));
+
+               dev = mnt_resolve_spec(spec, cache);
+               if (dev && !cache)
+                       allocated = 1;
+       }
+done:
+       free(spec);
+       if (dev) {
+               *path = allocated ? dev : strdup(dev);
+               if (!*path)
+                       return -ENOMEM;
+               return 0;
+       }
+
+       return 1;
+}
+
+#if defined(HAVE_FMEMOPEN) || defined(TEST_PROGRAM)
+
+/*
+ * This function tries to minimize possible races when we read
+ * /proc/#/{mountinfo,mount} files.
+ *
+ * The idea is to minimize number of read()s and check by poll() that during
+ * the read the mount table has not been modified. If yes, than re-read it
+ * (with some limitations to avoid never ending loop).
+ *
+ * Returns: <0 error, 0 success, 1 too many attempts
+ */
+static int read_procfs_file(int fd, char **buf, size_t *bufsiz)
+{
+       size_t bufmax = 0;
+       int rc = 0, tries = 0, ninters = 0;
+       char *bufptr = NULL;
+
+       assert(buf);
+       assert(bufsiz);
+
+       *bufsiz = 0;
+       *buf = NULL;
+
+       do {
+               ssize_t ret;
+
+               if (!bufptr || bufmax == *bufsiz) {
+                       char *tmp;
+
+                       bufmax = bufmax ? bufmax * 2 : (16 * 1024);
+                       tmp = realloc(*buf, bufmax);
+                       if (!tmp)
+                               break;
+                       *buf = tmp;
+                       bufptr = tmp + *bufsiz;
+               }
+
+               errno = 0;
+               ret = read(fd, bufptr, bufmax - *bufsiz);
+
+               if (ret < 0) {
+                       /* error */
+                       if ((errno == EAGAIN || errno == EINTR) && (ninters++ < 5)) {
+                               xusleep(200000);
+                               continue;
+                       }
+                       break;
+
+               } else if (ret > 0) {
+                       /* success -- verify no event during read */
+                       struct pollfd fds[] = {
+                               { .fd = fd, .events = POLLPRI }
+                       };
+
+                       rc = poll(fds, 1, 0);
+                       if (rc < 0)
+                               break;          /* poll() error */
+                       if (rc > 0) {
+                               /* event -- read all again */
+                               if (lseek(fd, 0, SEEK_SET) != 0)
+                                       break;
+                               *bufsiz = 0;
+                               bufptr = *buf;
+                               tries++;
+
+                               if (tries > 10)
+                                       /* busy system? -- wait */
+                                       xusleep(10000);
+                               continue;
+                       }
+
+                       /* successful read() without active poll() */
+                       (*bufsiz) += (size_t) ret;
+                       bufptr += ret;
+                       tries = ninters = 0;
+               } else {
+                       /* end-of-file */
+                       goto success;
+               }
+       } while (tries <= 100);
+
+       rc = errno ? -errno : 1;
+       free(*buf);
+       return rc;
+
+success:
+       return 0;
+}
+
+/*
+ * Create FILE stream for data from read_procfs_file()
+ */
+FILE *mnt_get_procfs_memstream(int fd, char **membuf)
+{
+       size_t sz = 0;
+       off_t cur;
+
+       *membuf = NULL;
+
+       /* in case of error, rewind to the original position */
+       cur = lseek(fd, 0, SEEK_CUR);
+
+       if (read_procfs_file(fd, membuf, &sz) == 0 && sz > 0) {
+               FILE *memf = fmemopen(*membuf, sz, "r");
+               if (memf)
+                       return memf;    /* success */
+
+               free(*membuf);
+               *membuf = NULL;
+       }
+
+       /* error */
+       if (cur != (off_t) -1)
+               lseek(fd, cur, SEEK_SET);
+       return NULL;
+}
+#else
+FILE *mnt_get_procfs_memstream(int fd __attribute((__unused__)),
+                              char **membuf __attribute((__unused__)))
+{
+       return NULL;
+}
+#endif /* HAVE_FMEMOPEN */
+
+
 #ifdef TEST_PROGRAM
-int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
+static int test_proc_read(struct libmnt_test *ts, int argc, char *argv[])
+{
+       char *buf = NULL;
+       char *filename = argv[1];
+       size_t bufsiz = 0;
+       int rc = 0, fd = open(filename, O_RDONLY);
+
+       if (fd <= 0) {
+               warn("%s: cannot open", filename);
+               return -errno;
+       }
+
+       rc = read_procfs_file(fd, &buf, &bufsiz);
+       close(fd);
+
+       switch (rc) {
+       case 0:
+               fwrite(buf, 1, bufsiz, stdout);
+               free(buf);
+               break;
+       case 1:
+               warnx("too many attempts");
+               break;
+       default:
+               warn("%s: cannot read", filename);
+               break;
+       }
+
+       return rc;
+}
+
+static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
 {
        char *type = argv[1];
        char *pattern = argv[2];
@@ -1126,7 +1317,7 @@ int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
        return 0;
 }
 
-int test_match_options(struct libmnt_test *ts, int argc, char *argv[])
+static int test_match_options(struct libmnt_test *ts, int argc, char *argv[])
 {
        char *optstr = argv[1];
        char *pattern = argv[2];
@@ -1135,7 +1326,7 @@ int test_match_options(struct libmnt_test *ts, int argc, char *argv[])
        return 0;
 }
 
-int test_startswith(struct libmnt_test *ts, int argc, char *argv[])
+static int test_startswith(struct libmnt_test *ts, int argc, char *argv[])
 {
        char *optstr = argv[1];
        char *pattern = argv[2];
@@ -1144,7 +1335,7 @@ int test_startswith(struct libmnt_test *ts, int argc, char *argv[])
        return 0;
 }
 
-int test_endswith(struct libmnt_test *ts, int argc, char *argv[])
+static int test_endswith(struct libmnt_test *ts, int argc, char *argv[])
 {
        char *optstr = argv[1];
        char *pattern = argv[2];
@@ -1153,7 +1344,7 @@ int test_endswith(struct libmnt_test *ts, int argc, char *argv[])
        return 0;
 }
 
-int test_appendstr(struct libmnt_test *ts, int argc, char *argv[])
+static int test_appendstr(struct libmnt_test *ts, int argc, char *argv[])
 {
        char *str = strdup(argv[1]);
        const char *ap = argv[2];
@@ -1165,7 +1356,7 @@ int test_appendstr(struct libmnt_test *ts, int argc, char *argv[])
        return 0;
 }
 
-int test_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
+static int test_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
 {
        char *path = canonicalize_path(argv[1]),
             *mnt = path ? mnt_get_mountpoint(path) :  NULL;
@@ -1176,18 +1367,7 @@ int test_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
        return 0;
 }
 
-int test_fsroot(struct libmnt_test *ts, int argc, char *argv[])
-{
-       char *path = canonicalize_path(argv[1]),
-            *mnt = path ? mnt_get_fs_root(path, NULL) : NULL;
-
-       printf("%s: %s\n", argv[1], mnt ? : "unknown");
-       free(mnt);
-       free(path);
-       return 0;
-}
-
-int test_filesystems(struct libmnt_test *ts, int argc, char *argv[])
+static int test_filesystems(struct libmnt_test *ts, int argc, char *argv[])
 {
        char **filesystems = NULL;
        int rc;
@@ -1202,7 +1382,7 @@ int test_filesystems(struct libmnt_test *ts, int argc, char *argv[])
        return rc;
 }
 
-int test_chdir(struct libmnt_test *ts, int argc, char *argv[])
+static int test_chdir(struct libmnt_test *ts, int argc, char *argv[])
 {
        int rc;
        char *path = canonicalize_path(argv[1]),
@@ -1221,7 +1401,7 @@ int test_chdir(struct libmnt_test *ts, int argc, char *argv[])
        return rc;
 }
 
-int test_kernel_cmdline(struct libmnt_test *ts, int argc, char *argv[])
+static int test_kernel_cmdline(struct libmnt_test *ts, int argc, char *argv[])
 {
        char *name = argv[1];
        char *res;
@@ -1239,7 +1419,34 @@ int test_kernel_cmdline(struct libmnt_test *ts, int argc, char *argv[])
        return 0;
 }
 
-int test_mkdir(struct libmnt_test *ts, int argc, char *argv[])
+
+static int test_guess_root(struct libmnt_test *ts, int argc, char *argv[])
+{
+       int rc;
+       char *real;
+       dev_t devno = 0;
+
+       if (argc) {
+               unsigned int x, y;
+
+               if (sscanf(argv[1], "%u:%u", &x, &y) != 2)
+                       return -EINVAL;
+               devno = makedev(x, y);
+       }
+
+       rc = mnt_guess_system_root(devno, NULL, &real);
+       if (rc < 0)
+               return rc;
+       if (rc == 1)
+               fputs("not found\n", stdout);
+       else {
+               printf("%s\n", real);
+               free(real);
+       }
+       return 0;
+}
+
+static int test_mkdir(struct libmnt_test *ts, int argc, char *argv[])
 {
        int rc;
 
@@ -1251,7 +1458,7 @@ int test_mkdir(struct libmnt_test *ts, int argc, char *argv[])
        return rc;
 }
 
-int test_statfs_type(struct libmnt_test *ts, int argc, char *argv[])
+static int test_statfs_type(struct libmnt_test *ts, int argc, char *argv[])
 {
        struct statfs vfs;
        int rc;
@@ -1277,11 +1484,12 @@ int main(int argc, char *argv[])
        { "--ends-with",     test_endswith,        "<string> <prefix>" },
        { "--append-string", test_appendstr,       "<string> <appendix>" },
        { "--mountpoint",    test_mountpoint,      "<path>" },
-       { "--fs-root",       test_fsroot,          "<path>" },
        { "--cd-parent",     test_chdir,           "<path>" },
        { "--kernel-cmdline",test_kernel_cmdline,  "<option> | <option>=" },
+       { "--guess-root",    test_guess_root,      "[<maj:min>]" },
        { "--mkdir",         test_mkdir,           "<path>" },
        { "--statfs-type",   test_statfs_type,     "<path>" },
+       { "--read-procfs",   test_proc_read,       "<path>" },
 
        { NULL }
        };