+/* 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.
*/
/**
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
+#include <poll.h>
#include <blkid.h>
#include "strutils.h"
/* used as a callback by bsearch in mnt_fstype_is_pseudofs() */
static int fstype_cmp(const void *v1, const void *v2)
{
- const char *s1 = *(const char **)v1;
- const char *s2 = *(const char **)v2;
+ const char *s1 = *(char * const *)v1;
+ const char *s2 = *(char * const *)v2;
return strcmp(s1, s2);
}
int mnt_stat_mountpoint(const char *target, struct stat *st)
{
#ifdef AT_NO_AUTOMOUNT
- return fstatat(-1, target, st, AT_NO_AUTOMOUNT);
+ return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT);
#else
return stat(target, st);
#endif
{
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 */
"autofs",
"bdev",
"binfmt_misc",
+ "bpf",
"cgroup",
"cgroup2",
"configfs",
"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",
"rootfs",
"rpc_pipefs",
"securityfs",
+ "selinuxfs",
"sockfs",
"spufs",
"sysfs",
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;
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:
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;
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;
}
/**
/* 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;
}
/* 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;
/* 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 */
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;
}
* 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
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)
{
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];
{ "--guess-root", test_guess_root, "[<maj:min>]" },
{ "--mkdir", test_mkdir, "<path>" },
{ "--statfs-type", test_statfs_type, "<path>" },
+ { "--read-procfs", test_proc_read, "<path>" },
{ NULL }
};