diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index ec33a61588..01a45e4384 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -780,6 +780,37 @@ int fd_reopen(int fd, int flags) { return new_fd; } +int fd_reopen_condition( + int fd, + int flags, + int mask, + int *ret_new_fd) { + + int r, new_fd; + + assert(fd >= 0); + + /* Invokes fd_reopen(fd, flags), but only if the existing F_GETFL flags don't match the specified + * flags (masked by the specified mask). This is useful for converting O_PATH fds into real fds if + * needed, but only then. */ + + r = fcntl(fd, F_GETFL); + if (r < 0) + return -errno; + + if ((r & mask) == (flags & mask)) { + *ret_new_fd = -1; + return fd; + } + + new_fd = fd_reopen(fd, flags); + if (new_fd < 0) + return new_fd; + + *ret_new_fd = new_fd; + return new_fd; +} + int read_nr_open(void) { _cleanup_free_ char *nr_open = NULL; int r; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 29c7d86f27..fbaa458613 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -108,6 +108,7 @@ static inline int make_null_stdio(void) { }) int fd_reopen(int fd, int flags); +int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd); int read_nr_open(void); int fd_get_diskseq(int fd, uint64_t *ret); diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 72fad160ed..eb9f54306f 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -43,23 +43,7 @@ static int fd_get_devnum(int fd, BlockDeviceLookupFlag flags, dev_t *ret) { /* If major(st.st_dev) is zero, this might mean we are backed by btrfs, which needs special * handing, to get the backing device node. */ - r = fcntl(fd, F_GETFL); - if (r < 0) - return -errno; - - if (FLAGS_SET(r, O_PATH)) { - _cleanup_close_ int regfd = -1; - - /* The fstat() above we can execute on an O_PATH fd. But the btrfs ioctl we cannot. - * Hence acquire a "real" fd first, without the O_PATH flag. */ - - regfd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (regfd < 0) - return regfd; - - r = btrfs_get_block_device_fd(regfd, &devnum); - } else - r = btrfs_get_block_device_fd(fd, &devnum); + r = btrfs_get_block_device_fd(fd, &devnum); if (r == -ENOTTY) /* not btrfs */ return -ENOTBLK; if (r < 0) @@ -288,21 +272,7 @@ int get_block_device_fd(int fd, dev_t *ret) { return 1; } - r = fcntl(fd, F_GETFL); - if (r < 0) - return -errno; - if (FLAGS_SET(r, O_PATH) && (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode))) { - _cleanup_close_ int real_fd = -1; - - /* The fstat() above we can execute on an O_PATH fd. But the btrfs ioctl we cannot. Hence - * acquire a "real" fd first, without the O_PATH flag. */ - - real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC); - if (real_fd < 0) - return real_fd; - r = btrfs_get_block_device_fd(real_fd, ret); - } else - r = btrfs_get_block_device_fd(fd, ret); + r = btrfs_get_block_device_fd(fd, ret); if (r > 0) return 1; if (r != -ENOTTY) /* not btrfs */ diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 4574a7899e..ba02de17f8 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -107,19 +107,11 @@ int btrfs_subvol_make_fd(int fd, const char *subvolume) { if (r < 0) return r; - r = fcntl(fd, F_GETFL); - if (r < 0) - return -errno; - if (FLAGS_SET(r, O_PATH)) { - /* An O_PATH fd was specified, let's convert here to a proper one, as btrfs ioctl's can't deal with - * O_PATH. */ - - real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (real_fd < 0) - return real_fd; - - fd = real_fd; - } + /* If an O_PATH fd was specified, let's convert here to a proper one, as btrfs ioctl's can't deal + * with O_PATH. */ + fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY, O_PATH|O_DIRECTORY, &real_fd); + if (fd < 0) + return fd; strncpy(args.name, subvolume, sizeof(args.name)-1); @@ -255,12 +247,17 @@ int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offs int btrfs_get_block_device_fd(int fd, dev_t *dev) { struct btrfs_ioctl_fs_info_args fsi = {}; + _cleanup_close_ int regfd = -1; uint64_t id; int r; assert(fd >= 0); assert(dev); + fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, O_PATH, ®fd); + if (fd < 0) + return fd; + r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; @@ -1768,6 +1765,7 @@ int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) { int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermediary_qgroup) { _cleanup_free_ uint64_t *qgroups = NULL; + _cleanup_close_ int real_fd = -1; uint64_t parent_subvol; bool changed = false; int n = 0, r; @@ -1811,6 +1809,11 @@ int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermed * qgroup that then includes all its own child subvolumes. */ + /* Turn this into a proper fd, if it is currently O_PATH */ + fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC, O_PATH, &real_fd); + if (fd < 0) + return fd; + if (subvol_id == 0) { r = btrfs_is_subvol_fd(fd); if (r < 0) diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 2b85ceab82..df6ca13785 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -483,6 +483,53 @@ TEST(fd_reopen) { fd1 = -1; } +TEST(fd_reopen_condition) { + _cleanup_close_ int fd1 = -1, fd3 = -1; + int fd2, fl; + + /* Open without O_PATH */ + fd1 = open("/usr/", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + assert_se(fd1 >= 0); + + fl = fcntl(fd1, F_GETFL); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(!FLAGS_SET(fl, O_PATH)); + + fd2 = fd_reopen_condition(fd1, O_DIRECTORY, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 == fd1); + assert_se(fd3 < 0); + + /* Switch on O_PATH */ + fd2 = fd_reopen_condition(fd1, O_DIRECTORY|O_PATH, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 != fd1); + assert_se(fd3 == fd2); + + fl = fcntl(fd2, F_GETFL); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(FLAGS_SET(fl, O_PATH)); + + close_and_replace(fd1, fd3); + + fd2 = fd_reopen_condition(fd1, O_DIRECTORY|O_PATH, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 == fd1); + assert_se(fd3 < 0); + + /* Switch off O_PATH again */ + fd2 = fd_reopen_condition(fd1, O_DIRECTORY, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 != fd1); + assert_se(fd3 == fd2); + + fl = fcntl(fd2, F_GETFL); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(!FLAGS_SET(fl, O_PATH)); + + close_and_replace(fd1, fd3); + + fd2 = fd_reopen_condition(fd1, O_DIRECTORY, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 == fd1); + assert_se(fd3 < 0); +} + TEST(take_fd) { _cleanup_close_ int fd1 = -1, fd2 = -1; int array[2] = { -1, -1 }, i = 0;