mountpoint-util: make fd_is_mountpoint() work reasonably with AT_EMPTYPATH

Inspired by: https://github.com/systemd/systemd/pull/24141

Calling fd_is_mountpoint() with AT_EMPTYPATH and an empty filename can
only work if we have new statx() available. If we do not, we can still
make things work for directories, but not for other inodes (since there
we cannot query information about the parent inode to compare things.)

Hence, let's handle and test this explicitly, to support this to the
level this is possible.
This commit is contained in:
Lennart Poettering 2022-08-02 13:58:09 +02:00 committed by Daan De Meyer
parent ff0a5070d4
commit 71c943dc40
2 changed files with 41 additions and 8 deletions

View file

@ -182,12 +182,18 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
int r;
assert(fd >= 0);
assert(filename);
assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0);
assert((flags & ~AT_SYMLINK_FOLLOW) == 0);
/* Insist that the specified filename is actually a filename, and not a path, i.e. some inode further
* up or down the tree then immediately below the specified directory fd. */
if (!filename_possibly_with_slash_suffix(filename))
if (!filename) {
/* If the file name is specified as NULL we'll see if the specified 'fd' is a mount
* point. That's only supported if the kernel supports statx(), or if the inode specified via
* 'fd' refers to a directory. Otherwise, we'll have to fail (ENOTDIR), because we have no
* kernel API to query the information we need. */
flags |= AT_EMPTY_PATH;
filename = "";
} else if (!filename_possibly_with_slash_suffix(filename))
/* Insist that the specified filename is actually a filename, and not a path, i.e. some inode further
* up or down the tree then immediately below the specified directory fd. */
return -EINVAL;
/* First we will try statx()' STATX_ATTR_MOUNT_ROOT attribute, which is our ideal API, available
@ -234,7 +240,10 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
nosupp = true;
}
r = name_to_handle_at_loop(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH);
if (isempty(filename))
r = name_to_handle_at_loop(fd, "..", &h_parent, &mount_id_parent, 0); /* can't work for non-directories 😢 */
else
r = name_to_handle_at_loop(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH);
if (r < 0) {
if (is_name_to_handle_at_fatal_error(r))
return r;
@ -271,7 +280,10 @@ fallback_fdinfo:
if (r < 0)
return r;
r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
if (isempty(filename))
r = fd_fdinfo_mnt_id(fd, "..", 0, &mount_id_parent); /* can't work for non-directories 😢 */
else
r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
if (r < 0)
return r;
@ -295,7 +307,11 @@ fallback_fstat:
if (S_ISLNK(a.st_mode)) /* Symlinks are never mount points */
return false;
if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0)
if (isempty(filename))
r = fstatat(fd, "..", &b, 0);
else
r = fstatat(fd, "", &b, AT_EMPTY_PATH);
if (r < 0)
return -errno;
/* A directory with same device and inode as its parent? Must be the root directory */

View file

@ -265,6 +265,7 @@ TEST(path_is_mount_point) {
TEST(fd_is_mount_point) {
_cleanup_close_ int fd = -1;
int r;
fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY);
assert_se(fd >= 0);
@ -290,6 +291,22 @@ TEST(fd_is_mount_point) {
* the system is borked. Let's allow for it to be missing though. */
assert_se(IN_SET(fd_is_mount_point(fd, "root", 0), -ENOENT, 0));
assert_se(IN_SET(fd_is_mount_point(fd, "root/", 0), -ENOENT, 0));
safe_close(fd);
fd = open("/proc", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY);
assert_se(fd >= 0);
assert_se(fd_is_mount_point(fd, NULL, 0) > 0);
assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL);
assert_se(fd_is_mount_point(fd, "version", 0) == 0);
safe_close(fd);
fd = open("/proc/version", O_RDONLY|O_CLOEXEC|O_NOCTTY);
assert_se(fd >= 0);
r = fd_is_mount_point(fd, NULL, 0);
assert_se(IN_SET(r, 0, -ENOTDIR)); /* on old kernels we can't determine if regular files are mount points if we have no directory fd */
assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL);
}
static int intro(void) {