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/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;