From 67387626884afec7dbb64cb78a39a3676b7ff663 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 27 Feb 2026 10:05:16 +0100 Subject: [PATCH 1/3] fileio: introduce write_data_file_atomic_at() helper 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 | 62 ++++++++++++++++++++++++++++++++++++++++++ src/basic/fileio.h | 6 ++++ src/test/test-fileio.c | 52 +++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 90436f6ecf..66d06484dc 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -7,12 +7,15 @@ #include #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; +} diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 578c16c0ee..3e2372c4dd 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -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); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 38d9229946..575e2c52ed 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -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); From 56356c9deca6bc2142b8f69c94aea91ca47d81e9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Feb 2026 11:24:21 +0100 Subject: [PATCH 2/3] udev: tag DMI id device with "systemd", so that we can order units after it For various usecases it is useful to read relevant data from the DMI udev device, but this means we need a way to wait for it for this to be probed to be race-free. Hence tag it with "systemd", so that sys-devices-virtual-dmi-id.device can be used as synchronization point. --- rules.d/60-dmi-id.rules | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rules.d/60-dmi-id.rules b/rules.d/60-dmi-id.rules index 10b1fe000c..ecea74ec60 100644 --- a/rules.d/60-dmi-id.rules +++ b/rules.d/60-dmi-id.rules @@ -2,24 +2,28 @@ ACTION=="remove", GOTO="dmi_end" SUBSYSTEM!="dmi", GOTO="dmi_end" +KERNEL!="id", GOTO="dmi_end" ENV{ID_SYS_VENDOR_IS_RUBBISH}!="1", ENV{ID_VENDOR}="$attr{sys_vendor}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="", ENV{ID_PRODUCT_NAME_IS_RUBBISH}!="1", ENV{ID_MODEL}="$attr{product_name}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="product_name", ENV{ID_MODEL}="$attr{product_name}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="product_version", ENV{ID_MODEL}="$attr{product_version}" -# fallback to board information +# Fallback to board information ENV{ID_VENDOR}=="", ENV{ID_VENDOR}="$attr{board_vendor}" ENV{ID_MODEL}=="", ENV{ID_MODEL}="$attr{board_name}" -# stock keeping unit +# Stock keeping unit ENV{ID_PRODUCT_SKU_IS_RUBBISH}!="1", ENV{ID_SKU}="$attr{product_sku}" -# hardware version +# Hardware version ENV{ID_PRODUCT_VERSION_IS_RUBBISH}!="1", ENV{ID_HARDWARE_VERSION}="$attr{product_version}" ENV{ID_HARDWARE_VERSION}=="", ENV{ID_BOARD_VERSION_IS_RUBBISH}!="1", ENV{ID_HARDWARE_VERSION}="$attr{board_version}" -# chassis asset tag +# Chassis asset tag ENV{MODALIAS}!="", ATTR{chassis_asset_tag}!="", IMPORT{builtin}="hwdb '$attr{modalias}cat$attr{chassis_asset_tag}:'" ENV{ID_CHASSIS_ASSET_TAG_IS_RUBBISH}!="1", ENV{ID_CHASSIS_ASSET_TAG}="$attr{chassis_asset_tag}" +# Allow units to be ordered after the DMI device +TAG+="systemd" + LABEL="dmi_end" From 56f3ae9292742fff1cac6dbe4883a1715e56e874 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 26 Feb 2026 10:10:53 +0100 Subject: [PATCH 3/3] iovec-util: introduce IOVEC_MAKE_BYTE() helper --- src/basic/iovec-util.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index 0d1d4a7a94..00cbb89a77 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -24,6 +24,12 @@ struct iovec* iovec_make_string(struct iovec *iovec, const char *s); .iov_len = STRLEN(s), \ } +#define IOVEC_MAKE_BYTE(c) \ + (const struct iovec) { \ + .iov_base = (char*) ((const char[]) { c }), \ + .iov_len = 1, \ + } + void iovec_done_erase(struct iovec *iovec); char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);