]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fs-util: add openat_report_new() wrapper around openat()
authorLennart Poettering <lennart@poettering.net>
Mon, 21 Mar 2022 17:11:26 +0000 (18:11 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 21 Mar 2022 17:23:44 +0000 (18:23 +0100)
This is a wrapper around openat(). It works mostly the same, except for
one thing: it race-freely reports whether we just created the indicated
file in case O_CREAT is passed without O_EXCL.

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

index 11039fd75d587ebc60c3ab2891319e61fa3ed3d1..14aea54e260fcc17211bf4c8651f6860fab5c587 100644 (file)
@@ -1083,3 +1083,42 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
 
         return TAKE_FD(fd);
 }
+
+int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) {
+        unsigned attempts = 7;
+
+        /* Just like openat(), but adds one thing: optionally returns whether we created the file anew or if
+         * it already existed before. This is only relevant of O_CREAT is set without O_EXCL, and thus will
+         * shortcut to openat() otherwise */
+
+        if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL) || !ret_newly_created)
+                return RET_NERRNO(openat(dirfd, pathname, flags, mode));
+
+        for (;;) {
+                int fd;
+
+                /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */
+                fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode);
+                if (fd >= 0) {
+                        *ret_newly_created = false;
+                        return fd;
+                }
+                if (errno != ENOENT)
+                        return -errno;
+
+                /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL. */
+                fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL, mode);
+                if (fd >= 0) {
+                        *ret_newly_created = true;
+                        return fd;
+                }
+                if (errno != EEXIST)
+                        return -errno;
+
+                /* Hmm, so now we got EEXIST? So it apparently exists now? If so, let's try to open again
+                 * without the two flags. But let's not spin forever, hnce put a limit on things */
+
+                if (--attempts == 0) /* Give up eventually, somebody is playing with us */
+                        return -EEXIST;
+        }
+}
index 0bbb3f629814b5e90e27180eaedfcaf035938b4c..e48cf6800fac1796ba7d403a592adb2c1fe4d603 100644 (file)
@@ -110,3 +110,5 @@ 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);
 
 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);
index 67b69969070d6adebb65f1f4f3c85fc04ec7aaf0..c93723ad293a0ebe8ea2e7f7ee1a56e6912e6894 100644 (file)
@@ -970,6 +970,56 @@ TEST(open_mkdir_at) {
         assert_se(subsubdir_fd >= 0);
 }
 
+TEST(openat_report_new) {
+        _cleanup_free_ char *j = NULL;
+        _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+        _cleanup_close_ int fd = -1;
+        bool b;
+
+        assert_se(mkdtemp_malloc(NULL, &d) >= 0);
+
+        j = path_join(d, "test");
+        assert_se(j);
+
+        fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b);
+        assert_se(fd >= 0);
+        fd = safe_close(fd);
+        assert_se(b);
+
+        fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b);
+        assert_se(fd >= 0);
+        fd = safe_close(fd);
+        assert_se(!b);
+
+        fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b);
+        assert_se(fd >= 0);
+        fd = safe_close(fd);
+        assert_se(!b);
+
+        assert_se(unlink(j) >= 0);
+
+        fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b);
+        assert_se(fd >= 0);
+        fd = safe_close(fd);
+        assert_se(b);
+
+        fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b);
+        assert_se(fd >= 0);
+        fd = safe_close(fd);
+        assert_se(!b);
+
+        assert_se(unlink(j) >= 0);
+
+        fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, NULL);
+        assert_se(fd >= 0);
+        fd = safe_close(fd);
+
+        fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b);
+        assert_se(fd >= 0);
+        fd = safe_close(fd);
+        assert_se(!b);
+}
+
 static int intro(void) {
         arg_test_dir = saved_argv[1];
         return EXIT_SUCCESS;