]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - libmount/src/utils.c
libmount: improve mountinfo reliability
[thirdparty/util-linux.git] / libmount / src / utils.c
index d82920905e9c6f517ce4f872414d227bda4b4a92..5b30f2ac0756e58a11093f71362da3aaa4c50fe1 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"
@@ -23,7 +29,9 @@
 #include "canonicalize.h"
 #include "env.h"
 #include "match.h"
+#include "fileutils.h"
 #include "statfs_magic.h"
+#include "sysfs.h"
 
 int append_string(char **a, const char *b)
 {
@@ -110,22 +118,19 @@ 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);
 }
 
-/* returns basename and keeps dirname in the @path, if @path is "/" (root)
- * then returns empty string */
-char *stripoff_last_component(char *path)
+int mnt_stat_mountpoint(const char *target, struct stat *st)
 {
-       char *p = path ? strrchr(path, '/') : NULL;
-
-       if (!p)
-               return NULL;
-       *p = '\0';
-       return p + 1;
+#ifdef AT_NO_AUTOMOUNT
+       return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT);
+#else
+       return stat(target, st);
+#endif
 }
 
 /*
@@ -204,7 +209,7 @@ int mnt_is_readonly(const char *path)
        if (errno != EACCES)
                return 0;
 
-#ifdef HAVE_FUTIMENS
+#ifdef HAVE_UTIMENSAT
        /*
         * access(2) returns EACCES on read-only FS:
         *
@@ -217,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 */
 
@@ -267,7 +274,9 @@ int mnt_fstype_is_pseudofs(const char *type)
                "autofs",
                "bdev",
                "binfmt_misc",
+               "bpf",
                "cgroup",
+               "cgroup2",
                "configfs",
                "cpuset",
                "debugfs",
@@ -276,12 +285,21 @@ 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.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.rofiles-fuse",
+               "fuse.xwmfs",
                "fusectl",
-               "fuse.gvfs-fuse-daemon",
                "hugetlbfs",
                "mqueue",
                "nfsd",
                "none",
+               "nsfs",
+               "overlay",
                "pipefs",
                "proc",
                "pstore",
@@ -289,6 +307,7 @@ int mnt_fstype_is_pseudofs(const char *type)
                "rootfs",
                "rpc_pipefs",
                "securityfs",
+               "selinuxfs",
                "sockfs",
                "spufs",
                "sysfs",
@@ -309,13 +328,13 @@ int mnt_fstype_is_pseudofs(const char *type)
  */
 int mnt_fstype_is_netfs(const char *type)
 {
-       assert(type);
-
        if (strcmp(type, "cifs")   == 0 ||
            strcmp(type, "smbfs")  == 0 ||
            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;
@@ -406,6 +425,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:
@@ -431,100 +456,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;
@@ -562,10 +493,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);
@@ -576,7 +507,7 @@ static int get_filesystems(const char *filename, char ***filesystems, const char
 {
        int rc = 0;
        FILE *f;
-       char line[128];
+       char line[129];
 
        f = fopen(filename, "r" UL_CLOEXECSTR);
        if (!f)
@@ -637,16 +568,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.
  */
@@ -654,14 +575,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);
@@ -673,17 +593,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 {
@@ -701,17 +620,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 {
@@ -753,20 +671,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;
 }
 
 /**
@@ -799,7 +742,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;
@@ -807,9 +751,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:
@@ -845,7 +791,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 */
@@ -862,11 +808,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;
        }
@@ -973,6 +921,10 @@ int mnt_open_uniq_filename(const char *filename, char **name)
  * This function finds the mountpoint that a given path resides in. @path
  * 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 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
  */
 char *mnt_get_mountpoint(const char *path)
@@ -981,7 +933,8 @@ char *mnt_get_mountpoint(const char *path)
        struct stat st;
        dev_t dir, base;
 
-       assert(path);
+       if (!path)
+               return NULL;
 
        mnt = strdup(path);
        if (!mnt)
@@ -989,7 +942,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;
 
@@ -998,7 +951,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) {
@@ -1018,51 +971,33 @@ 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
- * found.
+ * found.  If it is specified more than once, we grab the last copy.
  *
  * For example cmdline: "aaa bbb=BBB ccc"
  *
  *     @name is "aaa"  --returns--> "aaa" (pointer to @name)
  *     @name is "bbb=" --returns--> "BBB" (allocated)
  *     @name is "foo"  --returns--> NULL
+ *
+ * Note: It is not really feasible to parse the command line exactly the same
+ * as the kernel does since we don't know which options are valid.  We can use
+ * the -- marker though and not walk past that.
  */
 char *mnt_get_kernel_cmdline_option(const char *name)
 {
        FILE *f;
        size_t len;
        int val = 0;
-       char *p, *res = NULL;
+       char *p, *res = NULL, *mem = NULL;
        char buf[BUFSIZ];       /* see kernel include/asm-generic/setup.h: COMMAND_LINE_SIZE */
        const char *path = _PATH_PROC_CMDLINE;
 
-       if (!name)
+       if (!name || !name[0])
                return NULL;
 
 #ifdef TEST_PROGRAM
@@ -1080,14 +1015,20 @@ char *mnt_get_kernel_cmdline_option(const char *name)
        if (!p || !*p || *p == '\n')
                return NULL;
 
-       len = strlen(buf);
-       *(buf + len - 1) = '\0';        /* remove last '\n' */
+       p = strstr(p, " -- ");
+       if (p) {
+               /* no more kernel args after this */
+               *p = '\0';
+       } else {
+               len = strlen(buf);
+               buf[len - 1] = '\0';    /* remove last '\n' */
+       }
 
        len = strlen(name);
-       if (len && *(name + len - 1) == '=')
+       if (name[len - 1] == '=')
                val = 1;
 
-       for ( ; p && *p; p++) {
+       for (p = buf; p && *p; p++) {
                if (!(p = strstr(p, name)))
                        break;                  /* not found the option */
                if (p != buf && !isblank(*(p - 1)))
@@ -1096,59 +1037,266 @@ char *mnt_get_kernel_cmdline_option(const char *name)
                        continue;               /* no space after the option */
                if (val) {
                        char *v = p + len;
+                       int end;
 
                        while (*p && !isblank(*p))      /* jump to the end of the argument */
                                p++;
+                       end = (*p == '\0');
                        *p = '\0';
-                       res = strdup(v);
-                       break;
+                       free(mem);
+                       res = mem = strdup(v);
+                       if (end)
+                               break;
                } else
                        res = (char *) name;    /* option without '=' */
-               break;
+               /* don't break -- keep scanning for more options */
        }
 
        return res;
 }
 
-int mkdir_p(const char *path, mode_t mode)
+/**
+ * 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 *p, *dir;
-       int rc = 0;
+       char buf[PATH_MAX];
+       char *dev = NULL, *spec = NULL;
+       unsigned int x, y;
+       int allocated = 0;
 
-       if (!path || !*path)
-               return -EINVAL;
+       assert(path);
 
-       dir = p = strdup(path);
-       if (!dir)
-               return -ENOMEM;
+       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;
+       }
 
-       if (*p == '/')
-               p++;
+       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;
+       char *bufptr;
+
+       assert(buf);
+       assert(bufsiz);
+
+       *bufsiz = 0;
+
+       do {
+               ssize_t ret;
 
-       while (p && *p) {
-               char *e = strchr(p, '/');
-               if (e)
-                       *e = '\0';
-               if (*p) {
-                       rc = mkdir(dir, mode);
-                       if (rc && errno != EEXIST)
+               if (bufmax == *bufsiz) {
+                       char *tmp;
+
+                       bufmax = bufmax ? bufmax * 2 : (16 * 1024);
+
+                       tmp = realloc(*buf, bufmax);
+                       if (!tmp)
                                break;
-                       rc = 0;
+                       *buf = tmp;
+                       bufptr = tmp + *bufsiz;
                }
-               if (!e)
+
+               errno = 0;
+               ret = read(fd, bufptr, bufmax);
+
+               if (ret < 0) {
+                       /* error */
+                       if (errno == EAGAIN || errno == EINTR) {
+                               xusleep(250000);
+                               tries++;
+                               continue;
+                       }
                        break;
-               *e = '/';
-               p = e + 1;
-       }
 
-       DBG(UTILS, ul_debug("%s mkdir %s", path, rc ? "FAILED" : "SUCCESS"));
+               } 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++;
+                               continue;
+                       }
+
+                       /* successful read() without active poll() */
+                       *bufsiz += (size_t) ret;
+                       bufptr += ret;
+                       tries = 0;
+               } else {
+                       /* end-of-file */
+                       goto success;
+               }
+       } while (tries <= 5);
 
-       free(dir);
+       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)
+{
+       FILE *memf;
+       size_t sz = 0;
+       off_t cur;
+
+       /* 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
+           && (memf = fmemopen(*membuf, sz, "r")))
+               return memf;
+
+       /* error */
+       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];
@@ -1157,7 +1305,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];
@@ -1166,7 +1314,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];
@@ -1175,7 +1323,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];
@@ -1184,7 +1332,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];
@@ -1196,7 +1344,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;
@@ -1207,18 +1355,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;
@@ -1233,7 +1370,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]),
@@ -1252,7 +1389,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;
@@ -1270,7 +1407,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;
 
@@ -1282,7 +1446,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;
@@ -1308,11 +1472,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 }
        };