+/* 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 "pathnames.h"
#include "canonicalize.h"
#include "env.h"
#include "match.h"
-
-int endswith(const char *s, const char *sx)
-{
- ssize_t off;
-
- assert(s);
- assert(sx);
-
- off = strlen(s);
- if (!off)
- return 0;
- off -= strlen(sx);
- if (off < 0)
- return 0;
-
- return !strcmp(s + off, sx);
-}
-
-int startswith(const char *s, const char *sx)
-{
- size_t off;
-
- assert(s);
- assert(sx);
-
- off = strlen(sx);
- if (!off)
- return 0;
-
- return !strncmp(s, sx, off);
-}
+#include "fileutils.h"
+#include "statfs_magic.h"
+#include "sysfs.h"
int append_string(char **a, const char *b)
{
}
al = strlen(*a);
- bl = b ? strlen(b) : 0;
+ bl = strlen(b);
tmp = realloc(*a, al + bl + 1);
if (!tmp)
}
/*
- * Return 1 if the file does not accessible of empty
+ * Return 1 if the file is not accessible or empty
*/
int is_file_empty(const char *name)
{
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;
/* 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);
}
-/* returns basename and keeps dirname in the @path, if @path is "/" (root)
- * then returns empty string */
-char *stripoff_last_component(char *path)
+int mnt_stat_mountpoint(const char *target, struct stat *st)
{
- char *p = path ? strrchr(path, '/') : NULL;
-
- if (!p)
- return NULL;
- *p = '\0';
- return p + 1;
+#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 absolute path (so at least "/"). The
- * @filename returns allocated buffer with last path component, for example:
+ * 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"
*/
if (!target || *target != '/')
return -EINVAL;
- DBG(UTILS, mnt_debug("moving to %s parent", target));
+ DBG(UTILS, ul_debug("moving to %s parent", target));
buf = strdup(target);
if (!buf)
parent = buf && *buf ? buf : "/";
if (chdir(parent) == -1) {
- DBG(UTILS, mnt_debug("failed to chdir to %s: %m", parent));
+ DBG(UTILS, ul_debug("failed to chdir to %s: %m", parent));
rc = -errno;
goto err;
}
if (!getcwd(cwd, sizeof(cwd))) {
- DBG(UTILS, mnt_debug("failed to obtain current directory: %m"));
+ DBG(UTILS, ul_debug("failed to obtain current directory: %m"));
rc = -errno;
goto err;
}
if (strcmp(cwd, parent) != 0) {
- DBG(UTILS, mnt_debug(
+ DBG(UTILS, ul_debug(
"unexpected chdir (expected=%s, cwd=%s)", parent, cwd));
goto err;
}
- DBG(CXT, mnt_debug(
+ DBG(CXT, ul_debug(
"current directory moved to %s [last_component='%s']",
parent, last));
if (!last || !*last)
memcpy(*filename, ".", 2);
else
- memcpy(*filename, last, strlen(last) + 1);
+ memmove(*filename, last, strlen(last) + 1);
} else
free(buf);
return 0;
}
/*
- * Check if @path is on read-only filesystem independently on file permissions.
+ * Check if @path is on a read-only filesystem independently of file permissions.
*/
int mnt_is_readonly(const char *path)
{
if (errno != EACCES)
return 0;
-#ifdef HAVE_FUTIMENS
+#ifdef HAVE_UTIMENSAT
/*
* access(2) returns EACCES on read-only FS:
*
* accessible for the current rUID. (Note that euidaccess(2) does not
* check for EROFS at all).
*
- * - for read-write filesystem with read-only VFS node (aka -o remount,ro,bind)
+ * - 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 */
*
* Encode @str to be compatible with fstab/mtab
*
- * Returns: new allocated string or NULL in case of error.
+ * Returns: newly allocated string or NULL in case of error.
*/
char *mnt_mangle(const char *str)
{
*
* Decode @str from fstab/mtab
*
- * Returns: new allocated string or NULL in case of error.
+ * Returns: newly allocated string or NULL in case of error.
*/
char *mnt_unmangle(const char *str)
{
"autofs",
"bdev",
"binfmt_misc",
+ "bpf",
"cgroup",
+ "cgroup2",
"configfs",
"cpuset",
"debugfs",
"devpts",
"devtmpfs",
"dlmfs",
- "fuse.gvfs-fuse-daemon",
+ "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.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",
"pstore",
"rootfs",
"rpc_pipefs",
"securityfs",
+ "selinuxfs",
"sockfs",
"spufs",
"sysfs",
*/
int mnt_fstype_is_netfs(const char *type)
{
- assert(type);
-
if (strcmp(type, "cifs") == 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 filesystem can be prefixed with a global
+ * 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".
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 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 disable by "+" prefix, for example
- * "+noauto" matches if @optstr literally contains "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);
{
int rc = 0;
FILE *f;
- char line[128];
+ char line[129];
f = fopen(filename, "r" UL_CLOEXECSTR);
if (!f)
return 1;
- DBG(UTILS, mnt_debug("reading filesystems list from: %s", filename));
+ DBG(UTILS, ul_debug("reading filesystems list from: %s", filename));
while (fgets(line, sizeof(line), f)) {
char name[sizeof(line)];
}
/*
- * Always check @filesystems pointer!
+ * Always check the @filesystems pointer!
*
* man mount:
*
rc = get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern);
if (rc == 1 && *filesystems)
- rc = 0; /* not found /proc/filesystems */
+ rc = 0; /* /proc/filesystems not found */
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 allocated string with username or NULL.
+ * Returns an allocated string with username or NULL.
*/
char *mnt_get_username(const uid_t uid)
{
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 {
- DBG(UTILS, mnt_debug(
+ DBG(UTILS, ul_debug(
"cannot convert '%s' username to UID", username));
rc = errno ? -errno : -EINVAL;
}
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 {
- DBG(UTILS, mnt_debug(
+ DBG(UTILS, ul_debug(
"cannot convert '%s' groupname to GID", groupname));
rc = errno ? -errno : -EINVAL;
}
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;
}
/**
* @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
+ * 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).
if (mtab && !*mtab)
*mtab = filename;
- DBG(UTILS, mnt_debug("mtab: %s", filename));
+ DBG(UTILS, ul_debug("mtab: %s", filename));
rc = lstat(filename, &st);
if (rc == 0) {
- /* file exist */
+ /* 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:
- DBG(UTILS, mnt_debug("%s: irregular/non-writable", filename));
+ 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
+ * 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
if (utab && !*utab)
*utab = filename;
- DBG(UTILS, mnt_debug("utab: %s", filename));
+ DBG(UTILS, ul_debug("utab: %s", filename));
rc = lstat(filename, &st);
if (rc == 0) {
- /* file exist */
+ /* 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 regular file */
+ goto done; /* it's not a regular file */
}
if (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;
}
done:
- DBG(UTILS, mnt_debug("%s: irregular/non-writable file", filename));
+ DBG(UTILS, ul_debug("%s: irregular/non-writable file", filename));
return 0;
}
/**
* mnt_get_mtab_path:
*
- * This function returns *default* location of the mtab file. The result does
+ * 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.
}
-/* returns file descriptor or -errno, @name returns uniques filename
+/* returns file descriptor or -errno, @name returns a unique filename
*/
int mnt_open_uniq_filename(const char *filename, char **name)
{
if (rc <= 0)
return -errno;
- /* This is for very old glibc and for compatibility with Posix where is
+ /* 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)
else
free(n);
- return fd < 0 ? -errno : fd;
+ return fd;
}
/**
* This function finds the mountpoint that a given path resides in. @path
* should be canonicalized. The returned pointer should be freed by the caller.
*
- * Returns: allocated string with target of the mounted device or NULL on error
+ * 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)
{
struct stat st;
dev_t dir, base;
- assert(path);
+ if (!path)
+ return NULL;
mnt = strdup(path);
if (!mnt)
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) {
memcpy(mnt, "/", 2);
done:
- DBG(UTILS, mnt_debug("%s mountpoint is %s", path, mnt));
+ DBG(UTILS, ul_debug("%s mountpoint is %s", path, mnt));
return mnt;
err:
free(mnt);
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, mnt_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 parameter argument if the @name is
+ * 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.
+ * 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;
+ 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)
+ if (!name || !name[0])
return NULL;
#ifdef TEST_PROGRAM
if (!p || !*p || *p == '\n')
return NULL;
- len = strlen(buf);
- *(buf + len - 1) = '\0'; /* remove last '\n' */
+ 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 (len && *(name + len - 1) == '=')
+ if (name[len - 1] == '=')
val = 1;
- for ( ; p && *p; p++) {
+ 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 behind the option */
+ 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';
- res = strdup(v);
- break;
+ free(mem);
+ res = mem = strdup(v);
+ if (end)
+ break;
} else
res = (char *) name; /* option without '=' */
- break;
+ /* don't break -- keep scanning for more options */
}
return res;
}
-int mkdir_p(const char *path, mode_t mode)
+/**
+ * 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 *p, *dir;
- int rc = 0;
+ char buf[PATH_MAX];
+ char *dev = NULL, *spec = NULL;
+ unsigned int x, y;
+ int allocated = 0;
- if (!path || !*path)
- return -EINVAL;
+ assert(path);
- dir = p = strdup(path);
- if (!dir)
- return -ENOMEM;
+ 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);
- if (*p == '/')
- p++;
+ *bufsiz = 0;
+ *buf = NULL;
- while (p && *p) {
- char *e = strchr(p, '/');
- if (e)
- *e = '\0';
- if (*p) {
- rc = mkdir(dir, mode);
- if (rc && errno != EEXIST)
+ do {
+ ssize_t ret;
+
+ if (!bufptr || bufmax == *bufsiz) {
+ char *tmp;
+
+ bufmax = bufmax ? bufmax * 2 : (16 * 1024);
+ tmp = realloc(*buf, bufmax);
+ if (!tmp)
break;
- rc = 0;
+ *buf = tmp;
+ bufptr = tmp + *bufsiz;
}
- if (!e)
+
+ errno = 0;
+ ret = read(fd, bufptr, bufmax - *bufsiz);
+
+ if (ret < 0) {
+ /* error */
+ if ((errno == EAGAIN || errno == EINTR) && (ninters++ < 5)) {
+ xusleep(200000);
+ continue;
+ }
break;
- *e = '/';
- p = e + 1;
- }
- DBG(UTILS, mnt_debug("%s mkdir %s", path, rc ? "FAILED" : "SUCCESS"));
+ } 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;
+ }
- free(dir);
+ /* 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
-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;
}
+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[])
{
{ "--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 }
};