]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fileio: introduce write_data_file_atomic_at() helper
authorLennart Poettering <lennart@amutable.com>
Fri, 27 Feb 2026 09:05:16 +0000 (10:05 +0100)
committerLennart Poettering <lennart@amutable.com>
Fri, 20 Mar 2026 10:53:33 +0000 (11:53 +0100)
This is very similar to write_string_file_atomic(), but is intentionally
kept separate (after long consideration). It focusses on arbitrary
struct iovec data, not just strings, and hence also doesn't do stdio at
all. It's hence a lot more low-level.

We might want to consider moving write_string_file*() on top of
write_data_file_atomic_at(), but for now don't.

src/basic/fileio.c
src/basic/fileio.h
src/test/test-fileio.c

index 90436f6ecf8203cbb031399a72d5a18292468b2f..66d06484dc98105d87d29c2c4b2d15a71932ffd2 100644 (file)
@@ -7,12 +7,15 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "chase.h"
 #include "errno-util.h"
 #include "extract-word.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
 #include "hexdecoct.h"
+#include "io-util.h"
+#include "iovec-util.h"
 #include "label.h"
 #include "log.h"
 #include "mkdir.h"
@@ -1655,3 +1658,62 @@ int warn_file_is_world_accessible(const char *filename, struct stat *st, const c
                             filename, st->st_mode & 07777);
         return 0;
 }
+
+int write_data_file_atomic_at(
+                int dir_fd,
+                const char *path,
+                const struct iovec *iovec,
+                WriteDataFileFlags flags) {
+
+        int r;
+
+        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
+
+        /* This is a cousin of write_string_file_atomic(), but operates with arbitrary struct iovec binary
+         * data (rather than strings), works without FILE* streams, and does direct syscalls instead. */
+
+        _cleanup_free_ char *dn = NULL, *fn = NULL;
+        r = path_split_prefix_filename(path, &dn, &fn);
+        if (IN_SET(r, -EADDRNOTAVAIL, O_DIRECTORY))
+                return -EISDIR; /* path refers to "." or "/" (which are dirs, which we cannot write), or is suffixed with "/" */
+        if (r < 0)
+                return r;
+
+        _cleanup_close_ int mfd = -EBADF;
+        if (dn) {
+                /* If there's a directory component, readjust our position */
+                r = chaseat(dir_fd,
+                            dn,
+                            FLAGS_SET(flags, WRITE_DATA_FILE_MKDIR_0755) ? CHASE_MKDIR_0755 : 0,
+                            /* ret_path= */ NULL,
+                            &mfd);
+                if (r < 0)
+                        return r;
+
+                dir_fd = mfd;
+        }
+
+        _cleanup_free_ char *t = NULL;
+        _cleanup_close_ int fd = open_tmpfile_linkable_at(dir_fd, fn, O_WRONLY|O_CLOEXEC, &t);
+        if (fd < 0)
+                return fd;
+
+        CLEANUP_TMPFILE_AT(dir_fd, t);
+
+        if (iovec_is_set(iovec)) {
+                r = loop_write(fd, iovec->iov_base, iovec->iov_len);
+                if (r < 0)
+                        return r;
+        }
+
+        r = fchmod_umask(fd, 0644);
+        if (r < 0)
+                return r;
+
+        r = link_tmpfile_at(fd, dir_fd, t, fn, LINK_TMPFILE_REPLACE);
+        if (r < 0)
+                return r;
+
+        t = mfree(t); /* disarm CLEANUP_TMPFILE_AT */
+        return 0;
+}
index 578c16c0ee394bde2ff5219cea914beb2cc3f126..3e2372c4dddbc7b3446e7731304c431b12190c23 100644 (file)
@@ -163,3 +163,9 @@ int safe_fgetc(FILE *f, char *ret);
 int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line);
 
 int fopen_mode_to_flags(const char *mode);
+
+typedef enum WriteDataFileFlags {
+        WRITE_DATA_FILE_MKDIR_0755 = 1 << 0,
+} WriteDataFileFlags;
+
+int write_data_file_atomic_at(int dir_fd, const char *path, const struct iovec *iovec, WriteDataFileFlags flags);
index 38d92299467a75cd23ea99d86595319ab162e343..575e2c52ed7df5e25ed3377685fb55c9bc0b1496 100644 (file)
@@ -9,6 +9,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
+#include "iovec-util.h"
 #include "memfd-util.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -695,4 +696,55 @@ TEST(fdopen_independent) {
         f = safe_fclose(f);
 }
 
+TEST(write_data_file_atomic_at) {
+        struct iovec a = IOVEC_MAKE_STRING("hallo");
+        ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, "/tmp/wdfa", &a, /* flags= */ 0));
+
+        _cleanup_(iovec_done) struct iovec ra = {};
+        ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len));
+        ASSERT_EQ(iovec_memcmp(&a, &ra), 0);
+        ASSERT_OK_ERRNO(unlink("/tmp/wdfa"));
+
+        ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/wdfa", &a, /* flags= */ 0));
+        iovec_done(&ra);
+        ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len));
+        ASSERT_EQ(iovec_memcmp(&a, &ra), 0);
+        ASSERT_OK_ERRNO(unlink("/tmp/wdfa"));
+
+        ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, NULL, &a, /* flags= */ 0), EINVAL);
+        ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "", &a, /* flags= */ 0), EINVAL);
+        ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/", &a, /* flags= */ 0), EISDIR);
+        ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, ".", &a, /* flags= */ 0), EISDIR);
+        ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/tmp/", &a, /* flags= */ 0), EISDIR);
+
+        _cleanup_free_ char *cwd = NULL;
+        ASSERT_OK(safe_getcwd(&cwd));
+        ASSERT_OK_ERRNO(chdir("/tmp"));
+
+        ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, "wdfa", &a, /* flags= */ 0));
+        iovec_done(&ra);
+        ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len));
+        ASSERT_EQ(iovec_memcmp(&a, &ra), 0);
+        ASSERT_OK_ERRNO(unlink("/tmp/wdfa"));
+
+        ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/wdfa", &a, /* flags= */ 0));
+        iovec_done(&ra);
+        ASSERT_OK(read_full_file("/tmp/wdfa", (char**) &ra.iov_base, &ra.iov_len));
+        ASSERT_EQ(iovec_memcmp(&a, &ra), 0);
+        ASSERT_OK_ERRNO(unlink("/tmp/wdfa"));
+
+        ASSERT_OK_ERRNO(chdir(cwd));
+
+        ASSERT_ERROR(write_data_file_atomic_at(XAT_FDROOT, "tmp/zzz/wdfa", &a, /* flags= */ 0), ENOENT);
+        ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, "tmp/zzz/wdfa", &a, WRITE_DATA_FILE_MKDIR_0755));
+        iovec_done(&ra);
+        ASSERT_OK(read_full_file("/tmp/zzz/wdfa", (char**) &ra.iov_base, &ra.iov_len));
+        ASSERT_EQ(iovec_memcmp(&a, &ra), 0);
+        ASSERT_OK_ERRNO(unlink("/tmp/zzz/wdfa"));
+
+        ASSERT_ERROR(write_data_file_atomic_at(AT_FDCWD, "/tmp/zzz", &a, /* flags= */ 0), EEXIST);
+
+        ASSERT_OK_ERRNO(rmdir("/tmp/zzz"));
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);