]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - libmount/src/utils.c
libmount; fix and improve read+poll mountinfo
[thirdparty/util-linux.git] / libmount / src / utils.c
index bfd2fff2a9a12ead9eadbe7291d8fd6fe4ff018f..356174244c272f3542a4a419c63f80a878796529 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,8 @@
 #include <fcntl.h>
 #include <pwd.h>
 #include <grp.h>
+#include <poll.h>
+#include <blkid.h>
 
 #include "strutils.h"
 #include "pathnames.h"
 #include "canonicalize.h"
 #include "env.h"
 #include "match.h"
+#include "fileutils.h"
+#include "statfs_magic.h"
+#include "sysfs.h"
 
-int endswith(const char *s, const char *sx)
+int append_string(char **a, const char *b)
 {
-       ssize_t off;
+       size_t al, bl;
+       char *tmp;
 
-       assert(s);
-       assert(sx);
+       assert(a);
 
-       off = strlen(s);
-       if (!off)
-               return 0;
-       off -= strlen(sx);
-       if (off < 0)
+       if (!b || !*b)
                return 0;
+       if (!*a) {
+               *a = strdup(b);
+               return !*a ? -ENOMEM : 0;
+       }
+
+       al = strlen(*a);
+       bl = strlen(b);
 
-        return !strcmp(s + off, sx);
+       tmp = realloc(*a, al + bl + 1);
+       if (!tmp)
+               return -ENOMEM;
+       *a = tmp;
+       memcpy((*a) + al, b, bl + 1);
+       return 0;
 }
 
-int startswith(const char *s, const char *sx)
+/*
+ * Return 1 if the file is not accessible or empty
+ */
+int is_file_empty(const char *name)
 {
-       size_t off;
+       struct stat st;
+       assert(name);
 
-       assert(s);
-       assert(sx);
+       return (stat(name, &st) != 0 || st.st_size == 0);
+}
 
-       off = strlen(sx);
-       if (!off)
-               return 0;
+int mnt_valid_tagname(const char *tagname)
+{
+       if (tagname && *tagname && (
+           strcmp("UUID", tagname) == 0 ||
+           strcmp("LABEL", tagname) == 0 ||
+           strcmp("PARTUUID", tagname) == 0 ||
+           strcmp("PARTLABEL", tagname) == 0))
+               return 1;
 
-        return !strncmp(s, sx, off);
+       return 0;
+}
+
+/**
+ * mnt_tag_is_valid:
+ * @tag: NAME=value string
+ *
+ * Returns: 1 if the @tag is parsable and tag NAME= is supported by libmount, or 0.
+ */
+int mnt_tag_is_valid(const char *tag)
+{
+       char *t = NULL;
+       int rc = tag && blkid_parse_tag_string(tag, &t, NULL) == 0
+                    && mnt_valid_tagname(t);
+
+       free(t);
+       return rc;
 }
 
 int mnt_parse_offset(const char *str, size_t len, uintmax_t *res)
@@ -75,27 +118,24 @@ 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 */
-static 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
 }
 
 /*
- * Note that the @target has to be absolute path (so at least "/").  The
- * @filename returns allocated buffer with last path component, for example:
+ * 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:
  *
  * mnt_chdir_to_parent("/mnt/test", &buf) ==> chdir("/mnt"), buf="test"
  */
@@ -108,7 +148,7 @@ int mnt_chdir_to_parent(const char *target, char **filename)
        if (!target || *target != '/')
                return -EINVAL;
 
-       DBG(UTILS, mnt_debug("moving to %s parent", target));
+       DBG(UTILS, ul_debug("moving to %s parent", target));
 
        buf = strdup(target);
        if (!buf)
@@ -123,22 +163,22 @@ int mnt_chdir_to_parent(const char *target, char **filename)
        parent = buf && *buf ? buf : "/";
 
        if (chdir(parent) == -1) {
-               DBG(UTILS, mnt_debug("failed to chdir to %s: %m", parent));
+               DBG(UTILS, ul_debug("failed to chdir to %s: %m", parent));
                rc = -errno;
                goto err;
        }
        if (!getcwd(cwd, sizeof(cwd))) {
-               DBG(UTILS, mnt_debug("failed to obtain current directory: %m"));
+               DBG(UTILS, ul_debug("failed to obtain current directory: %m"));
                rc = -errno;
                goto err;
        }
        if (strcmp(cwd, parent) != 0) {
-               DBG(UTILS, mnt_debug(
+               DBG(UTILS, ul_debug(
                    "unexpected chdir (expected=%s, cwd=%s)", parent, cwd));
                goto err;
        }
 
-       DBG(CXT, mnt_debug(
+       DBG(CXT, ul_debug(
                "current directory moved to %s [last_component='%s']",
                parent, last));
 
@@ -148,8 +188,9 @@ int mnt_chdir_to_parent(const char *target, char **filename)
                if (!last || !*last)
                        memcpy(*filename, ".", 2);
                else
-                       memcpy(*filename, last, strlen(last) + 1);
-       }
+                       memmove(*filename, last, strlen(last) + 1);
+       } else
+               free(buf);
        return 0;
 err:
        free(buf);
@@ -157,7 +198,7 @@ err:
 }
 
 /*
- * Check if @path is on read-only filesystem independently on file permissions.
+ * Check if @path is on a read-only filesystem independently of file permissions.
  */
 int mnt_is_readonly(const char *path)
 {
@@ -168,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:
         *
@@ -176,11 +217,13 @@ int mnt_is_readonly(const char *path)
         *   accessible for the current rUID. (Note that euidaccess(2) does not
         *   check for EROFS at all).
         *
-        * - for read-write filesystem with read-only VFS node (aka -o remount,ro,bind)
+        * - for a read-write filesystem with a read-only VFS node (aka -o remount,ro,bind)
         */
        {
                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 */
 
@@ -197,7 +240,7 @@ int mnt_is_readonly(const char *path)
  *
  * Encode @str to be compatible with fstab/mtab
  *
- * Returns: new allocated string or NULL in case of error.
+ * Returns: newly allocated string or NULL in case of error.
  */
 char *mnt_mangle(const char *str)
 {
@@ -210,7 +253,7 @@ char *mnt_mangle(const char *str)
  *
  * Decode @str from fstab/mtab
  *
- * Returns: new allocated string or NULL in case of error.
+ * Returns: newly allocated string or NULL in case of error.
  */
 char *mnt_unmangle(const char *str)
 {
@@ -231,7 +274,9 @@ int mnt_fstype_is_pseudofs(const char *type)
                "autofs",
                "bdev",
                "binfmt_misc",
+               "bpf",
                "cgroup",
+               "cgroup2",
                "configfs",
                "cpuset",
                "debugfs",
@@ -239,12 +284,22 @@ int mnt_fstype_is_pseudofs(const char *type)
                "devpts",
                "devtmpfs",
                "dlmfs",
-               "fuse.gvfs-fuse-daemon",
+               "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",
                "hugetlbfs",
                "mqueue",
                "nfsd",
                "none",
+               "nsfs",
+               "overlay",
                "pipefs",
                "proc",
                "pstore",
@@ -252,12 +307,15 @@ int mnt_fstype_is_pseudofs(const char *type)
                "rootfs",
                "rpc_pipefs",
                "securityfs",
+               "selinuxfs",
                "sockfs",
                "spufs",
                "sysfs",
                "tmpfs"
        };
 
+       assert(type);
+
        return !(bsearch(&type, pseudofs, ARRAY_SIZE(pseudofs),
                                sizeof(char*), fstype_cmp) == NULL);
 }
@@ -275,17 +333,111 @@ 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;
 }
 
+const char *mnt_statfs_get_fstype(struct statfs *vfs)
+{
+       assert(vfs);
+
+       switch (vfs->f_type) {
+       case STATFS_ADFS_MAGIC:         return "adfs";
+       case STATFS_AFFS_MAGIC:         return "affs";
+       case STATFS_AFS_MAGIC:          return "afs";
+       case STATFS_AUTOFS_MAGIC:       return "autofs";
+       case STATFS_BDEVFS_MAGIC:       return "bdev";
+       case STATFS_BEFS_MAGIC:         return "befs";
+       case STATFS_BFS_MAGIC:          return "befs";
+       case STATFS_BINFMTFS_MAGIC:     return "binfmt_misc";
+       case STATFS_BTRFS_MAGIC:        return "btrfs";
+       case STATFS_CEPH_MAGIC:         return "ceph";
+       case STATFS_CGROUP_MAGIC:       return "cgroup";
+       case STATFS_CIFS_MAGIC:         return "cifs";
+       case STATFS_CODA_MAGIC:         return "coda";
+       case STATFS_CONFIGFS_MAGIC:     return "configfs";
+       case STATFS_CRAMFS_MAGIC:       return "cramfs";
+       case STATFS_DEBUGFS_MAGIC:      return "debugfs";
+       case STATFS_DEVPTS_MAGIC:       return "devpts";
+       case STATFS_ECRYPTFS_MAGIC:     return "ecryptfs";
+       case STATFS_EFIVARFS_MAGIC:     return "efivarfs";
+       case STATFS_EFS_MAGIC:          return "efs";
+       case STATFS_EXOFS_MAGIC:        return "exofs";
+       case STATFS_EXT4_MAGIC:         return "ext4";     /* all extN use the same magic */
+       case STATFS_F2FS_MAGIC:         return "f2fs";
+       case STATFS_FUSE_MAGIC:         return "fuse";
+       case STATFS_FUTEXFS_MAGIC:      return "futexfs";
+       case STATFS_GFS2_MAGIC:         return "gfs2";
+       case STATFS_HFSPLUS_MAGIC:      return "hfsplus";
+       case STATFS_HOSTFS_MAGIC:       return "hostfs";
+       case STATFS_HPFS_MAGIC:         return "hpfs";
+       case STATFS_HPPFS_MAGIC:        return "hppfs";
+       case STATFS_HUGETLBFS_MAGIC:    return "hugetlbfs";
+       case STATFS_ISOFS_MAGIC:        return "iso9660";
+       case STATFS_JFFS2_MAGIC:        return "jffs2";
+       case STATFS_JFS_MAGIC:          return "jfs";
+       case STATFS_LOGFS_MAGIC:        return "logfs";
+       case STATFS_MINIX2_MAGIC:
+       case STATFS_MINIX2_MAGIC2:
+       case STATFS_MINIX3_MAGIC:
+       case STATFS_MINIX_MAGIC:
+       case STATFS_MINIX_MAGIC2:       return "minix";
+       case STATFS_MQUEUE_MAGIC:       return "mqueue";
+       case STATFS_MSDOS_MAGIC:        return "vfat";
+       case STATFS_NCP_MAGIC:          return "ncp";
+       case STATFS_NFS_MAGIC:          return "nfs";
+       case STATFS_NILFS_MAGIC:        return "nilfs2";
+       case STATFS_NTFS_MAGIC:         return "ntfs";
+       case STATFS_OCFS2_MAGIC:        return "ocfs2";
+       case STATFS_OMFS_MAGIC:         return "omfs";
+       case STATFS_OPENPROMFS_MAGIC:   return "openpromfs";
+       case STATFS_PIPEFS_MAGIC:       return "pipefs";
+       case STATFS_PROC_MAGIC:         return "proc";
+       case STATFS_PSTOREFS_MAGIC:     return "pstore";
+       case STATFS_QNX4_MAGIC:         return "qnx4";
+       case STATFS_QNX6_MAGIC:         return "qnx6";
+       case STATFS_RAMFS_MAGIC:        return "ramfs";
+       case STATFS_REISERFS_MAGIC:     return "reiser4";
+       case STATFS_ROMFS_MAGIC:        return "romfs";
+       case STATFS_SECURITYFS_MAGIC:   return "securityfs";
+       case STATFS_SELINUXFS_MAGIC:    return "selinuxfs";
+       case STATFS_SMACKFS_MAGIC:      return "smackfs";
+       case STATFS_SMB_MAGIC:          return "smb";
+       case STATFS_SOCKFS_MAGIC:       return "sockfs";
+       case STATFS_SQUASHFS_MAGIC:     return "squashfs";
+       case STATFS_SYSFS_MAGIC:        return "sysfs";
+       case STATFS_TMPFS_MAGIC:        return "tmpfs";
+       case STATFS_UBIFS_MAGIC:        return "ubifs";
+       case STATFS_UDF_MAGIC:          return "udf";
+       case STATFS_UFS2_MAGIC:
+       case STATFS_UFS_MAGIC:          return "ufs";
+       case STATFS_V9FS_MAGIC:         return "9p";
+       case STATFS_VXFS_MAGIC:         return "vxfs";
+       case STATFS_XENFS_MAGIC:        return "xenfs";
+       case STATFS_XFS_MAGIC:          return "xfs";
+       default:
+               break;
+       }
+
+       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:
  * @type: filesystem type
  * @pattern: filesystem name or comma delimited list of names
  *
- * The @pattern list of filesystem can be prefixed with a global
+ * The @pattern list of filesystems can be prefixed with a global
  * "no" prefix to invert matching of the whole list. The "no" could
  * also be used for individual items in the @pattern list. So,
  * "nofoo,bar" has the same meaning as "nofoo,nobar".
@@ -304,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 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 disable by "+" prefix, for example
- * "+noauto" matches if @optstr literally contains "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;
@@ -435,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);
@@ -449,13 +507,13 @@ 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");
+       f = fopen(filename, "r" UL_CLOEXECSTR);
        if (!f)
                return 1;
 
-       DBG(UTILS, mnt_debug("reading filesystems list from: %s", filename));
+       DBG(UTILS, ul_debug("reading filesystems list from: %s", filename));
 
        while (fgets(line, sizeof(line), f)) {
                char name[sizeof(line)];
@@ -480,7 +538,7 @@ static int get_filesystems(const char *filename, char ***filesystems, const char
 }
 
 /*
- * Always check @filesystems pointer!
+ * Always check the @filesystems pointer!
  *
  * man mount:
  *
@@ -488,7 +546,7 @@ static int get_filesystems(const char *filename, char ***filesystems, const char
  * exist, /proc/filesystems. All of the filesystem  types  listed  there  will
  * be tried,  except  for  those  that  are  labeled  "nodev"  (e.g.,  devpts,
  * proc  and  nfs).  If /etc/filesystems ends in a line with a single * only,
- * mount will read /proc/filesystems after‐ wards.
+ * mount will read /proc/filesystems afterwards.
  */
 int mnt_get_filesystems(char ***filesystems, const char *pattern)
 {
@@ -505,36 +563,25 @@ int mnt_get_filesystems(char ***filesystems, const char *pattern)
 
        rc = get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern);
        if (rc == 1 && *filesystems)
-               rc = 0;                 /* not found /proc/filesystems */
+               rc = 0;                 /* /proc/filesystems not found */
 
        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 allocated string with username or NULL.
+ * Returns an allocated string with username or NULL.
  */
 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);
@@ -546,21 +593,20 @@ 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 {
-               DBG(UTILS, mnt_debug(
+               DBG(UTILS, ul_debug(
                        "cannot convert '%s' username to UID", username));
                rc = errno ? -errno : -EINVAL;
        }
@@ -574,21 +620,20 @@ 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 {
-               DBG(UTILS, mnt_debug(
+               DBG(UTILS, ul_debug(
                        "cannot convert '%s' groupname to GID", groupname));
                rc = errno ? -errno : -EINVAL;
        }
@@ -626,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;
 }
 
 /**
@@ -647,8 +717,8 @@ static int try_write(const char *filename)
  * @mtab: returns path to mtab
  * @writable: returns 1 if the file is writable
  *
- * If the file does not exist and @writable argument is not NULL then it will
- * try to create the file
+ * If the file does not exist and @writable argument is not NULL, then it will
+ * try to create the file.
  *
  * Returns: 1 if /etc/mtab is a regular file, and 0 in case of error (check
  *          errno for more details).
@@ -664,15 +734,16 @@ int mnt_has_regular_mtab(const char **mtab, int *writable)
        if (mtab && !*mtab)
                *mtab = filename;
 
-       DBG(UTILS, mnt_debug("mtab: %s", filename));
+       DBG(UTILS, ul_debug("mtab: %s", filename));
 
        rc = lstat(filename, &st);
 
        if (rc == 0) {
-               /* file exist */
+               /* 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;
@@ -680,20 +751,22 @@ 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:
-       DBG(UTILS, mnt_debug("%s: irregular/non-writable", filename));
+       DBG(UTILS, ul_debug("%s: irregular/non-writable", filename));
        return 0;
 }
 
 /*
  * Don't export this to libmount API -- utab is private library stuff.
  *
- * If the file does not exist and @writable argument is not NULL then it will
+ * If the file does not exist and @writable argument is not NULL, then it will
  * try to create the directory (e.g. /run/mount) and the file.
  *
  * Returns: 1 if utab is a regular file, and 0 in case of
@@ -710,18 +783,18 @@ int mnt_has_regular_utab(const char **utab, int *writable)
        if (utab && !*utab)
                *utab = filename;
 
-       DBG(UTILS, mnt_debug("utab: %s", filename));
+       DBG(UTILS, ul_debug("utab: %s", filename));
 
        rc = lstat(filename, &st);
 
        if (rc == 0) {
-               /* file exist */
+               /* 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 regular file */
+               goto done;      /* it's not regular file */
        }
 
        if (writable) {
@@ -735,16 +808,18 @@ 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;
        }
 done:
-       DBG(UTILS, mnt_debug("%s: irregular/non-writable file", filename));
+       DBG(UTILS, ul_debug("%s: irregular/non-writable file", filename));
        return 0;
 }
 
@@ -773,7 +848,7 @@ const char *mnt_get_fstab_path(void)
 /**
  * mnt_get_mtab_path:
  *
- * This function returns *default* location of the mtab file. The result does
+ * This function returns the *default* location of the mtab file. The result does
  * not have to be writable. See also mnt_has_regular_mtab().
  *
  * Returns: path to /etc/mtab or $LIBMOUNT_MTAB.
@@ -804,7 +879,7 @@ const char *mnt_get_utab_path(void)
 }
 
 
-/* returns file descriptor or -errno, @name returns uniques filename
+/* returns file descriptor or -errno, @name returns a unique filename
  */
 int mnt_open_uniq_filename(const char *filename, char **name)
 {
@@ -821,12 +896,14 @@ int mnt_open_uniq_filename(const char *filename, char **name)
        if (rc <= 0)
                return -errno;
 
-       /* This is for very old glibc and for compatibility with Posix where is
+       /* This is for very old glibc and for compatibility with Posix, which says
         * nothing about mkstemp() mode. All sane glibc use secure mode (0600).
         */
        oldmode = umask(S_IRGRP|S_IWGRP|S_IXGRP|
                        S_IROTH|S_IWOTH|S_IXOTH);
-       fd = mkstemp(n);
+       fd = mkostemp(n, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
+       if (fd < 0)
+               fd = -errno;
        umask(oldmode);
 
        if (fd >= 0 && name)
@@ -834,7 +911,7 @@ int mnt_open_uniq_filename(const char *filename, char **name)
        else
                free(n);
 
-       return fd < 0 ? -errno : fd;
+       return fd;
 }
 
 /**
@@ -844,20 +921,28 @@ 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.
  *
- * Returns: allocated string with target of the mounted device or NULL on error
+ * 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)
 {
-       char *mnt = strdup(path);
+       char *mnt;
        struct stat st;
        dev_t dir, base;
 
+       if (!path)
+               return NULL;
+
+       mnt = strdup(path);
        if (!mnt)
                return NULL;
        if (*mnt == '/' && *(mnt + 1) == '\0')
                goto done;
 
-       if (stat(mnt, &st))
+       if (mnt_stat_mountpoint(mnt, &st))
                goto err;
        base = st.st_dev;
 
@@ -866,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) {
@@ -879,58 +964,40 @@ char *mnt_get_mountpoint(const char *path)
 
        memcpy(mnt, "/", 2);
 done:
-       DBG(UTILS, mnt_debug("%s mountpoint is %s", path, mnt));
+       DBG(UTILS, ul_debug("%s mountpoint is %s", path, mnt));
        return mnt;
 err:
        free(mnt);
        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, mnt_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 parameter argument if the @name is
+ * Returns newly allocated string with 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
@@ -938,7 +1005,7 @@ char *mnt_get_kernel_cmdline_option(const char *name)
        if (!path)
                path = _PATH_PROC_CMDLINE;
 #endif
-       f = fopen(path, "r");
+       f = fopen(path, "r" UL_CLOEXECSTR);
        if (!f)
                return NULL;
 
@@ -948,38 +1015,291 @@ 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)))
                        continue;               /* no space before the option */
                if (!val && *(p + len) != '\0' && !isblank(*(p + len)))
-                       continue;               /* no space behind the option */
+                       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;
 }
 
+/**
+ * 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)
+{
+       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];
@@ -988,7 +1308,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];
@@ -997,7 +1317,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];
@@ -1006,7 +1326,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];
@@ -1015,21 +1335,22 @@ int test_endswith(struct libmnt_test *ts, int argc, char *argv[])
        return 0;
 }
 
-int test_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
+static int test_appendstr(struct libmnt_test *ts, int argc, char *argv[])
 {
-       char *path = canonicalize_path(argv[1]),
-            *mnt = path ? mnt_get_mountpoint(path) :  NULL;
+       char *str = strdup(argv[1]);
+       const char *ap = argv[2];
 
-       printf("%s: %s\n", argv[1], mnt ? : "unknown");
-       free(mnt);
-       free(path);
+       append_string(&str, ap);
+       printf("new string: '%s'\n", str);
+
+       free(str);
        return 0;
 }
 
-int test_fsroot(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_fs_root(path, NULL) : NULL;
+            *mnt = path ? mnt_get_mountpoint(path) :  NULL;
 
        printf("%s: %s\n", argv[1], mnt ? : "unknown");
        free(mnt);
@@ -1037,7 +1358,7 @@ int test_fsroot(struct libmnt_test *ts, int argc, char *argv[])
        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;
@@ -1052,7 +1373,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]),
@@ -1071,7 +1392,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;
@@ -1090,6 +1411,60 @@ int test_kernel_cmdline(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;
+
+       rc = mkdir_p(argv[1], S_IRWXU |
+                        S_IRGRP | S_IXGRP |
+                        S_IROTH | S_IXOTH);
+       if (rc)
+               printf("mkdir %s failed\n", argv[1]);
+       return rc;
+}
+
+static int test_statfs_type(struct libmnt_test *ts, int argc, char *argv[])
+{
+       struct statfs vfs;
+       int rc;
+
+       rc = statfs(argv[1], &vfs);
+       if (rc)
+               printf("%s: statfs failed: %m\n", argv[1]);
+       else
+               printf("%-30s: statfs type: %-12s [0x%lx]\n", argv[1],
+                               mnt_statfs_get_fstype(&vfs),
+                               (long) vfs.f_type);
+       return rc;
+}
+
+
 int main(int argc, char *argv[])
 {
        struct libmnt_test tss[] = {
@@ -1098,10 +1473,15 @@ int main(int argc, char *argv[])
        { "--filesystems",   test_filesystems,     "[<pattern>] list /{etc,proc}/filesystems" },
        { "--starts-with",   test_startswith,      "<string> <prefix>" },
        { "--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 }
        };