From: Lennart Poettering Date: Sat, 21 Dec 2024 11:54:53 +0000 (+0100) Subject: fs-util: add XO_REGULAR flag for xopenat() X-Git-Tag: v258-rc1~1688^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dffafa47ae77e279392d91aef9f676718596e574;p=thirdparty%2Fsystemd.git fs-util: add XO_REGULAR flag for xopenat() If this flag is set we guarantee that the fd returned refers to a regular file. If the file exists and is not one, fails. --- diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 982c3bd5c3e..3b3fa1811eb 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1123,6 +1123,9 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + /* An inode cannot be both a directory and a regular file at the same time. */ + assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR))); + /* This is like openat(), but has a few tricks up its sleeves, extending behaviour: * * • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately @@ -1134,6 +1137,8 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ * * • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available. * + * • if XO_REGULAR is specified will return an error if inode is not a regular file. + * * • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files. */ @@ -1142,6 +1147,13 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ if (isempty(path)) { assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL)); + + if (FLAGS_SET(xopen_flags, XO_REGULAR)) { + r = fd_verify_regular(dir_fd); + if (r < 0) + return r; + } + return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW); } @@ -1171,10 +1183,66 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ open_flags &= ~(O_EXCL|O_CREAT); } - fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file); - if (fd < 0) { - r = fd; - goto error; + if (FLAGS_SET(xopen_flags, XO_REGULAR)) { + /* Guarantee we return a regular fd only, and don't open the file unless we verified it + * first */ + + if (FLAGS_SET(open_flags, O_PATH)) { + fd = openat(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = -errno; + goto error; + } + + r = fd_verify_regular(fd); + if (r < 0) + goto error; + + } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) { + /* In O_EXCL mode we can just create the thing, everything is dealt with for us */ + fd = openat(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = -errno; + goto error; + } + + made_file = true; + } else { + /* Otherwise pin the inode first via O_PATH */ + _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW)); + if (inode_fd < 0) { + if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) { + r = -errno; + goto error; + } + + /* Doesn't exist yet, then try to create it */ + fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode); + if (fd < 0) { + r = -errno; + goto error; + } + + made_file = true; + } else { + /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */ + r = fd_verify_regular(inode_fd); + if (r < 0) + goto error; + + fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT)); + if (fd < 0) { + r = fd; + goto error; + } + } + } + } else { + fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file); + if (fd < 0) { + r = fd; + goto error; + } } if (call_label_ops_post) { diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 028c86a3b2e..5ee7417eeb4 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -134,9 +134,10 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size); int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path); typedef enum XOpenFlags { - XO_LABEL = 1 << 0, - XO_SUBVOLUME = 1 << 1, - XO_NOCOW = 1 << 2, + XO_LABEL = 1 << 0, /* When creating: relabel */ + XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */ + XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */ + XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */ } XOpenFlags; int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 55202240d3f..3becf8f9ab7 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -713,6 +713,34 @@ TEST(xopenat_full) { assert_se((fd2 = xopenat_full(fd, "", O_RDWR|O_CLOEXEC, 0, 0644)) >= 0); } +TEST(xopenat_regular) { + + _cleanup_close_ int fd = -EBADF; + + assert_se(xopenat_full(AT_FDCWD, "/dev/null", O_RDWR|O_CLOEXEC, XO_REGULAR, 0) == -EBADFD); + assert_se(xopenat_full(AT_FDCWD, "/proc", O_RDONLY|O_CLOEXEC, XO_REGULAR, 0) == -EISDIR); + assert_se(xopenat_full(AT_FDCWD, "/proc/self", O_RDONLY|O_CLOEXEC, XO_REGULAR, 0) == -EISDIR); + assert_se(xopenat_full(AT_FDCWD, "/proc/self", O_RDONLY|O_CLOEXEC|O_NOFOLLOW, XO_REGULAR, 0) == -ELOOP); + + fd = xopenat_full(AT_FDCWD, "/proc/mounts", O_RDONLY|O_CLOEXEC, XO_REGULAR, 0); + assert_se(fd >= 0); + fd = safe_close(fd); + + assert_se(xopenat_full(AT_FDCWD, "/proc/mounts", O_RDONLY|O_CLOEXEC|O_NOFOLLOW, XO_REGULAR, 0) == -ELOOP); + + fd = xopenat_full(AT_FDCWD, "/proc/mounts", O_RDONLY|O_CLOEXEC|O_PATH, XO_REGULAR, 0); + assert_se(fd >= 0); + fd = safe_close(fd); + + fd = xopenat_full(AT_FDCWD, "/tmp/xopenat-regular-test", O_RDWR|O_CLOEXEC|O_CREAT, XO_REGULAR, 0600); + assert_se(fd >= 0); + fd = safe_close(fd); + + assert_se(xopenat_full(AT_FDCWD, "/tmp/xopenat-regular-test", O_RDWR|O_CLOEXEC|O_CREAT|O_EXCL, XO_REGULAR, 0600) == -EEXIST); + + assert_se(unlink("/tmp/xopenat-regular-test") >= 0); +} + TEST(xopenat_lock_full) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF;