+/* 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 rc;
}
-static int try_write(const char *filename)
+static int try_write(const char *filename, const char *directory)
{
- int fd, ret = 0;
+ int rc = 0;
if (!filename)
return -EINVAL;
- fd = open(filename, O_RDWR|O_CREAT|O_CLOEXEC,
+ 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)
- ret = -errno;
- close(fd);
- return ret;
+ if (fd < 0)
+ rc = -errno;
+ else
+ close(fd);
+ }
+ 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;
+ char *bufptr;
+
+ assert(buf);
+ assert(bufsiz);
+
+ *bufsiz = 0;
+
+ do {
+ ssize_t ret;
+
+ if (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);
+
+ if (ret < 0) {
+ /* error */
+ if (errno == EAGAIN || errno == EINTR) {
+ xusleep(250000);
+ tries++;
+ 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++;
+ continue;
+ }
+
+ /* successful read() without active poll() */
+ *bufsiz += (size_t) ret;
+ bufptr += ret;
+ tries = 0;
+ } else {
+ /* end-of-file */
+ goto success;
+ }
+ } while (tries <= 5);
+
+ 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 }
};