summaryrefslogtreecommitdiffstats
path: root/libmount/src/utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmount/src/utils.c')
-rw-r--r--libmount/src/utils.c1524
1 files changed, 1524 insertions, 0 deletions
diff --git a/libmount/src/utils.c b/libmount/src/utils.c
new file mode 100644
index 0000000..92829eb
--- /dev/null
+++ b/libmount/src/utils.c
@@ -0,0 +1,1524 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * 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.
+ */
+
+/**
+ * SECTION: utils
+ * @title: Utils
+ * @short_description: misc utils.
+ */
+#include <ctype.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+#include <poll.h>
+#include <blkid.h>
+
+#include "strutils.h"
+#include "pathnames.h"
+#include "mountP.h"
+#include "mangle.h"
+#include "canonicalize.h"
+#include "env.h"
+#include "match.h"
+#include "fileutils.h"
+#include "statfs_magic.h"
+#include "sysfs.h"
+
+int append_string(char **a, const char *b)
+{
+ size_t al, bl;
+ char *tmp;
+
+ assert(a);
+
+ if (!b || !*b)
+ return 0;
+ if (!*a) {
+ *a = strdup(b);
+ return !*a ? -ENOMEM : 0;
+ }
+
+ al = strlen(*a);
+ bl = strlen(b);
+
+ tmp = realloc(*a, al + bl + 1);
+ if (!tmp)
+ return -ENOMEM;
+ *a = tmp;
+ memcpy((*a) + al, b, bl + 1);
+ return 0;
+}
+
+/*
+ * Return 1 if the file is not accessible or empty
+ */
+int is_file_empty(const char *name)
+{
+ struct stat st;
+ assert(name);
+
+ return (stat(name, &st) != 0 || st.st_size == 0);
+}
+
+int mnt_valid_tagname(const char *tagname)
+{
+ if (tagname && *tagname && (
+ strcmp("ID", tagname) == 0 ||
+ strcmp("UUID", tagname) == 0 ||
+ strcmp("LABEL", tagname) == 0 ||
+ strcmp("PARTUUID", tagname) == 0 ||
+ strcmp("PARTLABEL", tagname) == 0))
+ return 1;
+
+ 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)
+{
+ char *p;
+ int rc = 0;
+
+ if (!str || !*str)
+ return -EINVAL;
+
+ p = strndup(str, len);
+ if (!p)
+ return -errno;
+
+ if (strtosize(p, res))
+ rc = -EINVAL;
+ free(p);
+ return rc;
+}
+
+/* used as a callback by bsearch in mnt_fstype_is_pseudofs() */
+static int fstype_cmp(const void *v1, const void *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
+}
+
+int mnt_lstat_mountpoint(const char *target, struct stat *st)
+{
+#ifdef AT_NO_AUTOMOUNT
+ return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW);
+#else
+ return lstat(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:
+ *
+ * mnt_chdir_to_parent("/mnt/test", &buf) ==> chdir("/mnt"), buf="test"
+ */
+int mnt_chdir_to_parent(const char *target, char **filename)
+{
+ char *buf, *parent, *last = NULL;
+ char cwd[PATH_MAX];
+ int rc = -EINVAL;
+
+ if (!target || *target != '/')
+ return -EINVAL;
+
+ DBG(UTILS, ul_debug("moving to %s parent", target));
+
+ buf = strdup(target);
+ if (!buf)
+ return -ENOMEM;
+
+ if (*(buf + 1) != '\0') {
+ last = stripoff_last_component(buf);
+ if (!last)
+ goto err;
+ }
+
+ parent = buf && *buf ? buf : "/";
+
+ if (chdir(parent) == -1) {
+ DBG(UTILS, ul_debug("failed to chdir to %s: %m", parent));
+ rc = -errno;
+ goto err;
+ }
+ if (!getcwd(cwd, sizeof(cwd))) {
+ DBG(UTILS, ul_debug("failed to obtain current directory: %m"));
+ rc = -errno;
+ goto err;
+ }
+ if (strcmp(cwd, parent) != 0) {
+ DBG(UTILS, ul_debug(
+ "unexpected chdir (expected=%s, cwd=%s)", parent, cwd));
+ goto err;
+ }
+
+ DBG(CXT, ul_debug(
+ "current directory moved to %s [last_component='%s']",
+ parent, last));
+
+ if (filename) {
+ *filename = buf;
+
+ if (!last || !*last)
+ memcpy(*filename, ".", 2);
+ else
+ memmove(*filename, last, strlen(last) + 1);
+ } else
+ free(buf);
+ return 0;
+err:
+ free(buf);
+ return rc;
+}
+
+/*
+ * Check if @path is on a read-only filesystem independently of file permissions.
+ */
+int mnt_is_readonly(const char *path)
+{
+ if (access(path, W_OK) == 0)
+ return 0;
+ if (errno == EROFS)
+ return 1;
+ if (errno != EACCES)
+ return 0;
+
+#ifdef HAVE_UTIMENSAT
+ /*
+ * access(2) returns EACCES on read-only FS:
+ *
+ * - for set-uid application if one component of the path is not
+ * accessible for the current rUID. (Note that euidaccess(2) does not
+ * check for EROFS at all).
+ *
+ * - 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 */
+
+ if (utimensat(AT_FDCWD, path, times, 0) == -1)
+ return errno == EROFS;
+ }
+#endif
+ return 0;
+}
+
+/**
+ * mnt_mangle:
+ * @str: string
+ *
+ * Encode @str to be compatible with fstab/mtab
+ *
+ * Returns: newly allocated string or NULL in case of error.
+ */
+char *mnt_mangle(const char *str)
+{
+ return mangle(str);
+}
+
+/**
+ * mnt_unmangle:
+ * @str: string
+ *
+ * Decode @str from fstab/mtab
+ *
+ * Returns: newly allocated string or NULL in case of error.
+ */
+char *mnt_unmangle(const char *str)
+{
+ return unmangle(str, NULL);
+}
+
+/**
+ * mnt_fstype_is_pseudofs:
+ * @type: filesystem name
+ *
+ * Returns: 1 for filesystems like proc, sysfs, ... or 0.
+ */
+int mnt_fstype_is_pseudofs(const char *type)
+{
+ /* This array must remain sorted when adding new fstypes */
+ static const char *pseudofs[] = {
+ "anon_inodefs",
+ "apparmorfs",
+ "autofs",
+ "bdev",
+ "binder",
+ "binfmt_misc",
+ "bpf",
+ "cgroup",
+ "cgroup2",
+ "configfs",
+ "cpuset",
+ "debugfs",
+ "devfs",
+ "devpts",
+ "devtmpfs",
+ "dlmfs",
+ "dmabuf",
+ "drm",
+ "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",
+ "hugetlbfs",
+ "ipathfs",
+ "mqueue",
+ "nfsd",
+ "none",
+ "nsfs",
+ "overlay",
+ "pipefs",
+ "proc",
+ "pstore",
+ "ramfs",
+ "resctrl",
+ "rootfs",
+ "rpc_pipefs",
+ "securityfs",
+ "selinuxfs",
+ "smackfs",
+ "sockfs",
+ "spufs",
+ "sysfs",
+ "tmpfs",
+ "tracefs"
+ };
+
+ assert(type);
+
+ return !(bsearch(&type, pseudofs, ARRAY_SIZE(pseudofs),
+ sizeof(char*), fstype_cmp) == NULL);
+}
+
+/**
+ * mnt_fstype_is_netfs:
+ * @type: filesystem name
+ *
+ * Returns: 1 for filesystems like cifs, nfs, ... or 0.
+ */
+int mnt_fstype_is_netfs(const char *type)
+{
+ if (strcmp(type, "cifs") == 0 ||
+ strcmp(type, "smb3") == 0 ||
+ strcmp(type, "smbfs") == 0 ||
+ 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 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".
+ *
+ * "bar" : "nofoo,bar" -> False (global "no" prefix)
+ *
+ * "bar" : "foo,bar" -> True
+ *
+ * "bar" : "foo,nobar" -> False
+ *
+ * Returns: 1 if type is matching, else 0. This function also returns
+ * 0 if @pattern is NULL and @type is non-NULL.
+ */
+int mnt_match_fstype(const char *type, const char *pattern)
+{
+ return match_fstype(type, pattern);
+}
+
+void mnt_free_filesystems(char **filesystems)
+{
+ char **p;
+
+ if (!filesystems)
+ return;
+ for (p = filesystems; *p; p++)
+ free(*p);
+ free(filesystems);
+}
+
+static int add_filesystem(char ***filesystems, char *name)
+{
+ int n = 0;
+
+ assert(filesystems);
+ assert(name);
+
+ if (*filesystems) {
+ char **p;
+ for (n = 0, p = *filesystems; *p; p++, n++) {
+ if (strcmp(*p, name) == 0)
+ return 0;
+ }
+ }
+
+ #define MYCHUNK 16
+
+ if (n == 0 || !((n + 1) % MYCHUNK)) {
+ size_t items = ((n + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK;
+ char **x = realloc(*filesystems, items * sizeof(char *));
+
+ if (!x)
+ goto err;
+ *filesystems = x;
+ }
+ name = strdup(name);
+ (*filesystems)[n] = name;
+ (*filesystems)[n + 1] = NULL;
+ if (!name)
+ goto err;
+ return 0;
+err:
+ mnt_free_filesystems(*filesystems);
+ return -ENOMEM;
+}
+
+static int get_filesystems(const char *filename, char ***filesystems, const char *pattern)
+{
+ int rc = 0;
+ FILE *f;
+ char line[129];
+
+ f = fopen(filename, "r" UL_CLOEXECSTR);
+ if (!f)
+ return 1;
+
+ DBG(UTILS, ul_debug("reading filesystems list from: %s", filename));
+
+ while (fgets(line, sizeof(line), f)) {
+ char name[sizeof(line)];
+
+ if (*line == '#' || strncmp(line, "nodev", 5) == 0)
+ continue;
+ if (sscanf(line, " %128[^\n ]\n", name) != 1)
+ continue;
+ if (strcmp(name, "*") == 0) {
+ rc = 1;
+ break; /* end of the /etc/filesystems */
+ }
+ if (pattern && !mnt_match_fstype(name, pattern))
+ continue;
+ rc = add_filesystem(filesystems, name);
+ if (rc)
+ break;
+ }
+
+ fclose(f);
+ return rc;
+}
+
+/*
+ * Always check the @filesystems pointer!
+ *
+ * man mount:
+ *
+ * ...mount will try to read the file /etc/filesystems, or, if that does not
+ * 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 afterwards.
+ */
+int mnt_get_filesystems(char ***filesystems, const char *pattern)
+{
+ int rc;
+
+ if (!filesystems)
+ return -EINVAL;
+
+ *filesystems = NULL;
+
+ rc = get_filesystems(_PATH_FILESYSTEMS, filesystems, pattern);
+ if (rc != 1)
+ return rc;
+
+ rc = get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern);
+ if (rc == 1 && *filesystems)
+ rc = 0; /* /proc/filesystems not found */
+
+ return rc;
+}
+
+/*
+ * Returns an allocated string with username or NULL.
+ */
+char *mnt_get_username(const uid_t uid)
+{
+ struct passwd pwd;
+ struct passwd *res;
+ char *buf, *username = NULL;
+
+ buf = malloc(UL_GETPW_BUFSIZ);
+ if (!buf)
+ return NULL;
+
+ if (!getpwuid_r(uid, &pwd, buf, UL_GETPW_BUFSIZ, &res) && res)
+ username = strdup(pwd.pw_name);
+
+ free(buf);
+ return username;
+}
+
+int mnt_get_uid(const char *username, uid_t *uid)
+{
+ int rc = -1;
+ struct passwd pwd;
+ struct passwd *pw;
+ char *buf;
+
+ if (!username || !uid)
+ return -EINVAL;
+
+ buf = malloc(UL_GETPW_BUFSIZ);
+ if (!buf)
+ return -ENOMEM;
+
+ if (!getpwnam_r(username, &pwd, buf, UL_GETPW_BUFSIZ, &pw) && pw) {
+ *uid= pw->pw_uid;
+ rc = 0;
+ } else {
+ DBG(UTILS, ul_debug(
+ "cannot convert '%s' username to UID", username));
+ rc = errno ? -errno : -EINVAL;
+ }
+
+ free(buf);
+ return rc;
+}
+
+int mnt_get_gid(const char *groupname, gid_t *gid)
+{
+ int rc = -1;
+ struct group grp;
+ struct group *gr;
+ char *buf;
+
+ if (!groupname || !gid)
+ return -EINVAL;
+
+ buf = malloc(UL_GETPW_BUFSIZ);
+ if (!buf)
+ return -ENOMEM;
+
+ if (!getgrnam_r(groupname, &grp, buf, UL_GETPW_BUFSIZ, &gr) && gr) {
+ *gid= gr->gr_gid;
+ rc = 0;
+ } else {
+ DBG(UTILS, ul_debug(
+ "cannot convert '%s' groupname to GID", groupname));
+ rc = errno ? -errno : -EINVAL;
+ }
+
+ free(buf);
+ return rc;
+}
+
+int mnt_in_group(gid_t gid)
+{
+ int rc = 0, n, i;
+ gid_t *grps = NULL;
+
+ if (getgid() == gid)
+ return 1;
+
+ n = getgroups(0, NULL);
+ if (n <= 0)
+ goto done;
+
+ grps = malloc(n * sizeof(*grps));
+ if (!grps)
+ goto done;
+
+ if (getgroups(n, grps) == n) {
+ for (i = 0; i < n; i++) {
+ if (grps[i] == gid) {
+ rc = 1;
+ break;
+ }
+ }
+ }
+done:
+ free(grps);
+ return rc;
+}
+
+static int try_write(const char *filename, const char *directory)
+{
+ int rc = 0;
+
+ if (!filename)
+ return -EINVAL;
+
+ 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;
+ }
+
+ if (errno != ENOENT) {
+ DBG(UTILS, ul_debug(" access FAILED"));
+ return -errno;
+ }
+
+ 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;
+ }
+#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 rc;
+}
+
+/**
+ * mnt_has_regular_mtab:
+ * @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.
+ *
+ * Returns: 1 if /etc/mtab is a regular file, and 0 in case of error (check
+ * errno for more details).
+ */
+int mnt_has_regular_mtab(const char **mtab, int *writable)
+{
+ struct stat st;
+ int rc;
+ const char *filename = mtab && *mtab ? *mtab : mnt_get_mtab_path();
+
+ if (writable)
+ *writable = 0;
+ if (mtab && !*mtab)
+ *mtab = filename;
+
+ DBG(UTILS, ul_debug("mtab: %s", filename));
+
+ rc = lstat(filename, &st);
+
+ if (rc == 0) {
+ /* file exists */
+ if (S_ISREG(st.st_mode)) {
+ if (writable)
+ *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, NULL);
+ if (*writable) {
+ DBG(UTILS, ul_debug("%s: writable", filename));
+ return 1;
+ }
+ }
+
+done:
+ 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
+ * 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
+ * error (check errno for more details).
+ */
+int mnt_has_regular_utab(const char **utab, int *writable)
+{
+ struct stat st;
+ int rc;
+ const char *filename = utab && *utab ? *utab : mnt_get_utab_path();
+
+ if (writable)
+ *writable = 0;
+ if (utab && !*utab)
+ *utab = filename;
+
+ DBG(UTILS, ul_debug("utab: %s", filename));
+
+ rc = lstat(filename, &st);
+
+ if (rc == 0) {
+ /* file exists */
+ if (S_ISREG(st.st_mode)) {
+ if (writable)
+ *writable = !try_write(filename, NULL);
+ return 1;
+ }
+ goto done; /* it's not a regular file */
+ }
+
+ if (writable) {
+ char *dirname = strdup(filename);
+
+ if (!dirname)
+ goto done;
+
+ stripoff_last_component(dirname); /* remove filename */
+
+ rc = mkdir(dirname, S_IWUSR|
+ S_IRUSR|S_IRGRP|S_IROTH|
+ S_IXUSR|S_IXGRP|S_IXOTH);
+ if (rc && errno != EEXIST) {
+ free(dirname);
+ goto done; /* probably EACCES */
+ }
+
+ *writable = !try_write(filename, dirname);
+ free(dirname);
+ if (*writable)
+ return 1;
+ }
+done:
+ DBG(UTILS, ul_debug("%s: irregular/non-writable file", filename));
+ return 0;
+}
+
+/**
+ * mnt_get_swaps_path:
+ *
+ * Returns: path to /proc/swaps or $LIBMOUNT_SWAPS.
+ */
+const char *mnt_get_swaps_path(void)
+{
+ const char *p = safe_getenv("LIBMOUNT_SWAPS");
+ return p ? : _PATH_PROC_SWAPS;
+}
+
+/**
+ * mnt_get_fstab_path:
+ *
+ * Returns: path to /etc/fstab or $LIBMOUNT_FSTAB.
+ */
+const char *mnt_get_fstab_path(void)
+{
+ const char *p = safe_getenv("LIBMOUNT_FSTAB");
+ return p ? : _PATH_MNTTAB;
+}
+
+/**
+ * mnt_get_mtab_path:
+ *
+ * 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.
+ */
+const char *mnt_get_mtab_path(void)
+{
+ const char *p = safe_getenv("LIBMOUNT_MTAB");
+ return p ? : _PATH_MOUNTED;
+}
+
+/*
+ * Don't export this to libmount API -- utab is private library stuff.
+ *
+ * Returns: path to /run/mount/utab (or /dev/.mount/utab) or $LIBMOUNT_UTAB.
+ */
+const char *mnt_get_utab_path(void)
+{
+ struct stat st;
+ const char *p = safe_getenv("LIBMOUNT_UTAB");
+
+ if (p)
+ return p;
+
+ if (stat(MNT_RUNTIME_TOPDIR, &st) == 0)
+ return MNT_PATH_UTAB;
+
+ return MNT_PATH_UTAB_OLD;
+}
+
+
+/* returns file descriptor or -errno, @name returns a unique filename
+ */
+int mnt_open_uniq_filename(const char *filename, char **name)
+{
+ int rc, fd;
+ char *n;
+ mode_t oldmode;
+
+ if (!filename)
+ return -EINVAL;
+ if (name)
+ *name = NULL;
+
+ rc = asprintf(&n, "%s.XXXXXX", filename);
+ if (rc <= 0)
+ return -errno;
+
+ /* 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 = mkostemp(n, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
+ if (fd < 0)
+ fd = -errno;
+ umask(oldmode);
+
+ if (fd >= 0 && name)
+ *name = n;
+ else
+ free(n);
+
+ return fd;
+}
+
+/**
+ * mnt_get_mountpoint:
+ * @path: pathname
+ *
+ * This function finds the mountpoint that a given path resides in. @path
+ * 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 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;
+ 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 (mnt_stat_mountpoint(mnt, &st))
+ goto err;
+ base = st.st_dev;
+
+ do {
+ char *p = stripoff_last_component(mnt);
+
+ if (!p)
+ break;
+ if (mnt_stat_mountpoint(*mnt ? mnt : "/", &st))
+ goto err;
+ dir = st.st_dev;
+ if (dir != base) {
+ if (p > mnt)
+ *(p - 1) = '/';
+ goto done;
+ }
+ base = dir;
+ } while (mnt && *(mnt + 1) != '\0');
+
+ memcpy(mnt, "/", 2);
+done:
+ DBG(UTILS, ul_debug("%s mountpoint is %s", path, mnt));
+ return mnt;
+err:
+ free(mnt);
+ return NULL;
+}
+
+/*
+ * 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
+ * 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, *mem = NULL;
+ char buf[BUFSIZ]; /* see kernel include/asm-generic/setup.h: COMMAND_LINE_SIZE */
+ const char *path = _PATH_PROC_CMDLINE;
+
+ if (!name || !name[0])
+ return NULL;
+
+#ifdef TEST_PROGRAM
+ path = getenv("LIBMOUNT_KERNEL_CMDLINE");
+ if (!path)
+ path = _PATH_PROC_CMDLINE;
+#endif
+ f = fopen(path, "r" UL_CLOEXECSTR);
+ if (!f)
+ return NULL;
+
+ p = fgets(buf, sizeof(buf), f);
+ fclose(f);
+
+ if (!p || !*p || *p == '\n')
+ return NULL;
+
+ 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 (name[len - 1] == '=')
+ val = 1;
+
+ 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 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';
+ free(mem);
+ res = mem = strdup(v);
+ if (end)
+ break;
+ } else
+ res = (char *) name; /* option without '=' */
+ /* 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;
+
+ } 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
+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];
+
+ printf("%s\n", mnt_match_fstype(type, pattern) ? "MATCH" : "NOT-MATCH");
+ return 0;
+}
+
+static int test_match_options(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr = argv[1];
+ char *pattern = argv[2];
+
+ printf("%s\n", mnt_match_options(optstr, pattern) ? "MATCH" : "NOT-MATCH");
+ return 0;
+}
+
+static int test_startswith(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr = argv[1];
+ char *pattern = argv[2];
+
+ printf("%s\n", startswith(optstr, pattern) ? "YES" : "NOT");
+ return 0;
+}
+
+static int test_endswith(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr = argv[1];
+ char *pattern = argv[2];
+
+ printf("%s\n", endswith(optstr, pattern) ? "YES" : "NOT");
+ return 0;
+}
+
+static int test_appendstr(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *str = strdup(argv[1]);
+ const char *ap = argv[2];
+
+ append_string(&str, ap);
+ printf("new string: '%s'\n", str);
+
+ free(str);
+ return 0;
+}
+
+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;
+
+ printf("%s: %s\n", argv[1], mnt ? : "unknown");
+ free(mnt);
+ free(path);
+ return 0;
+}
+
+static int test_filesystems(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char **filesystems = NULL;
+ int rc;
+
+ rc = mnt_get_filesystems(&filesystems, argc ? argv[1] : NULL);
+ if (!rc) {
+ char **p;
+ for (p = filesystems; *p; p++)
+ printf("%s\n", *p);
+ mnt_free_filesystems(filesystems);
+ }
+ return rc;
+}
+
+static int test_chdir(struct libmnt_test *ts, int argc, char *argv[])
+{
+ int rc;
+ char *path = canonicalize_path(argv[1]),
+ *last = NULL;
+
+ if (!path)
+ return -errno;
+
+ rc = mnt_chdir_to_parent(path, &last);
+ if (!rc) {
+ printf("path='%s', abs='%s', last='%s'\n",
+ argv[1], path, last);
+ }
+ free(path);
+ free(last);
+ return rc;
+}
+
+static int test_kernel_cmdline(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *name = argv[1];
+ char *res;
+
+ res = mnt_get_kernel_cmdline_option(name);
+ if (!res)
+ printf("'%s' not found\n", name);
+ else if (res == name)
+ printf("'%s' found\n", name);
+ else {
+ printf("'%s' found, argument: '%s'\n", name, res);
+ free(res);
+ }
+
+ return 0;
+}
+
+
+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[] = {
+ { "--match-fstype", test_match_fstype, "<type> <pattern> FS types matching" },
+ { "--match-options", test_match_options, "<options> <pattern> options matching" },
+ { "--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>" },
+ { "--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 }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */