From 04db1a6aecb55f34fb0558264baf0051eabbc7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 19 Mar 2025 16:18:27 +0100 Subject: [PATCH 1/5] update-done: adjust comments The man page was right, but the comment in the generated file was wrong. The timestamp is *not* the timestamp when the update is being done. While at it, say to what directory the message applies. This makes it easier for a casual reader to figure out what is happening. Also rename the function to better reflect what it does. Inspired by https://github.com/systemd/systemd/issues/36045. --- man/systemd-update-done.service.xml | 11 +++++------ src/update-done/update-done.c | 23 +++++++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/man/systemd-update-done.service.xml b/man/systemd-update-done.service.xml index 6b863ecff3a..fb228796885 100644 --- a/man/systemd-update-done.service.xml +++ b/man/systemd-update-done.service.xml @@ -37,12 +37,11 @@ /etc/ or /var/ on the following boot. - systemd-update-done.service updates the - file modification time (mtime) of the stamp files - /etc/.updated and - /var/.updated to the modification time of the - /usr/ directory, unless the stamp files are - already newer. + systemd-update-done.service updates the file modification time (mtime) stored + in and "on" the files /etc/.updated and /var/.updated to the + modification time of the /usr/ directory, unless the stamp files are already newer. + (The timestamp is stored as the mtime field on the file, but also in the file + to support filesystems that do not store full timestamp precision.) Services that shall run after offline upgrades of /usr/ should order themselves before diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index 9eb58311e92..15836971dfe 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -6,16 +6,12 @@ #include "alloc-util.h" #include "fileio.h" +#include "path-util.h" #include "selinux-util.h" #include "time-util.h" -#define MESSAGE \ - "# This file was created by systemd-update-done. Its only\n" \ - "# purpose is to hold a timestamp of the time this directory\n" \ - "# was updated. See man:systemd-update-done.service(8).\n" - -static int apply_timestamp(const char *path, struct timespec *ts) { - _cleanup_free_ char *message = NULL; +static int save_timestamp(const char *dir, struct timespec *ts) { + _cleanup_free_ char *message = NULL, *path = NULL; int r; /* @@ -23,9 +19,16 @@ static int apply_timestamp(const char *path, struct timespec *ts) { * to support filesystems which cannot store nanosecond-precision timestamps. */ + path = path_join(dir, ".updated"); + if (!path) + return log_oom(); + if (asprintf(&message, - MESSAGE + "# This file was created by systemd-update-done. The timestamp below is the\n" + "# modification time of /usr/ for which the most recent updates of %s have\n" + "# been applied. See man:systemd-update-done.service(8) for details.\n" "TIMESTAMP_NSEC=" NSEC_FMT "\n", + dir, timespec_load_nsec(ts)) < 0) return log_oom(); @@ -52,8 +55,8 @@ int main(int argc, char *argv[]) { if (r < 0) return EXIT_FAILURE; - r = apply_timestamp("/etc/.updated", &st.st_mtim); - q = apply_timestamp("/var/.updated", &st.st_mtim); + r = save_timestamp("/etc/", &st.st_mtim); + q = save_timestamp("/var/", &st.st_mtim); return r < 0 || q < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } From bca9a6e2be56fc81397d96be08d06b4afb0deb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 19 Mar 2025 16:28:50 +0100 Subject: [PATCH 2/5] update-done: split out run() --- src/update-done/update-done.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index 15836971dfe..a4289ddf2ad 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "fileio.h" +#include "main-func.h" #include "path-util.h" #include "selinux-util.h" #include "time-util.h" @@ -40,23 +41,23 @@ static int save_timestamp(const char *dir, struct timespec *ts) { return 0; } -int main(int argc, char *argv[]) { +static int run(int argc, char *argv[]) { struct stat st; - int r, q = 0; + int r; log_setup(); - if (stat("/usr", &st) < 0) { - log_error_errno(errno, "Failed to stat /usr: %m"); - return EXIT_FAILURE; - } + if (stat("/usr", &st) < 0) + return log_error_errno(errno, "Failed to stat /usr: %m"); r = mac_init(); if (r < 0) - return EXIT_FAILURE; + return r; - r = save_timestamp("/etc/", &st.st_mtim); - q = save_timestamp("/var/", &st.st_mtim); - - return r < 0 || q < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + r = 0; + RET_GATHER(r, save_timestamp("/etc/", &st.st_mtim)); + RET_GATHER(r, save_timestamp("/var/", &st.st_mtim)); + return r; } + +DEFINE_MAIN_FUNCTION(run); From 20f7f0a89177214298b7c70fffef2c512cd7a384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 19 Mar 2025 16:47:02 +0100 Subject: [PATCH 3/5] update-done: add basic argument parsing and --help We certainly want to reject calls with any args specified. Previously we would just silently ignore any args. --- man/systemd-update-done.service.xml | 14 ++++++- src/update-done/update-done.c | 61 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/man/systemd-update-done.service.xml b/man/systemd-update-done.service.xml index fb228796885..2db2e407b6f 100644 --- a/man/systemd-update-done.service.xml +++ b/man/systemd-update-done.service.xml @@ -3,7 +3,7 @@ - + systemd-update-done.service @@ -18,7 +18,7 @@ systemd-update-done.service systemd-update-done - Mark /etc/ and /var/ fully updated + Mark /etc/ and /var/ as fully updated @@ -63,6 +63,16 @@ reboot where the kernel switch is not specified anymore. + + Options + + The following options are understood: + + + + + + See Also diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index a4289ddf2ad..f9c8ec4f429 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include #include @@ -8,6 +9,7 @@ #include "fileio.h" #include "main-func.h" #include "path-util.h" +#include "pretty-print.h" #include "selinux-util.h" #include "time-util.h" @@ -41,10 +43,69 @@ static int save_timestamp(const char *dir, struct timespec *ts) { return 0; } +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-update-done", "8", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...]\n\n" + "%5$sMark /etc/ and /var/ as fully updated.%6$s\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + {}, + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + + return 1; +} + + static int run(int argc, char *argv[]) { struct stat st; int r; + r = parse_argv(argc, argv); + if (r <= 0) + return r; + log_setup(); if (stat("/usr", &st) < 0) From 2ff40c758ed6f5bf802748ffd71487d912a961e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 19 Mar 2025 17:05:20 +0100 Subject: [PATCH 4/5] update-done: add --root= arg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The idea is to use this when building an image to mark the image as not needing updates after the reboot. In general it is impossible to say if any of the early boot update services can be safely skipped, except when the creator of the image knows all the contents there and has made sure that all the updates have been processed. (This is in fact what happens in a typical package-based installation: the packages have scriptlets which implement the changes during or after the installation process.) With this patch, the image build process can do 'systemd-update-done --root=…' at the appropriate point to avoid triggering of ldconfig.service, systemd-hwdb-update.service, etc. I didn't write --image=, because it doesn't seem immediately useful. The approach with --root is most useful when we're building the image "offline", which means that we have a directory we're working on. --- man/systemd-update-done.service.xml | 9 +++++ src/update-done/update-done.c | 54 +++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/man/systemd-update-done.service.xml b/man/systemd-update-done.service.xml index 2db2e407b6f..d9d78262a14 100644 --- a/man/systemd-update-done.service.xml +++ b/man/systemd-update-done.service.xml @@ -69,6 +69,15 @@ The following options are understood: + + + Takes a directory path as an argument. The program + will operate on paths below the specified root directory. + + + + + diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index f9c8ec4f429..b30bb2dbf0b 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -6,15 +6,23 @@ #include #include "alloc-util.h" +#include "chase.h" +#include "fd-util.h" #include "fileio.h" #include "main-func.h" +#include "parse-argument.h" #include "path-util.h" #include "pretty-print.h" #include "selinux-util.h" #include "time-util.h" +static char *arg_root = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_root, freep); + static int save_timestamp(const char *dir, struct timespec *ts) { - _cleanup_free_ char *message = NULL, *path = NULL; + _cleanup_free_ char *message = NULL, *dirpath = NULL; + _cleanup_close_ int fd = -EBADF; int r; /* @@ -22,9 +30,12 @@ static int save_timestamp(const char *dir, struct timespec *ts) { * to support filesystems which cannot store nanosecond-precision timestamps. */ - path = path_join(dir, ".updated"); - if (!path) - return log_oom(); + fd = chase_and_open(dir, arg_root, + CHASE_PREFIX_ROOT | CHASE_WARN | CHASE_MUST_BE_DIRECTORY, + O_DIRECTORY | O_CLOEXEC, + &dirpath); + if (fd < 0) + return log_error_errno(fd, "Failed to open %s%s: %m", strempty(arg_root), dir); if (asprintf(&message, "# This file was created by systemd-update-done. The timestamp below is the\n" @@ -35,11 +46,16 @@ static int save_timestamp(const char *dir, struct timespec *ts) { timespec_load_nsec(ts)) < 0) return log_oom(); - r = write_string_file_full(AT_FDCWD, path, message, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL, ts, NULL); - if (r == -EROFS) - log_debug_errno(r, "Cannot create \"%s\", file system is read-only.", path); + r = write_string_file_full(fd, ".updated", message, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL, + ts, NULL); + if (r == -EROFS && !arg_root) + log_debug_errno(r, "Cannot create \"%s/.updated\", file system is read-only.", dirpath); else if (r < 0) - return log_error_errno(r, "Failed to write \"%s\": %m", path); + return log_error_errno(r, "Failed to write \"%s/.updated\": %m", dirpath); + else + log_debug("%s/.updated updated to TIMESTAMP_NSEC="NSEC_FMT, dirpath, timespec_load_nsec(ts)); + return 0; } @@ -55,6 +71,7 @@ static int help(void) { "%5$sMark /etc/ and /var/ as fully updated.%6$s\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" + " --root=PATH Operate on root directory PATH\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -67,12 +84,17 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { + enum { + ARG_ROOT = 0x100, + }; + static const struct option options[] = { { "help", no_argument, NULL, 'h' }, + { "root", required_argument, NULL, ARG_ROOT }, {}, }; - int c; + int r, c; assert(argc >= 0); assert(argv); @@ -84,6 +106,12 @@ static int parse_argv(int argc, char *argv[]) { case 'h': return help(); + case ARG_ROOT: + r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -108,8 +136,12 @@ static int run(int argc, char *argv[]) { log_setup(); - if (stat("/usr", &st) < 0) - return log_error_errno(errno, "Failed to stat /usr: %m"); + r = chase_and_stat("/usr", arg_root, + CHASE_PREFIX_ROOT | CHASE_WARN | CHASE_MUST_BE_DIRECTORY, + /* ret_path = */ NULL, + &st); + if (r < 0) + return log_error_errno(r, "Failed to stat %s/usr/: %m", strempty(arg_root)); r = mac_init(); if (r < 0) From ace814f0a8a512c4751af4429918dd2c638606cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 19 Mar 2025 17:06:44 +0100 Subject: [PATCH 5/5] update-done: create /etc and /var if they didn't exist Previously, we would fail. But this doesn't seem useful: we may want to mark the update as done even if /etc/ or /var/ no updates were necessary and there was no need to create /etc/ or /var/ yet. --- src/update-done/update-done.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index b30bb2dbf0b..62c12c5d214 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -32,7 +32,7 @@ static int save_timestamp(const char *dir, struct timespec *ts) { fd = chase_and_open(dir, arg_root, CHASE_PREFIX_ROOT | CHASE_WARN | CHASE_MUST_BE_DIRECTORY, - O_DIRECTORY | O_CLOEXEC, + O_DIRECTORY | O_CLOEXEC | O_CREAT, &dirpath); if (fd < 0) return log_error_errno(fd, "Failed to open %s%s: %m", strempty(arg_root), dir);