fd-util: Add fd_is_writable() to check if FD is opened for writing

This checks whether a file descriptor is valid and opened in a mode that
allows writing (O_WRONLY or O_RDWR). This is useful when we want to
verify that inherited FDs can actually be used for output operations
before dup'ing them.

The helper explicitly handles O_PATH file descriptors, which cannot be
used for I/O operations and thus are never writable.
This commit is contained in:
Chris Down
2025-11-17 11:05:09 +08:00
parent 3f0fc93219
commit 592c57e586
3 changed files with 39 additions and 0 deletions

View File

@@ -998,6 +998,21 @@ int fd_vet_accmode(int fd, int mode) {
return -EPROTOTYPE;
}
int fd_is_writable(int fd) {
int r;
assert(fd >= 0);
r = fd_vet_accmode(fd, O_WRONLY);
if (r >= 0)
return true;
if (IN_SET(r, -EPROTOTYPE, -EBADFD, -EISDIR))
return false;
return r;
}
int fd_verify_safe_flags_full(int fd, int extra_flags) {
int flags, unexpected_flags;

View File

@@ -152,6 +152,7 @@ int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd);
int fd_is_opath(int fd);
int fd_vet_accmode(int fd, int mode);
int fd_is_writable(int fd);
int fd_verify_safe_flags_full(int fd, int extra_flags);
static inline int fd_verify_safe_flags(int fd) {

View File

@@ -903,4 +903,27 @@ TEST(fd_vet_accmode) {
ASSERT_ERROR(fd_vet_accmode(fd_opath, O_RDWR), EBADFD);
}
TEST(fd_is_writable) {
_cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fd-writable.XXXXXX";
_cleanup_close_ int fd_ro = -EBADF, fd_wo = -EBADF, fd_rw = -EBADF, fd_path = -EBADF;
ASSERT_OK(fd_rw = mkostemp_safe(name));
ASSERT_OK_POSITIVE(fd_is_writable(fd_rw));
ASSERT_OK(fd_ro = open(name, O_RDONLY | O_CLOEXEC));
ASSERT_OK_ZERO(fd_is_writable(fd_ro));
ASSERT_OK(fd_wo = open(name, O_WRONLY | O_CLOEXEC));
ASSERT_OK_POSITIVE(fd_is_writable(fd_wo));
ASSERT_OK(fd_path = open(name, O_PATH | O_CLOEXEC));
ASSERT_OK_ZERO(fd_is_writable(fd_path));
ASSERT_SIGNAL(fd_is_writable(-1), SIGABRT);
safe_close(fd_ro);
ASSERT_ERROR(fd_is_writable(fd_ro), EBADF);
TAKE_FD(fd_ro);
}
DEFINE_TEST_MAIN(LOG_DEBUG);