diff options
author | Lennart Poettering <lennart@poettering.net> | 2015-04-23 13:23:03 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2015-04-23 13:40:54 +0200 |
commit | 3f72b427b44f39a1aec6806dad6f6b57103ae9ed (patch) | |
tree | 010e80281bcff5910756cc00b1bdc115b569070d | |
parent | 47d36b7c851f9145def609e4f9d8feb6d3d33740 (diff) |
path-util: make use of "mnt_id" field exported in /proc/self/fdinfo/<fd> to test for mount points
It's a very recent kernel addition, but certainly makes sense to
support.
-rw-r--r-- | src/shared/path-util.c | 135 | ||||
-rw-r--r-- | src/test/test-path-util.c | 9 |
2 files changed, 116 insertions, 28 deletions
diff --git a/src/shared/path-util.c b/src/shared/path-util.c index a01475a614..925bb28c32 100644 --- a/src/shared/path-util.c +++ b/src/shared/path-util.c @@ -33,6 +33,7 @@ #include "strv.h" #include "path-util.h" #include "missing.h" +#include "fileio.h" bool path_is_absolute(const char *p) { return p[0] == '/'; @@ -470,25 +471,82 @@ char* path_join(const char *root, const char *path, const char *rest) { NULL); } +static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) { + char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; + _cleanup_free_ char *fdinfo = NULL; + _cleanup_close_ int subfd = -1; + char *p; + int r; + + if ((flags & AT_EMPTY_PATH) && isempty(filename)) + xsprintf(path, "/proc/self/fdinfo/%i", fd); + else { + subfd = openat(fd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH); + if (subfd < 0) + return -errno; + + xsprintf(path, "/proc/self/fdinfo/%i", subfd); + } + + r = read_full_file(path, &fdinfo, NULL); + if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */ + return -EOPNOTSUPP; + if (r < 0) + return -errno; + + p = startswith(fdinfo, "mnt_id:"); + if (!p) { + p = strstr(fdinfo, "\nmnt_id:"); + if (!p) /* The mnt_id field is a relatively new addition */ + return -EOPNOTSUPP; + + p += 8; + } + + p += strspn(p, WHITESPACE); + p[strcspn(p, WHITESPACE)] = 0; + + return safe_atoi(p, mnt_id); +} + int fd_is_mount_point(int fd) { union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT; int mount_id = -1, mount_id_parent = -1; - bool nosupp = false; + bool nosupp = false, check_st_dev = false; struct stat a, b; int r; assert(fd >= 0); - /* We are not actually interested in the file handles, but - * name_to_handle_at() also passes us the mount ID, hence use - * it but throw the handle away */ + /* First we will try the name_to_handle_at() syscall, which + * tells us the mount id and an opaque file "handle". It is + * not supported everywhere though (kernel compile-time + * option, not all file systems are hooked up). If it works + * the mount id is usually good enough to tell us whether + * something is a mount point. + * + * If that didn't work we will try to read the mount id from + * /proc/self/fdinfo/<fd>. This is almost as good as + * name_to_handle_at(), however, does not return the the + * opaque file handle. The opaque file handle is pretty useful + * to detect the root directory, which we should always + * consider a mount point. Hence we use this only as + * fallback. Exporting the mnt_id in fdinfo is a pretty recent + * kernel addition. + * + * As last fallback we do traditional fstat() based st_dev + * comparisons. This is how things were traditionally done, + * but unionfs breaks breaks this since it exposes file + * systems with a variety of st_dev reported. Also, btrfs + * subvolumes have different st_dev, even though they aren't + * real mounts of their own. */ r = name_to_handle_at(fd, "", &h.handle, &mount_id, AT_EMPTY_PATH); if (r < 0) { if (errno == ENOSYS) /* This kernel does not support name_to_handle_at() - * fall back to the traditional stat() logic. */ - goto fallback; + * fall back to simpler logic. */ + goto fallback_fdinfo; else if (errno == EOPNOTSUPP) /* This kernel or file system does not support * name_to_handle_at(), hence let's see if the @@ -506,7 +564,7 @@ int fd_is_mount_point(int fd) { if (nosupp) /* Neither parent nor child do name_to_handle_at()? We have no choice but to fall back. */ - goto fallback; + goto fallback_fdinfo; else /* The parent can't do name_to_handle_at() but the * directory we are interested in can? @@ -514,32 +572,53 @@ int fd_is_mount_point(int fd) { return 1; } else return -errno; - } else if (nosupp) - /* The parent can do name_to_handle_at() but the - * directory we are interested in can't? If so, it - * must be a mount point. */ + } + + /* The parent can do name_to_handle_at() but the + * directory we are interested in can't? If so, it + * must be a mount point. */ + if (nosupp) return 1; - else { - /* If the file handle for the directory we are - * interested in and its parent are identical, we - * assume this is the root directory, which is a mount - * point. */ - - if (h.handle.handle_bytes == h_parent.handle.handle_bytes && - h.handle.handle_type == h_parent.handle.handle_type && - memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0) - return 1; - return mount_id != mount_id_parent; - } + /* If the file handle for the directory we are + * interested in and its parent are identical, we + * assume this is the root directory, which is a mount + * point. */ + + if (h.handle.handle_bytes == h_parent.handle.handle_bytes && + h.handle.handle_type == h_parent.handle.handle_type && + memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0) + return 1; + + return mount_id != mount_id_parent; -fallback: - r = fstatat(fd, "", &a, AT_EMPTY_PATH); +fallback_fdinfo: + r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id); + if (r == -EOPNOTSUPP) + goto fallback_fstat; if (r < 0) - return -errno; + return r; - r = fstatat(fd, "..", &b, 0); + r = fd_fdinfo_mnt_id(fd, "..", 0, &mount_id_parent); if (r < 0) + return r; + + if (mount_id != mount_id_parent) + return 1; + + /* Hmm, so, the mount ids are the same. This leaves one + * special case though for the root file system. For that, + * let's see if the parent directory has the same inode as we + * are interested in. Hence, let's also do fstat() checks now, + * too, but avoid the st_dev comparisons, since they aren't + * that useful on unionfs mounts. */ + check_st_dev = false; + +fallback_fstat: + if (fstatat(fd, "", &a, AT_EMPTY_PATH) < 0) + return -errno; + + if (fstatat(fd, "..", &b, 0) < 0) return -errno; /* A directory with same device and inode as its parent? Must @@ -548,7 +627,7 @@ fallback: a.st_ino == b.st_ino) return 1; - return a.st_dev != b.st_dev; + return check_st_dev && (a.st_dev != b.st_dev); } int path_is_mount_point(const char *t, bool allow_symlink) { diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 55d75ae7a1..e5b9c28bc0 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -36,6 +36,8 @@ } static void test_path(void) { + _cleanup_close_ int fd = -1; + test_path_compare("/goo", "/goo", 0); test_path_compare("/goo", "/goo", 0); test_path_compare("//goo", "/goo", 0); @@ -89,9 +91,16 @@ static void test_path(void) { assert_se(path_is_mount_point("/", true) > 0); assert_se(path_is_mount_point("/", false) > 0); + fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); + assert_se(fd >= 0); + assert_se(fd_is_mount_point(fd) > 0); + assert_se(path_is_mount_point("/proc", true) > 0); assert_se(path_is_mount_point("/proc", false) > 0); + assert_se(path_is_mount_point("/proc/1", true) == 0); + assert_se(path_is_mount_point("/proc/1", false) == 0); + assert_se(path_is_mount_point("/sys", true) > 0); assert_se(path_is_mount_point("/sys", false) > 0); |