+/* 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"
#include "match.h"
#include "fileutils.h"
#include "statfs_magic.h"
+#include "sysfs.h"
int append_string(char **a, const char *b)
{
/* used as a callback by bsearch in mnt_fstype_is_pseudofs() */
static int fstype_cmp(const void *v1, const void *v2)
{
- const char *s1 = *(const char **)v1;
- const char *s2 = *(const char **)v2;
+ const char *s1 = *(char * const *)v1;
+ const char *s2 = *(char * const *)v2;
return strcmp(s1, s2);
}
+int mnt_stat_mountpoint(const char *target, struct stat *st)
+{
+#ifdef AT_NO_AUTOMOUNT
+ return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT);
+#else
+ return stat(target, st);
+#endif
+}
+
/*
* Note that the @target has to be an absolute path (so at least "/"). The
* @filename returns an allocated buffer with the last path component, for example:
{
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",
"cpuset",
"debugfs",
"devtmpfs",
"dlmfs",
"efivarfs",
+ "fuse", /* Fallback name of fuse used by many poorly written drivers. */
+ "fuse.archivemount", /* Not a true pseudofs (has source), but source is not reported. */
+ "fuse.avfsd", /* Not a true pseudofs (has source), but source is not reported. */
+ "fuse.dumpfs", /* In fact, it is a netfs, but source is not reported. */
+ "fuse.encfs", /* Not a true pseudofs (has source), but source is not reported. */
+ "fuse.gvfs-fuse-daemon", /* Old name, not used by gvfs any more. */
+ "fuse.gvfsd-fuse",
+ "fuse.lxcfs",
+ "fuse.rofiles-fuse",
+ "fuse.vmware-vmblock",
+ "fuse.xwmfs",
"fusectl",
- "fuse.gvfs-fuse-daemon",
"hugetlbfs",
"mqueue",
"nfsd",
"none",
+ "nsfs",
+ "overlay",
"pipefs",
"proc",
"pstore",
"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) {
- 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;
*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);
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.
*/
{
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);
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 {
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 {
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;
}
goto done;
/* 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:
/* 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
if (*mnt == '/' && *(mnt + 1) == '\0')
goto done;
- if (stat(mnt, &st))
+ if (mnt_stat_mountpoint(mnt, &st))
goto err;
base = st.st_dev;
if (!p)
break;
- if (stat(*mnt ? mnt : "/", &st))
+ if (mnt_stat_mountpoint(*mnt ? mnt : "/", &st))
goto err;
dir = st.st_dev;
if (dir != base) {
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
return res;
}
+/**
+ * mnt_guess_system_root:
+ * @devno: device number or zero
+ * @cache: paths cache or NULL
+ * @path: returns allocated path
+ *
+ * Converts @devno to the real device name if devno major number is greater
+ * than zero, otherwise use root= kernel cmdline option to get device name.
+ *
+ * The function uses /sys to convert devno to device name.
+ *
+ * Returns: 0 = success, 1 = not found, <0 = error
+ *
+ * Since: 2.34
+ */
+int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path)
+{
+ char buf[PATH_MAX];
+ char *dev = NULL, *spec = NULL;
+ unsigned int x, y;
+ int allocated = 0;
+
+ assert(path);
+
+ DBG(UTILS, ul_debug("guessing system root [devno %u:%u]", major(devno), minor(devno)));
+
+ /* The pseudo-fs, net-fs or btrfs devno is useless, otherwise it
+ * usually matches with the source device, let's try to use it.
+ */
+ if (major(devno) > 0) {
+ dev = sysfs_devno_to_devpath(devno, buf, sizeof(buf));
+ if (dev) {
+ DBG(UTILS, ul_debug(" devno converted to %s", dev));
+ goto done;
+ }
+ }
+
+ /* Let's try to use root= kernel command line option
+ */
+ spec = mnt_get_kernel_cmdline_option("root=");
+ if (!spec)
+ goto done;
+
+ /* maj:min notation */
+ if (sscanf(spec, "%u:%u", &x, &y) == 2) {
+ dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf));
+ if (dev) {
+ DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev));
+ goto done;
+ }
+
+ /* hexhex notation */
+ } else if (isxdigit_string(spec)) {
+ char *end = NULL;
+ uint32_t n;
+
+ errno = 0;
+ n = strtoul(spec, &end, 16);
+
+ if (errno || spec == end || (end && *end))
+ DBG(UTILS, ul_debug(" failed to parse root='%s'", spec));
+ else {
+ /* kernel new_decode_dev() */
+ x = (n & 0xfff00) >> 8;
+ y = (n & 0xff) | ((n >> 12) & 0xfff00);
+ dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf));
+ if (dev) {
+ DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev));
+ goto done;
+ }
+ }
+
+ /* devname or PARTUUID= etc. */
+ } else {
+ DBG(UTILS, ul_debug(" converting root='%s'", spec));
+
+ dev = mnt_resolve_spec(spec, cache);
+ if (dev && !cache)
+ allocated = 1;
+ }
+done:
+ free(spec);
+ if (dev) {
+ *path = allocated ? dev : strdup(dev);
+ if (!*path)
+ return -ENOMEM;
+ return 0;
+ }
+
+ return 1;
+}
+
+#if defined(HAVE_FMEMOPEN) || defined(TEST_PROGRAM)
+
+/*
+ * This function tries to minimize possible races when we read
+ * /proc/#/{mountinfo,mount} files.
+ *
+ * The idea is to minimize number of read()s and check by poll() that during
+ * the read the mount table has not been modified. If yes, than re-read it
+ * (with some limitations to avoid never ending loop).
+ *
+ * Returns: <0 error, 0 success, 1 too many attempts
+ */
+static int read_procfs_file(int fd, char **buf, size_t *bufsiz)
+{
+ size_t bufmax = 0;
+ int rc = 0, tries = 0, ninters = 0;
+ char *bufptr = NULL;
+
+ assert(buf);
+ assert(bufsiz);
+
+ *bufsiz = 0;
+ *buf = NULL;
+
+ do {
+ ssize_t ret;
+
+ if (!bufptr || bufmax == *bufsiz) {
+ char *tmp;
+
+ bufmax = bufmax ? bufmax * 2 : (16 * 1024);
+ tmp = realloc(*buf, bufmax);
+ if (!tmp)
+ break;
+ *buf = tmp;
+ bufptr = tmp + *bufsiz;
+ }
+
+ errno = 0;
+ ret = read(fd, bufptr, bufmax - *bufsiz);
+
+ if (ret < 0) {
+ /* error */
+ if ((errno == EAGAIN || errno == EINTR) && (ninters++ < 5)) {
+ xusleep(200000);
+ continue;
+ }
+ break;
+
+ } else if (ret > 0) {
+ /* success -- verify no event during read */
+ struct pollfd fds[] = {
+ { .fd = fd, .events = POLLPRI }
+ };
+
+ rc = poll(fds, 1, 0);
+ if (rc < 0)
+ break; /* poll() error */
+ if (rc > 0) {
+ /* event -- read all again */
+ if (lseek(fd, 0, SEEK_SET) != 0)
+ break;
+ *bufsiz = 0;
+ bufptr = *buf;
+ tries++;
+
+ if (tries > 10)
+ /* busy system? -- wait */
+ xusleep(10000);
+ continue;
+ }
+
+ /* successful read() without active poll() */
+ (*bufsiz) += (size_t) ret;
+ bufptr += ret;
+ tries = ninters = 0;
+ } else {
+ /* end-of-file */
+ goto success;
+ }
+ } while (tries <= 100);
+
+ rc = errno ? -errno : 1;
+ free(*buf);
+ return rc;
+
+success:
+ return 0;
+}
+
+/*
+ * Create FILE stream for data from read_procfs_file()
+ */
+FILE *mnt_get_procfs_memstream(int fd, char **membuf)
+{
+ size_t sz = 0;
+ off_t cur;
+
+ *membuf = NULL;
+
+ /* in case of error, rewind to the original position */
+ cur = lseek(fd, 0, SEEK_CUR);
+
+ if (read_procfs_file(fd, membuf, &sz) == 0 && sz > 0) {
+ FILE *memf = fmemopen(*membuf, sz, "r");
+ if (memf)
+ return memf; /* success */
+
+ free(*membuf);
+ *membuf = NULL;
+ }
+
+ /* error */
+ if (cur != (off_t) -1)
+ lseek(fd, cur, SEEK_SET);
+ return NULL;
+}
+#else
+FILE *mnt_get_procfs_memstream(int fd __attribute((__unused__)),
+ char **membuf __attribute((__unused__)))
+{
+ return NULL;
+}
+#endif /* HAVE_FMEMOPEN */
+
+
#ifdef TEST_PROGRAM
-int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
+static int test_proc_read(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *buf = NULL;
+ char *filename = argv[1];
+ size_t bufsiz = 0;
+ int rc = 0, fd = open(filename, O_RDONLY);
+
+ if (fd <= 0) {
+ warn("%s: cannot open", filename);
+ return -errno;
+ }
+
+ rc = read_procfs_file(fd, &buf, &bufsiz);
+ close(fd);
+
+ switch (rc) {
+ case 0:
+ fwrite(buf, 1, bufsiz, stdout);
+ free(buf);
+ break;
+ case 1:
+ warnx("too many attempts");
+ break;
+ default:
+ warn("%s: cannot read", filename);
+ break;
+ }
+
+ return rc;
+}
+
+static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
{
char *type = argv[1];
char *pattern = argv[2];
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];
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];
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];
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];
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;
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;
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]),
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;
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;
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;
{ "--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 }
};