diff --git a/man/systemd-update-done.service.xml b/man/systemd-update-done.service.xml index 6b863ecff3a..d9d78262a14 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 @@ -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 @@ -64,6 +63,25 @@ reboot where the kernel switch is not specified anymore. + + Options + + The following options are understood: + + + + + Takes a directory path as an argument. The program + will operate on paths below the specified root directory. + + + + + + + + + See Also diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index 9eb58311e92..62c12c5d214 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -1,21 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include #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" -#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 char *arg_root = NULL; -static int apply_timestamp(const char *path, struct timespec *ts) { - _cleanup_free_ char *message = NULL; +STATIC_DESTRUCTOR_REGISTER(arg_root, freep); + +static int save_timestamp(const char *dir, struct timespec *ts) { + _cleanup_free_ char *message = NULL, *dirpath = NULL; + _cleanup_close_ int fd = -EBADF; int r; /* @@ -23,37 +30,127 @@ static int apply_timestamp(const char *path, struct timespec *ts) { * to support filesystems which cannot store nanosecond-precision timestamps. */ + fd = chase_and_open(dir, arg_root, + CHASE_PREFIX_ROOT | CHASE_WARN | CHASE_MUST_BE_DIRECTORY, + 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); + 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(); - 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; } -int main(int argc, char *argv[]) { +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" + " --root=PATH Operate on root directory PATH\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[]) { + enum { + ARG_ROOT = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "root", required_argument, NULL, ARG_ROOT }, + {}, + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + 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; + + 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, q = 0; + int r; + + r = parse_argv(argc, argv); + if (r <= 0) + return r; log_setup(); - if (stat("/usr", &st) < 0) { - log_error_errno(errno, "Failed to stat /usr: %m"); - return EXIT_FAILURE; - } + 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) - return EXIT_FAILURE; + return r; - r = apply_timestamp("/etc/.updated", &st.st_mtim); - q = apply_timestamp("/var/.updated", &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);