LCOV - code coverage report
Current view: top level - basic - mountpoint-util.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 125 181 69.1 %
Date: 2019-08-22 15:41:25 Functions: 10 13 76.9 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: LGPL-2.1+ */
       2             : 
       3             : #include <errno.h>
       4             : #include <fcntl.h>
       5             : #include <sys/mount.h>
       6             : 
       7             : #include "alloc-util.h"
       8             : #include "fd-util.h"
       9             : #include "fileio.h"
      10             : #include "fs-util.h"
      11             : #include "missing.h"
      12             : #include "mountpoint-util.h"
      13             : #include "parse-util.h"
      14             : #include "path-util.h"
      15             : #include "stdio-util.h"
      16             : #include "strv.h"
      17             : 
      18             : /* This is the original MAX_HANDLE_SZ definition from the kernel, when the API was introduced. We use that in place of
      19             :  * any more currently defined value to future-proof things: if the size is increased in the API headers, and our code
      20             :  * is recompiled then it would cease working on old kernels, as those refuse any sizes larger than this value with
      21             :  * EINVAL right-away. Hence, let's disconnect ourselves from any such API changes, and stick to the original definition
      22             :  * from when it was introduced. We use it as a start value only anyway (see below), and hence should be able to deal
      23             :  * with large file handles anyway. */
      24             : #define ORIGINAL_MAX_HANDLE_SZ 128
      25             : 
      26         201 : int name_to_handle_at_loop(
      27             :                 int fd,
      28             :                 const char *path,
      29             :                 struct file_handle **ret_handle,
      30             :                 int *ret_mnt_id,
      31             :                 int flags) {
      32             : 
      33         402 :         _cleanup_free_ struct file_handle *h = NULL;
      34         201 :         size_t n = ORIGINAL_MAX_HANDLE_SZ;
      35             : 
      36             :         /* We need to invoke name_to_handle_at() in a loop, given that it might return EOVERFLOW when the specified
      37             :          * buffer is too small. Note that in contrast to what the docs might suggest, MAX_HANDLE_SZ is only good as a
      38             :          * start value, it is not an upper bound on the buffer size required.
      39             :          *
      40             :          * This improves on raw name_to_handle_at() also in one other regard: ret_handle and ret_mnt_id can be passed
      41             :          * as NULL if there's no interest in either. */
      42             : 
      43           0 :         for (;;) {
      44         201 :                 int mnt_id = -1;
      45             : 
      46         201 :                 h = malloc0(offsetof(struct file_handle, f_handle) + n);
      47         201 :                 if (!h)
      48         201 :                         return -ENOMEM;
      49             : 
      50         201 :                 h->handle_bytes = n;
      51             : 
      52         201 :                 if (name_to_handle_at(fd, path, h, &mnt_id, flags) >= 0) {
      53             : 
      54         168 :                         if (ret_handle)
      55         145 :                                 *ret_handle = TAKE_PTR(h);
      56             : 
      57         168 :                         if (ret_mnt_id)
      58         168 :                                 *ret_mnt_id = mnt_id;
      59             : 
      60         168 :                         return 0;
      61             :                 }
      62          33 :                 if (errno != EOVERFLOW)
      63          33 :                         return -errno;
      64             : 
      65           0 :                 if (!ret_handle && ret_mnt_id && mnt_id >= 0) {
      66             : 
      67             :                         /* As it appears, name_to_handle_at() fills in mnt_id even when it returns EOVERFLOW when the
      68             :                          * buffer is too small, but that's undocumented. Hence, let's make use of this if it appears to
      69             :                          * be filled in, and the caller was interested in only the mount ID an nothing else. */
      70             : 
      71           0 :                         *ret_mnt_id = mnt_id;
      72           0 :                         return 0;
      73             :                 }
      74             : 
      75             :                 /* If name_to_handle_at() didn't increase the byte size, then this EOVERFLOW is caused by something
      76             :                  * else (apparently EOVERFLOW is returned for untriggered nfs4 mounts sometimes), not by the too small
      77             :                  * buffer. In that case propagate EOVERFLOW */
      78           0 :                 if (h->handle_bytes <= n)
      79           0 :                         return -EOVERFLOW;
      80             : 
      81             :                 /* The buffer was too small. Size the new buffer by what name_to_handle_at() returned. */
      82           0 :                 n = h->handle_bytes;
      83           0 :                 if (offsetof(struct file_handle, f_handle) + n < n) /* check for addition overflow */
      84           0 :                         return -EOVERFLOW;
      85             : 
      86           0 :                 h = mfree(h);
      87             :         }
      88             : }
      89             : 
      90          24 : static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) {
      91             :         char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
      92          24 :         _cleanup_free_ char *fdinfo = NULL;
      93          24 :         _cleanup_close_ int subfd = -1;
      94             :         char *p;
      95             :         int r;
      96             : 
      97          24 :         if ((flags & AT_EMPTY_PATH) && isempty(filename))
      98           4 :                 xsprintf(path, "/proc/self/fdinfo/%i", fd);
      99             :         else {
     100          20 :                 subfd = openat(fd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_FOLLOW ? 0 : O_NOFOLLOW));
     101          20 :                 if (subfd < 0)
     102           1 :                         return -errno;
     103             : 
     104          19 :                 xsprintf(path, "/proc/self/fdinfo/%i", subfd);
     105             :         }
     106             : 
     107          23 :         r = read_full_file(path, &fdinfo, NULL);
     108          23 :         if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
     109           0 :                 return -EOPNOTSUPP;
     110          23 :         if (r < 0)
     111           0 :                 return r;
     112             : 
     113          23 :         p = startswith(fdinfo, "mnt_id:");
     114          23 :         if (!p) {
     115          23 :                 p = strstr(fdinfo, "\nmnt_id:");
     116          23 :                 if (!p) /* The mnt_id field is a relatively new addition */
     117           0 :                         return -EOPNOTSUPP;
     118             : 
     119          23 :                 p += 8;
     120             :         }
     121             : 
     122          23 :         p += strspn(p, WHITESPACE);
     123          23 :         p[strcspn(p, WHITESPACE)] = 0;
     124             : 
     125          23 :         return safe_atoi(p, mnt_id);
     126             : }
     127             : 
     128          81 : int fd_is_mount_point(int fd, const char *filename, int flags) {
     129          81 :         _cleanup_free_ struct file_handle *h = NULL, *h_parent = NULL;
     130          81 :         int mount_id = -1, mount_id_parent = -1;
     131          81 :         bool nosupp = false, check_st_dev = true;
     132             :         struct stat a, b;
     133             :         int r;
     134             : 
     135          81 :         assert(fd >= 0);
     136          81 :         assert(filename);
     137             : 
     138             :         /* First we will try the name_to_handle_at() syscall, which
     139             :          * tells us the mount id and an opaque file "handle". It is
     140             :          * not supported everywhere though (kernel compile-time
     141             :          * option, not all file systems are hooked up). If it works
     142             :          * the mount id is usually good enough to tell us whether
     143             :          * something is a mount point.
     144             :          *
     145             :          * If that didn't work we will try to read the mount id from
     146             :          * /proc/self/fdinfo/<fd>. This is almost as good as
     147             :          * name_to_handle_at(), however, does not return the
     148             :          * opaque file handle. The opaque file handle is pretty useful
     149             :          * to detect the root directory, which we should always
     150             :          * consider a mount point. Hence we use this only as
     151             :          * fallback. Exporting the mnt_id in fdinfo is a pretty recent
     152             :          * kernel addition.
     153             :          *
     154             :          * As last fallback we do traditional fstat() based st_dev
     155             :          * comparisons. This is how things were traditionally done,
     156             :          * but unionfs breaks this since it exposes file
     157             :          * systems with a variety of st_dev reported. Also, btrfs
     158             :          * subvolumes have different st_dev, even though they aren't
     159             :          * real mounts of their own. */
     160             : 
     161          81 :         r = name_to_handle_at_loop(fd, filename, &h, &mount_id, flags);
     162          81 :         if (IN_SET(r, -ENOSYS, -EACCES, -EPERM, -EOVERFLOW, -EINVAL))
     163             :                 /* This kernel does not support name_to_handle_at() at all (ENOSYS), or the syscall was blocked
     164             :                  * (EACCES/EPERM; maybe through seccomp, because we are running inside of a container?), or the mount
     165             :                  * point is not triggered yet (EOVERFLOW, think nfs4), or some general name_to_handle_at() flakiness
     166             :                  * (EINVAL): fall back to simpler logic. */
     167           0 :                 goto fallback_fdinfo;
     168          81 :         else if (r == -EOPNOTSUPP)
     169             :                 /* This kernel or file system does not support name_to_handle_at(), hence let's see if the upper fs
     170             :                  * supports it (in which case it is a mount point), otherwise fallback to the traditional stat()
     171             :                  * logic */
     172          13 :                 nosupp = true;
     173          68 :         else if (r < 0)
     174           0 :                 return r;
     175             : 
     176          81 :         r = name_to_handle_at_loop(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH);
     177          81 :         if (r == -EOPNOTSUPP) {
     178           4 :                 if (nosupp)
     179             :                         /* Neither parent nor child do name_to_handle_at()?  We have no choice but to fall back. */
     180           4 :                         goto fallback_fdinfo;
     181             :                 else
     182             :                         /* The parent can't do name_to_handle_at() but the directory we are interested in can?  If so,
     183             :                          * it must be a mount point. */
     184           0 :                         return 1;
     185          77 :         } else if (r < 0)
     186           0 :                 return r;
     187             : 
     188             :         /* The parent can do name_to_handle_at() but the
     189             :          * directory we are interested in can't? If so, it
     190             :          * must be a mount point. */
     191          77 :         if (nosupp)
     192           9 :                 return 1;
     193             : 
     194             :         /* If the file handle for the directory we are
     195             :          * interested in and its parent are identical, we
     196             :          * assume this is the root directory, which is a mount
     197             :          * point. */
     198             : 
     199          68 :         if (h->handle_bytes == h_parent->handle_bytes &&
     200          66 :             h->handle_type == h_parent->handle_type &&
     201          66 :             memcmp(h->f_handle, h_parent->f_handle, h->handle_bytes) == 0)
     202           1 :                 return 1;
     203             : 
     204          67 :         return mount_id != mount_id_parent;
     205             : 
     206           4 : fallback_fdinfo:
     207           4 :         r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
     208           4 :         if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM))
     209           0 :                 goto fallback_fstat;
     210           4 :         if (r < 0)
     211           0 :                 return r;
     212             : 
     213           4 :         r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
     214           4 :         if (r < 0)
     215           0 :                 return r;
     216             : 
     217           4 :         if (mount_id != mount_id_parent)
     218           0 :                 return 1;
     219             : 
     220             :         /* Hmm, so, the mount ids are the same. This leaves one
     221             :          * special case though for the root file system. For that,
     222             :          * let's see if the parent directory has the same inode as we
     223             :          * are interested in. Hence, let's also do fstat() checks now,
     224             :          * too, but avoid the st_dev comparisons, since they aren't
     225             :          * that useful on unionfs mounts. */
     226           4 :         check_st_dev = false;
     227             : 
     228           4 : fallback_fstat:
     229             :         /* yay for fstatat() taking a different set of flags than the other
     230             :          * _at() above */
     231           4 :         if (flags & AT_SYMLINK_FOLLOW)
     232           2 :                 flags &= ~AT_SYMLINK_FOLLOW;
     233             :         else
     234           2 :                 flags |= AT_SYMLINK_NOFOLLOW;
     235           4 :         if (fstatat(fd, filename, &a, flags) < 0)
     236           0 :                 return -errno;
     237             : 
     238           4 :         if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0)
     239           0 :                 return -errno;
     240             : 
     241             :         /* A directory with same device and inode as its parent? Must
     242             :          * be the root directory */
     243           4 :         if (a.st_dev == b.st_dev &&
     244           4 :             a.st_ino == b.st_ino)
     245           0 :                 return 1;
     246             : 
     247           4 :         return check_st_dev && (a.st_dev != b.st_dev);
     248             : }
     249             : 
     250             : /* flags can be AT_SYMLINK_FOLLOW or 0 */
     251          33 : int path_is_mount_point(const char *t, const char *root, int flags) {
     252          33 :         _cleanup_free_ char *canonical = NULL;
     253          33 :         _cleanup_close_ int fd = -1;
     254             :         int r;
     255             : 
     256          33 :         assert(t);
     257          33 :         assert((flags & ~AT_SYMLINK_FOLLOW) == 0);
     258             : 
     259          33 :         if (path_equal(t, "/"))
     260           5 :                 return 1;
     261             : 
     262             :         /* we need to resolve symlinks manually, we can't just rely on
     263             :          * fd_is_mount_point() to do that for us; if we have a structure like
     264             :          * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
     265             :          * look at needs to be /usr, not /. */
     266          28 :         if (flags & AT_SYMLINK_FOLLOW) {
     267          16 :                 r = chase_symlinks(t, root, CHASE_TRAIL_SLASH, &canonical);
     268          16 :                 if (r < 0)
     269           0 :                         return r;
     270             : 
     271          16 :                 t = canonical;
     272             :         }
     273             : 
     274          28 :         fd = open_parent(t, O_PATH|O_CLOEXEC, 0);
     275          28 :         if (fd < 0)
     276           0 :                 return -errno;
     277             : 
     278          28 :         return fd_is_mount_point(fd, last_path_component(t), flags);
     279             : }
     280             : 
     281          39 : int path_get_mnt_id(const char *path, int *ret) {
     282             :         int r;
     283             : 
     284          39 :         r = name_to_handle_at_loop(AT_FDCWD, path, NULL, ret, 0);
     285          39 :         if (IN_SET(r, -EOPNOTSUPP, -ENOSYS, -EACCES, -EPERM, -EOVERFLOW, -EINVAL)) /* kernel/fs don't support this, or seccomp blocks access, or untriggered mount, or name_to_handle_at() is flaky */
     286          16 :                 return fd_fdinfo_mnt_id(AT_FDCWD, path, 0, ret);
     287             : 
     288          23 :         return r;
     289             : }
     290             : 
     291          30 : bool fstype_is_network(const char *fstype) {
     292             :         const char *x;
     293             : 
     294          30 :         x = startswith(fstype, "fuse.");
     295          30 :         if (x)
     296           1 :                 fstype = x;
     297             : 
     298          30 :         return STR_IN_SET(fstype,
     299             :                           "afs",
     300             :                           "cifs",
     301             :                           "smbfs",
     302             :                           "sshfs",
     303             :                           "ncpfs",
     304             :                           "ncp",
     305             :                           "nfs",
     306             :                           "nfs4",
     307             :                           "gfs",
     308             :                           "gfs2",
     309             :                           "glusterfs",
     310             :                           "pvfs2", /* OrangeFS */
     311             :                           "ocfs2",
     312             :                           "lustre");
     313             : }
     314             : 
     315          30 : bool fstype_is_api_vfs(const char *fstype) {
     316          30 :         return STR_IN_SET(fstype,
     317             :                           "autofs",
     318             :                           "bpf",
     319             :                           "cgroup",
     320             :                           "cgroup2",
     321             :                           "configfs",
     322             :                           "cpuset",
     323             :                           "debugfs",
     324             :                           "devpts",
     325             :                           "devtmpfs",
     326             :                           "efivarfs",
     327             :                           "fusectl",
     328             :                           "hugetlbfs",
     329             :                           "mqueue",
     330             :                           "proc",
     331             :                           "pstore",
     332             :                           "ramfs",
     333             :                           "securityfs",
     334             :                           "sysfs",
     335             :                           "tmpfs",
     336             :                           "tracefs");
     337             : }
     338             : 
     339          16 : bool fstype_is_ro(const char *fstype) {
     340             :         /* All Linux file systems that are necessarily read-only */
     341          16 :         return STR_IN_SET(fstype,
     342             :                           "DM_verity_hash",
     343             :                           "iso9660",
     344             :                           "squashfs");
     345             : }
     346             : 
     347           0 : bool fstype_can_discard(const char *fstype) {
     348           0 :         return STR_IN_SET(fstype,
     349             :                           "btrfs",
     350             :                           "ext4",
     351             :                           "vfat",
     352             :                           "xfs");
     353             : }
     354             : 
     355           0 : bool fstype_can_uid_gid(const char *fstype) {
     356             : 
     357             :         /* All file systems that have a uid=/gid= mount option that fixates the owners of all files and directories,
     358             :          * current and future. */
     359             : 
     360           0 :         return STR_IN_SET(fstype,
     361             :                           "adfs",
     362             :                           "exfat",
     363             :                           "fat",
     364             :                           "hfs",
     365             :                           "hpfs",
     366             :                           "iso9660",
     367             :                           "msdos",
     368             :                           "ntfs",
     369             :                           "vfat");
     370             : }
     371             : 
     372           0 : int dev_is_devtmpfs(void) {
     373           0 :         _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
     374             :         int mount_id, r;
     375             :         char *e;
     376             : 
     377           0 :         r = path_get_mnt_id("/dev", &mount_id);
     378           0 :         if (r < 0)
     379           0 :                 return r;
     380             : 
     381           0 :         r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo);
     382           0 :         if (r < 0)
     383           0 :                 return r;
     384             : 
     385           0 :         for (;;) {
     386           0 :                 _cleanup_free_ char *line = NULL;
     387             :                 int mid;
     388             : 
     389           0 :                 r = read_line(proc_self_mountinfo, LONG_LINE_MAX, &line);
     390           0 :                 if (r < 0)
     391           0 :                         return r;
     392           0 :                 if (r == 0)
     393           0 :                         break;
     394             : 
     395           0 :                 if (sscanf(line, "%i", &mid) != 1)
     396           0 :                         continue;
     397             : 
     398           0 :                 if (mid != mount_id)
     399           0 :                         continue;
     400             : 
     401           0 :                 e = strstr(line, " - ");
     402           0 :                 if (!e)
     403           0 :                         continue;
     404             : 
     405             :                 /* accept any name that starts with the currently expected type */
     406           0 :                 if (startswith(e + 3, "devtmpfs"))
     407           0 :                         return true;
     408             :         }
     409             : 
     410           0 :         return false;
     411             : }
     412             : 
     413           5 : const char *mount_propagation_flags_to_string(unsigned long flags) {
     414             : 
     415           5 :         switch (flags & (MS_SHARED|MS_SLAVE|MS_PRIVATE)) {
     416           2 :         case 0:
     417           2 :                 return "";
     418           1 :         case MS_SHARED:
     419           1 :                 return "shared";
     420           1 :         case MS_SLAVE:
     421           1 :                 return "slave";
     422           1 :         case MS_PRIVATE:
     423           1 :                 return "private";
     424             :         }
     425             : 
     426           0 :         return NULL;
     427             : }
     428             : 
     429           7 : int mount_propagation_flags_from_string(const char *name, unsigned long *ret) {
     430             : 
     431           7 :         if (isempty(name))
     432           2 :                 *ret = 0;
     433           5 :         else if (streq(name, "shared"))
     434           1 :                 *ret = MS_SHARED;
     435           4 :         else if (streq(name, "slave"))
     436           1 :                 *ret = MS_SLAVE;
     437           3 :         else if (streq(name, "private"))
     438           1 :                 *ret = MS_PRIVATE;
     439             :         else
     440           2 :                 return -EINVAL;
     441           5 :         return 0;
     442             : }

Generated by: LCOV version 1.14