]> 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 29f259ffefdd6e99c29ad358baa2b136bac4d2bd..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,7 @@
 #include <fcntl.h>
 #include <pwd.h>
 #include <grp.h>
+#include <poll.h>
 #include <blkid.h>
 
 #include "strutils.h"
@@ -112,8 +118,8 @@ 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);
 }
@@ -121,7 +127,7 @@ static int fstype_cmp(const void *v1, const void *v2)
 int mnt_stat_mountpoint(const char *target, struct stat *st)
 {
 #ifdef AT_NO_AUTOMOUNT
-       return fstatat(-1, target, st, AT_NO_AUTOMOUNT);
+       return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT);
 #else
        return stat(target, st);
 #endif
@@ -216,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 */
 
@@ -266,6 +274,7 @@ int mnt_fstype_is_pseudofs(const char *type)
                "autofs",
                "bdev",
                "binfmt_misc",
+               "bpf",
                "cgroup",
                "cgroup2",
                "configfs",
@@ -276,12 +285,20 @@ int mnt_fstype_is_pseudofs(const char *type)
                "devtmpfs",
                "dlmfs",
                "efivarfs",
-               "fuse.gvfs-fuse-daemon",
+               "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",
@@ -290,6 +307,7 @@ int mnt_fstype_is_pseudofs(const char *type)
                "rootfs",
                "rpc_pipefs",
                "securityfs",
+               "selinuxfs",
                "sockfs",
                "spufs",
                "sysfs",
@@ -315,6 +333,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;
@@ -405,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:
@@ -430,98 +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 && !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;
@@ -737,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;
 }
 
 /**
@@ -783,7 +742,7 @@ 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;
                }
@@ -792,7 +751,7 @@ int mnt_has_regular_mtab(const char **mtab, int *writable)
 
        /* try to create the file */
        if (writable) {
-               *writable = !try_write(filename);
+               *writable = !try_write(filename, NULL);
                if (*writable) {
                        DBG(UTILS, ul_debug("%s: writable", filename));
                        return 1;
@@ -832,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 */
@@ -849,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;
        }
@@ -961,7 +922,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
@@ -1094,13 +1055,20 @@ 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)
 {
@@ -1179,8 +1147,158 @@ done:
        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
+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];
@@ -1362,6 +1480,7 @@ int main(int argc, char *argv[])
        { "--guess-root",    test_guess_root,      "[<maj:min>]" },
        { "--mkdir",         test_mkdir,           "<path>" },
        { "--statfs-type",   test_statfs_type,     "<path>" },
+       { "--read-procfs",   test_proc_read,       "<path>" },
 
        { NULL }
        };