summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2015-04-23 13:23:03 +0200
committerLennart Poettering <lennart@poettering.net>2015-04-23 13:40:54 +0200
commit3f72b427b44f39a1aec6806dad6f6b57103ae9ed (patch)
tree010e80281bcff5910756cc00b1bdc115b569070d
parent47d36b7c851f9145def609e4f9d8feb6d3d33740 (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.c135
-rw-r--r--src/test/test-path-util.c9
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);