]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fs-util: add XO_REGULAR flag for xopenat()
authorLennart Poettering <lennart@poettering.net>
Sat, 21 Dec 2024 11:54:53 +0000 (12:54 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 6 Jan 2025 22:20:09 +0000 (23:20 +0100)
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.

src/basic/fs-util.c
src/basic/fs-util.h
src/test/test-fs-util.c

index 982c3bd5c3e33d85cd7d5fc1dbfd73b9741c2a22..3b3fa1811eb2800f57fd0211ff7a29e4a42eda5c 100644 (file)
@@ -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) {
index 028c86a3b2e8dad3c374cfe26d85e4c27a71dad9..5ee7417eeb4488735957a3dc831aecbe347eb5b3 100644 (file)
@@ -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);
index 55202240d3fc058514d691152b7f5eab6338471e..3becf8f9ab7fa51b774ac8290d7e331fcc1aae40 100644 (file)
@@ -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;