]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fs-util: Add xopenat()
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 14 Mar 2023 08:55:04 +0000 (09:55 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 14 Mar 2023 08:55:04 +0000 (09:55 +0100)
xopenat() will create directories if O_DIRECTORY and O_CREAT are
specified. Note that this is not an atomic operation.

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

index a895f4f2df2eb3c851d90704b6afab8acc4d6d8b..5007a829ea93f2af248297aca20693eb3e2964f3 100644 (file)
@@ -993,7 +993,6 @@ int parse_cifs_service(
 int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
         _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
         _cleanup_free_ char *fname = NULL;
-        bool made;
         int r;
 
         /* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
@@ -1033,33 +1032,11 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
                 path = fname;
         }
 
-        r = RET_NERRNO(mkdirat(dirfd, path, mode));
-        if (r == -EEXIST) {
-                if (FLAGS_SET(flags, O_EXCL))
-                        return -EEXIST;
-
-                made = false;
-        } else if (r < 0)
-                return r;
-        else
-                made = true;
-
-        fd = RET_NERRNO(openat(dirfd, path, (flags & ~O_EXCL)|O_DIRECTORY|O_NOFOLLOW));
-        if (fd < 0) {
-                if (fd == -ENOENT)  /* We got ENOENT? then someone else immediately removed it after we
-                                     * created it. In that case let's return immediately without unlinking
-                                     * anything, because there simply isn't anything to unlink anymore. */
-                        return -ENOENT;
-                if (fd == -ELOOP)   /* is a symlink? exists already → created by someone else, don't unlink */
-                        return -EEXIST;
-                if (fd == -ENOTDIR) /* not a directory? exists already → created by someone else, don't unlink */
-                        return -EEXIST;
-
-                if (made)
-                        (void) unlinkat(dirfd, path, AT_REMOVEDIR);
-
+        fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, mode);
+        if (IN_SET(fd, -ELOOP, -ENOTDIR))
+                return -EEXIST;
+        if (fd < 0)
                 return fd;
-        }
 
         return TAKE_FD(fd);
 }
@@ -1110,3 +1087,45 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b
                         return -EEXIST;
         }
 }
+
+int xopenat(int dir_fd, const char *path, int flags, mode_t mode) {
+        _cleanup_close_ int fd = -EBADF;
+        bool made = false;
+        int r;
+
+        if (FLAGS_SET(flags, O_DIRECTORY|O_CREAT)) {
+                r = RET_NERRNO(mkdirat(dir_fd, path, mode));
+                if (r == -EEXIST) {
+                        if (FLAGS_SET(flags, O_EXCL))
+                                return -EEXIST;
+
+                        made = false;
+                } else if (r < 0)
+                        return r;
+                else
+                        made = true;
+
+                flags &= ~(O_EXCL|O_CREAT);
+        }
+
+        fd = RET_NERRNO(openat(dir_fd, path, flags, mode));
+        if (fd < 0) {
+                if (IN_SET(fd,
+                           /* We got ENOENT? then someone else immediately removed it after we
+                           * created it. In that case let's return immediately without unlinking
+                           * anything, because there simply isn't anything to unlink anymore. */
+                           -ENOENT,
+                           /* is a symlink? exists already → created by someone else, don't unlink */
+                           -ELOOP,
+                           /* not a directory? exists already → created by someone else, don't unlink */
+                           -ENOTDIR))
+                        return fd;
+
+                if (made)
+                        (void) unlinkat(dir_fd, path, AT_REMOVEDIR);
+
+                return fd;
+        }
+
+        return TAKE_FD(fd);
+}
index 932d003f199356a7038d7087feac8d8031ae3d3b..3db9df2224352c4858cdde7f154ef8776e067e9d 100644 (file)
@@ -129,3 +129,5 @@ int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char
 int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode);
 
 int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created);
+
+int xopenat(int dir_fd, const char *path, int flags, mode_t mode);
index c009424a2bcc48dd54893284ea1327033f10d62f..065ad3c6e7f09b1631b4174c63976a90b40a95bc 100644 (file)
@@ -1155,6 +1155,31 @@ TEST(openat_report_new) {
         assert_se(b);
 }
 
+TEST(xopenat) {
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF;
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+
+        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+
+        /* Test that xopenat() creates directories if O_DIRECTORY is specified. */
+
+        assert_se((fd = xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0755)) >= 0);
+        assert_se((fd_verify_directory(fd) >= 0));
+        fd = safe_close(fd);
+
+        assert_se(xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0755) == -EEXIST);
+
+        assert_se((fd = xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_CLOEXEC, 0755)) >= 0);
+        assert_se((fd_verify_directory(fd) >= 0));
+        fd = safe_close(fd);
+
+        /* Test that xopenat() creates regular files if O_DIRECTORY is not specified. */
+
+        assert_se((fd = xopenat(tfd, "def", O_CREAT|O_EXCL|O_CLOEXEC, 0644)) >= 0);
+        assert_se(fd_verify_regular(fd) >= 0);
+        fd = safe_close(fd);
+}
+
 static int intro(void) {
         arg_test_dir = saved_argv[1];
         return EXIT_SUCCESS;