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.
This commit is contained in:
Lennart Poettering 2015-04-23 13:23:03 +02:00
parent 47d36b7c85
commit 3f72b427b4
2 changed files with 117 additions and 29 deletions

View file

@ -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. */
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;
}
fallback:
r = fstatat(fd, "", &a, AT_EMPTY_PATH);
/* 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;
/* 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_fdinfo:
r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id);
if (r == -EOPNOTSUPP)
goto fallback_fstat;
if (r < 0)
return r;
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;
r = fstatat(fd, "..", &b, 0);
if (r < 0)
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) {

View file

@ -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);