]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #31028 from yuwata/journalctl-raise
authorMike Yuan <me@yhndnzj.com>
Mon, 12 Feb 2024 11:16:15 +0000 (19:16 +0800)
committerGitHub <noreply@github.com>
Mon, 12 Feb 2024 11:16:15 +0000 (19:16 +0800)
journalctl: call all cleanup functions before raise()

39 files changed:
NEWS
man/systemd-gpt-auto-generator.xml
man/systemd.service.xml
src/core/service.c
src/coredump/coredumpctl.c
src/firstboot/firstboot.c
src/gpt-auto-generator/gpt-auto-generator.c
src/journal-remote/fuzz-journal-remote.c
src/journal-remote/journal-upload.c
src/journal/bsod.c
src/journal/journalctl.c
src/libsystemd/sd-journal/journal-file.c
src/libsystemd/sd-journal/journal-file.h
src/libsystemd/sd-journal/journal-internal.h
src/libsystemd/sd-journal/sd-journal.c
src/libsystemd/sd-journal/test-journal-enum.c
src/libsystemd/sd-journal/test-journal-flush.c
src/libsystemd/sd-journal/test-journal-init.c
src/libsystemd/sd-journal/test-journal-interleaving.c
src/libsystemd/sd-journal/test-journal-match.c
src/libsystemd/sd-journal/test-journal-stream.c
src/network/networkctl.c
src/network/networkd-link.c
src/partition/repart.c
src/shared/fstab-util.c
src/shared/fstab-util.h
src/shared/journal-util.c
src/shared/journal-util.h
src/shared/logs-show.c
src/shared/udev-util.c
src/shared/udev-util.h
src/systemd/sd-journal.h
src/udev/udev-event.c
src/udev/udev-worker.c
test/test-network-generator-conversion.sh
test/test-network/systemd-networkd-tests.py
test/test-sysusers.sh.in
test/testsuite-23.units/testsuite-23-oneshot-restartforce.sh [new file with mode: 0755]
test/units/testsuite-23.oneshot-restart.sh

diff --git a/NEWS b/NEWS
index 2ba7e7dca6796c95f2c7aa7ee89c142ff80d7c42..bcf42ffd8e24ac3a597dbb15e9a31720ff6d95fa 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,13 @@ CHANGES WITH 256 in spe:
           section, then all assigned VLAN IDs on the interface that are not
           configured in the .network file are removed.
 
+        * systemd-gpt-auto-generator will stop generating units for ESP or
+          XBOOTLDR partitions if it finds mount entries in the /boot/ or /efi/
+          hierarchies in fstab. This is to prevent the generator from
+          interfering with systems where ESP is explicitly configured to be
+          mounted at some path, for example /boot/efi/ (this type of setup is
+          obsolete but is still commonly found).
+
         Network Management:
 
         * systemd-networkd's proxy support gained a new option to configure
index 3b86ef6d2dd0679bf0a9f95636e565201f9e3285..c8cf12a005993e67b2b1bff42a12aa02e50f27c5 100644 (file)
 
     <para><filename>systemd-gpt-auto-generator</filename> is a unit generator that automatically discovers
     the root partition, <filename>/home/</filename>, <filename>/srv/</filename>, <filename>/var/</filename>,
-    <filename>/var/tmp/</filename>, the EFI System Partition, the Extended Boot Loader Partition, and swap
-    partitions and creates mount and swap units for them, based on the partition type GUIDs of GUID partition
-    tables (GPT). See <ulink url="https://uefi.org/specifications">UEFI Specification</ulink>, chapter 5 for
-    more details. It implements the <ulink
+    <filename>/var/tmp/</filename>, the EFI System Partition (ESP), the Extended Boot Loader Partition
+    (XBOOTLDR), and swap partitions and creates mount and swap units for them, based on the partition type
+    GUIDs of GUID partition tables (GPT). See <ulink url="https://uefi.org/specifications">UEFI
+    Specification</ulink>, chapter 5 for more details. It implements the <ulink
     url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable
     Partitions Specification</ulink>.</para>
 
     <para>Note that this generator has no effect on non-GPT systems. It will also not create mount point
     configuration for directories which already contain files or if the mount point is explicitly configured
     in <citerefentry
-    project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>. If
-    the units this generator creates are overridden, for example by units in directories with higher
+    project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Additionally
+    no unit will be created for the ESP or the XBOOTLDR partition if mount entries are found in the
+    <filename>/boot/</filename> or <filename>/efi/</filename> hierarchies in <citerefentry
+    project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+    <para>If the units this generator creates are overridden, for example by units in directories with higher
     precedence, drop-ins and additional dependencies created by this generator might still be used.</para>
 
     <para>This generator will only look for the root partition on the same physical disk where the EFI System
index 74c22260bc56155503f51d283147ebfb01e33e62..154a7a2e66a06aa67b24e6bded5d8063bfc2970c 100644 (file)
 
       <varlistentry>
         <term><varname>Restart=</varname></term>
-        <listitem><para>Configures whether the service shall be
-        restarted when the service process exits, is killed, or a
-        timeout is reached. The service process may be the main
-        service process, but it may also be one of the processes
-        specified with <varname>ExecStartPre=</varname>,
-        <varname>ExecStartPost=</varname>,
-        <varname>ExecStop=</varname>,
-        <varname>ExecStopPost=</varname>, or
-        <varname>ExecReload=</varname>. When the death of the process
-        is a result of systemd operation (e.g. service stop or
-        restart), the service will not be restarted. Timeouts include
-        missing the watchdog "keep-alive ping" deadline and a service
-        start, reload, and stop operation timeouts.</para>
-
-        <para>Takes one of
-        <option>no</option>,
-        <option>on-success</option>,
-        <option>on-failure</option>,
-        <option>on-abnormal</option>,
-        <option>on-watchdog</option>,
-        <option>on-abort</option>, or
-        <option>always</option>.
-        If set to <option>no</option> (the default), the service will
-        not be restarted. If set to <option>on-success</option>, it
-        will be restarted only when the service process exits cleanly.
+        <listitem><para>Configures whether the service shall be restarted when the service process exits,
+        is killed, or a timeout is reached. The service process may be the main service process, but it may
+        also be one of the processes specified with <varname>ExecStartPre=</varname>,
+        <varname>ExecStartPost=</varname>, <varname>ExecStop=</varname>, <varname>ExecStopPost=</varname>,
+        or <varname>ExecReload=</varname>. When the death of the process is a result of systemd operation
+        (e.g. service stop or restart), the service will not be restarted. Timeouts include missing the watchdog
+        "keep-alive ping" deadline and a service start, reload, and stop operation timeouts.</para>
+
+        <para>Takes one of <option>no</option>, <option>on-success</option>, <option>on-failure</option>,
+        <option>on-abnormal</option>, <option>on-watchdog</option>, <option>on-abort</option>, or
+        <option>always</option>. If set to <option>no</option> (the default), the service will not be restarted.
+        If set to <option>on-success</option>, it will be restarted only when the service process exits cleanly.
         In this context, a clean exit means any of the following:
         <itemizedlist>
             <listitem><simpara>exit code of 0;</simpara></listitem>
-            <listitem><simpara>for types other than
-            <varname>Type=oneshot</varname>, one of the signals
-                <constant>SIGHUP</constant>,
-                <constant>SIGINT</constant>,
-                <constant>SIGTERM</constant>, or
-                <constant>SIGPIPE</constant>;</simpara></listitem>
+            <listitem><simpara>for types other than <varname>Type=oneshot</varname>, one of the signals
+                <constant>SIGHUP</constant>, <constant>SIGINT</constant>,
+                <constant>SIGTERM</constant>, or <constant>SIGPIPE</constant>;
+            </simpara></listitem>
             <listitem><simpara>exit statuses and signals specified in
                 <varname>SuccessExitStatus=</varname>.</simpara></listitem>
         </itemizedlist>
-        If set to
-        <option>on-failure</option>, the service will be restarted
-        when the process exits with a non-zero exit code, is
-        terminated by a signal (including on core dump, but excluding
-        the aforementioned four signals), when an operation (such as
-        service reload) times out, and when the configured watchdog
-        timeout is triggered. If set to <option>on-abnormal</option>,
-        the service will be restarted when the process is terminated
-        by a signal (including on core dump, excluding the
-        aforementioned four signals), when an operation times out, or
-        when the watchdog timeout is triggered. If set to
-        <option>on-abort</option>, the service will be restarted only
-        if the service process exits due to an uncaught signal not
-        specified as a clean exit status. If set to
-        <option>on-watchdog</option>, the service will be restarted
-        only if the watchdog timeout for the service expires. If set
-        to <option>always</option>, the service will be restarted
-        regardless of whether it exited cleanly or not, got terminated
-        abnormally by a signal, or hit a timeout.</para>
+        If set to <option>on-failure</option>, the service will be restarted when the process exits with
+        a non-zero exit code, is terminated by a signal (including on core dump, but excluding the aforementioned
+        four signals), when an operation (such as service reload) times out, and when the configured watchdog
+        timeout is triggered. If set to <option>on-abnormal</option>, the service will be restarted when
+        the process is terminated by a signal (including on core dump, excluding the aforementioned four signals),
+        when an operation times out, or when the watchdog timeout is triggered. If set to <option>on-abort</option>,
+        the service will be restarted only if the service process exits due to an uncaught signal not specified
+        as a clean exit status. If set to <option>on-watchdog</option>, the service will be restarted
+        only if the watchdog timeout for the service expires. If set to <option>always</option>, the service
+        will be restarted regardless of whether it exited cleanly or not, got terminated abnormally by
+        a signal, or hit a timeout. Note that <varname>Type=oneshot</varname> services will never be restarted
+        on a clean exit status, i.e. <option>always</option> and <option>on-success</option> are rejected
+        for them.</para>
 
         <table>
           <title>Exit causes and the effect of the <varname>Restart=</varname> settings</title>
 
       <varlistentry>
         <term><varname>RestartForceExitStatus=</varname></term>
-        <listitem><para>Takes a list of exit status definitions that,
-        when returned by the main service process, will force automatic
-        service restarts, regardless of the restart setting configured
-        with <varname>Restart=</varname>. The argument format is
-        similar to
-        <varname>RestartPreventExitStatus=</varname>.</para>
+
+        <listitem><para>Takes a list of exit status definitions that, when returned by the main service
+        process, will force automatic service restarts, regardless of the restart setting configured with
+        <varname>Restart=</varname>. The argument format is similar to <varname>RestartPreventExitStatus=</varname>.
+        </para>
+
+        <para>Note that for <varname>Type=oneshot</varname> services, a success exit status will prevent
+        them from auto-restarting, no matter whether the corresponding exit statuses are listed in this
+        option or not.</para>
 
         <xi:include href="version-info.xml" xpointer="v215"/></listitem>
       </varlistentry>
index 2b760eb7aa36d1302e4b26d2b4948f95d22b77e9..d2b8c18af1687068f880b36dd3f4818930ba5bdc 100644 (file)
@@ -662,13 +662,9 @@ static int service_verify(Service *s) {
         if (s->type != SERVICE_ONESHOT && s->exec_command[SERVICE_EXEC_START]->command_next)
                 return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has more than one ExecStart= setting, which is only allowed for Type=oneshot services. Refusing.");
 
-        if (s->type == SERVICE_ONESHOT &&
-            !IN_SET(s->restart, SERVICE_RESTART_NO, SERVICE_RESTART_ON_FAILURE, SERVICE_RESTART_ON_ABNORMAL, SERVICE_RESTART_ON_WATCHDOG, SERVICE_RESTART_ON_ABORT))
+        if (s->type == SERVICE_ONESHOT && IN_SET(s->restart, SERVICE_RESTART_ALWAYS, SERVICE_RESTART_ON_SUCCESS))
                 return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has Restart= set to either always or on-success, which isn't allowed for Type=oneshot services. Refusing.");
 
-        if (s->type == SERVICE_ONESHOT && !exit_status_set_is_empty(&s->restart_force_status))
-                return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has RestartForceExitStatus= set, which isn't allowed for Type=oneshot services. Refusing.");
-
         if (s->type == SERVICE_ONESHOT && s->exit_type == SERVICE_EXIT_CGROUP)
                 return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has ExitType=cgroup set, which isn't allowed for Type=oneshot services. Refusing.");
 
@@ -1901,6 +1897,7 @@ static int cgroup_good(Service *s) {
 
 static bool service_shall_restart(Service *s, const char **reason) {
         assert(s);
+        assert(reason);
 
         /* Don't restart after manual stops */
         if (s->forbid_restart) {
@@ -1916,6 +1913,13 @@ static bool service_shall_restart(Service *s, const char **reason) {
 
         /* Restart if the exit code/status are configured as restart triggers */
         if (exit_status_set_test(&s->restart_force_status,  s->main_exec_status.code, s->main_exec_status.status)) {
+                /* Don't allow Type=oneshot services to restart on success. Note that Restart=always/on-success
+                 * is already rejected in service_verify. */
+                if (s->type == SERVICE_ONESHOT && s->result == SERVICE_SUCCESS) {
+                        *reason = "service type and exit status";
+                        return false;
+                }
+
                 *reason = "forced by exit status";
                 return true;
         }
index 90b2fe4a7a5ec3128c196bf3842e018314b8418f..53769f8d4c6005373b40bbf332e735ecb82c594b 100644 (file)
@@ -128,19 +128,19 @@ static int acquire_journal(sd_journal **ret, char **matches) {
         assert(ret);
 
         if (arg_directory) {
-                r = sd_journal_open_directory(&j, arg_directory, 0);
+                r = sd_journal_open_directory(&j, arg_directory, SD_JOURNAL_ASSUME_IMMUTABLE);
                 if (r < 0)
                         return log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory);
         } else if (arg_root) {
-                r = sd_journal_open_directory(&j, arg_root, SD_JOURNAL_OS_ROOT);
+                r = sd_journal_open_directory(&j, arg_root, SD_JOURNAL_OS_ROOT | SD_JOURNAL_ASSUME_IMMUTABLE);
                 if (r < 0)
                         return log_error_errno(r, "Failed to open journals in root directory: %s: %m", arg_root);
         } else if (arg_file) {
-                r = sd_journal_open_files(&j, (const char**)arg_file, 0);
+                r = sd_journal_open_files(&j, (const char**)arg_file, SD_JOURNAL_ASSUME_IMMUTABLE);
                 if (r < 0)
                         return log_error_errno(r, "Failed to open journal files: %m");
         } else {
-                r = sd_journal_open(&j, arg_all ? 0 : SD_JOURNAL_LOCAL_ONLY);
+                r = sd_journal_open(&j, arg_all ? 0 : SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE);
                 if (r < 0)
                         return log_error_errno(r, "Failed to open journal: %m");
         }
index 652a6d5c89fe2d96714123f9973e7f5f310674ca..76af4e4ed52992065187743e8791b3ef4d89d87f 100644 (file)
@@ -458,6 +458,20 @@ static int process_locale(int rfd) {
         return 1;
 }
 
+static bool keymap_exists_bool(const char *name) {
+        return keymap_exists(name) > 0;
+}
+
+static typeof(&keymap_is_valid) determine_keymap_validity_func(int rfd) {
+        int r;
+
+        r = dir_fd_is_root(rfd);
+        if (r < 0)
+                log_debug_errno(r, "Unable to determine if operating on host root directory, assuming we are: %m");
+
+        return r != 0 ? keymap_exists_bool : keymap_is_valid;
+}
+
 static int prompt_keymap(int rfd) {
         _cleanup_strv_free_ char **kmaps = NULL;
         int r;
@@ -489,7 +503,7 @@ static int prompt_keymap(int rfd) {
         print_welcome(rfd);
 
         return prompt_loop("Please enter system keymap name or number",
-                           kmaps, 60, keymap_is_valid, &arg_keymap);
+                           kmaps, 60, determine_keymap_validity_func(rfd), &arg_keymap);
 }
 
 static int process_keymap(int rfd) {
@@ -1695,6 +1709,8 @@ static int run(int argc, char *argv[]) {
         /* We check these conditions here instead of in parse_argv() so that we can take the root directory
          * into account. */
 
+        if (arg_keymap && !determine_keymap_validity_func(rfd)(arg_keymap))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Keymap %s is not installed.", arg_keymap);
         if (arg_locale && !locale_is_ok(rfd, arg_locale))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale);
         if (arg_locale_messages && !locale_is_ok(rfd, arg_locale_messages))
index d23999ed93b436ecca71ad9fe4eebfa4af1adae2..1fd687108a9954674092bbca7c761dbfed55db07 100644 (file)
@@ -463,18 +463,6 @@ static int add_automount(
         return generator_add_symlink(arg_dest, SPECIAL_LOCAL_FS_TARGET, "wants", unit);
 }
 
-static int slash_boot_in_fstab(void) {
-        static int cache = -1;
-
-        if (cache >= 0)
-                return cache;
-
-        cache = fstab_is_mount_point("/boot");
-        if (cache < 0)
-                return log_error_errno(cache, "Failed to parse fstab: %m");
-        return cache;
-}
-
 static int add_partition_xbootldr(DissectedPartition *p) {
         _cleanup_free_ char *options = NULL;
         int r;
@@ -486,14 +474,6 @@ static int add_partition_xbootldr(DissectedPartition *p) {
                 return 0;
         }
 
-        r = slash_boot_in_fstab();
-        if (r < 0)
-                return r;
-        if (r > 0) {
-                log_debug("/boot/ specified in fstab, ignoring XBOOTLDR partition.");
-                return 0;
-        }
-
         r = path_is_busy("/boot");
         if (r < 0)
                 return r;
@@ -523,18 +503,6 @@ static int add_partition_xbootldr(DissectedPartition *p) {
 }
 
 #if ENABLE_EFI
-static int slash_efi_in_fstab(void) {
-        static int cache = -1;
-
-        if (cache >= 0)
-                return cache;
-
-        cache = fstab_is_mount_point("/efi");
-        if (cache < 0)
-                return log_error_errno(cache, "Failed to parse fstab: %m");
-        return cache;
-}
-
 static bool slash_boot_exists(void) {
         static int cache = -1;
 
@@ -574,27 +542,16 @@ static int add_partition_esp(DissectedPartition *p, bool has_xbootldr) {
          * Otherwise, if /efi/ is unused and empty (or missing), we'll take that.
          * Otherwise, we do nothing. */
         if (!has_xbootldr && slash_boot_exists()) {
-                r = slash_boot_in_fstab();
+                r = path_is_busy("/boot");
                 if (r < 0)
                         return r;
                 if (r == 0) {
-                        r = path_is_busy("/boot");
-                        if (r < 0)
-                                return r;
-                        if (r == 0) {
-                                esp_path = "/boot";
-                                id = "boot";
-                        }
+                        esp_path = "/boot";
+                        id = "boot";
                 }
         }
 
         if (!esp_path) {
-                r = slash_efi_in_fstab();
-                if (r < 0)
-                        return r;
-                if (r > 0)
-                        return 0;
-
                 r = path_is_busy("/efi");
                 if (r < 0)
                         return r;
@@ -781,6 +738,18 @@ static int process_loader_partitions(DissectedPartition *esp, DissectedPartition
         assert(esp);
         assert(xbootldr);
 
+        /* If any paths in fstab look similar to our favorite paths for ESP or XBOOTLDR, we just exit
+         * early. We also don't bother with cases where one is configured explicitly and the other shall be
+         * mounted automatically. */
+
+        r = fstab_has_mount_point_prefix_strv(STRV_MAKE("/boot", "/efi"));
+        if (r > 0) {
+                log_debug("Found mount entries in the /boot/ or /efi/ hierarchies in fstab, not generating ESP or XBOOTLDR mounts.");
+                return 0;
+        }
+        if (r < 0)
+                log_debug_errno(r, "Failed to check fstab existing paths, ignoring: %m");
+
         if (!is_efi_boot()) {
                 log_debug("Not an EFI boot, skipping loader partition UUID check.");
                 goto mount;
index 557100bb965d55f7670bfcbc68175685bc6e4336..774389dee35601df15e37ee27dcf8615fcf57112 100644 (file)
@@ -67,7 +67,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
 
         /* Out */
 
-        r = sd_journal_open_files(&j, (const char**) STRV_MAKE(name), 0);
+        r = sd_journal_open_files(&j, (const char**) STRV_MAKE(name), SD_JOURNAL_ASSUME_IMMUTABLE);
         if (r < 0) {
                 log_error_errno(r, "sd_journal_open_files([\"%s\"]) failed: %m", name);
                 assert_se(IN_SET(r, -ENOMEM, -EMFILE, -ENFILE, -ENODATA));
index db7435584269a1cabfffb287dca6094390adea2a..97b5f929abd2388b6b9a81b962b31eea256806ae 100644 (file)
@@ -777,7 +777,7 @@ static int open_journal(sd_journal **j) {
         else if (arg_file)
                 r = sd_journal_open_files(j, (const char**) arg_file, 0);
         else if (arg_machine)
-                r = journal_open_machine(j, arg_machine);
+                r = journal_open_machine(j, arg_machine, 0);
         else
                 r = sd_journal_open_namespace(j, arg_namespace,
                                               (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | arg_namespace_flags | arg_journal_type);
index 059e255ea4636446dd867e5ef3598ded5b17465c..32525437df513d865528a245d6cc842e20d886c5 100644 (file)
@@ -60,7 +60,7 @@ static int acquire_first_emergency_log_message(char **ret) {
 
         assert(ret);
 
-        r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+        r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE);
         if (r < 0)
                 return log_error_errno(r, "Failed to open journal: %m");
 
index 073dc1426caf627f2d5c891a3601098f108d5a00..6262dd0aa6b0593afbc047e3162ab192702e9262 100644 (file)
@@ -130,6 +130,7 @@ static const char *arg_field = NULL;
 static bool arg_catalog = false;
 static bool arg_reverse = false;
 static int arg_journal_type = 0;
+static int arg_journal_additional_open_flags = 0;
 static int arg_namespace_flags = 0;
 static char *arg_root = NULL;
 static char *arg_image = NULL;
@@ -1153,6 +1154,9 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_reverse = true;
         }
 
+        if (!arg_follow)
+                arg_journal_additional_open_flags = SD_JOURNAL_ASSUME_IMMUTABLE;
+
         return 1;
 }
 
@@ -2413,21 +2417,21 @@ static int run(int argc, char *argv[]) {
         }
 
         if (arg_directory)
-                r = sd_journal_open_directory(&j, arg_directory, arg_journal_type);
+                r = sd_journal_open_directory(&j, arg_directory, arg_journal_type | arg_journal_additional_open_flags);
         else if (arg_root)
-                r = sd_journal_open_directory(&j, arg_root, arg_journal_type | SD_JOURNAL_OS_ROOT);
+                r = sd_journal_open_directory(&j, arg_root, arg_journal_type | arg_journal_additional_open_flags | SD_JOURNAL_OS_ROOT);
         else if (arg_file_stdin)
-                r = sd_journal_open_files_fd(&j, (int[]) { STDIN_FILENO }, 1, 0);
+                r = sd_journal_open_files_fd(&j, (int[]) { STDIN_FILENO }, 1, arg_journal_additional_open_flags);
         else if (arg_file)
-                r = sd_journal_open_files(&j, (const char**) arg_file, 0);
+                r = sd_journal_open_files(&j, (const char**) arg_file, arg_journal_additional_open_flags);
         else if (arg_machine)
-                r = journal_open_machine(&j, arg_machine);
+                r = journal_open_machine(&j, arg_machine, arg_journal_additional_open_flags);
         else
                 r = sd_journal_open_namespace(
                                 &j,
                                 arg_namespace,
                                 (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) |
-                                arg_namespace_flags | arg_journal_type);
+                                arg_namespace_flags | arg_journal_type | arg_journal_additional_open_flags);
         if (r < 0)
                 return log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal");
 
index 64bf8ef9afe4fe250f29f3a0f8c3b7294a40214b..8039b2aaf95bb6703c8fa52ae338b28c210a7feb 100644 (file)
@@ -2244,7 +2244,6 @@ static int journal_file_link_entry(
         f->header->tail_entry_monotonic = o->entry.monotonic;
         if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset))
                 f->header->tail_entry_offset = htole64(offset);
-        f->newest_mtime = 0; /* we have a new tail entry now, explicitly invalidate newest boot id/timestamp info */
 
         /* Link up the items */
         for (uint64_t i = 0; i < n_items; i++) {
index 81fafb9becdb4e06a30683a483670ea0a3e78535..6b378a2039036136f3b1bda7d4658f92d7f498ef 100644 (file)
@@ -129,7 +129,8 @@ typedef struct JournalFile {
         uint64_t newest_monotonic_usec;
         uint64_t newest_realtime_usec;
         unsigned newest_boot_id_prioq_idx;
-        usec_t newest_mtime;
+        uint64_t newest_entry_offset;
+        uint8_t newest_state;
 } JournalFile;
 
 typedef enum JournalFileFlags {
index e57c5208c18c425498b56e85719815a9c00ace61..0293389a78a3ffc1f162c461b73c24e87645d67e 100644 (file)
@@ -12,7 +12,7 @@
 #include "journal-def.h"
 #include "journal-file.h"
 #include "list.h"
-#include "set.h"
+#include "prioq.h"
 
 #define JOURNAL_FILES_MAX 7168u
 
@@ -69,6 +69,11 @@ struct Directory {
         unsigned last_seen_generation;
 };
 
+typedef struct NewestByBootId {
+        sd_id128_t boot_id;
+        Prioq *prioq; /* JournalFile objects ordered by monotonic timestamp of last update. */
+} NewestByBootId;
+
 struct sd_journal {
         int toplevel_fd;
 
@@ -79,7 +84,10 @@ struct sd_journal {
         OrderedHashmap *files;
         IteratedCache *files_cache;
         MMapCache *mmap;
-        Hashmap *newest_by_boot_id; /* key: boot_id, value: prioq, ordered by monotonic timestamp of last update */
+
+        /* a bisectable array of NewestByBootId, ordered by boot id. */
+        NewestByBootId *newest_by_boot_id;
+        size_t n_newest_by_boot_id;
 
         Location current_location;
 
index 5a8c8a85790bc5afdc03c7ba45ea2a56d0c34ed0..1c2e273841dd688714faf8fe2141f5bd0b1fba63 100644 (file)
@@ -38,6 +38,7 @@
 #include "prioq.h"
 #include "process-util.h"
 #include "replace-var.h"
+#include "sort-util.h"
 #include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
@@ -413,6 +414,99 @@ _public_ void sd_journal_flush_matches(sd_journal *j) {
         detach_location(j);
 }
 
+static int newest_by_boot_id_compare(const NewestByBootId *a, const NewestByBootId *b) {
+        return id128_compare_func(&a->boot_id, &b->boot_id);
+}
+
+static void journal_file_unlink_newest_by_boot_id(sd_journal *j, JournalFile *f) {
+        NewestByBootId *found;
+
+        assert(j);
+        assert(f);
+
+        if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) /* not linked currently, hence this is a NOP */
+                return;
+
+        found = typesafe_bsearch(&(NewestByBootId) { .boot_id = f->newest_boot_id },
+                                 j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare);
+        assert(found);
+
+        assert_se(prioq_remove(found->prioq, f, &f->newest_boot_id_prioq_idx) > 0);
+        f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL;
+
+        /* The prioq may be empty, but that should not cause any issue. Let's keep it. */
+}
+
+static void journal_clear_newest_by_boot_id(sd_journal *j) {
+        FOREACH_ARRAY(i, j->newest_by_boot_id, j->n_newest_by_boot_id) {
+                JournalFile *f;
+
+                while ((f = prioq_peek(i->prioq)))
+                        journal_file_unlink_newest_by_boot_id(j, f);
+
+                prioq_free(i->prioq);
+        }
+
+        j->newest_by_boot_id = mfree(j->newest_by_boot_id);
+        j->n_newest_by_boot_id = 0;
+}
+
+static int journal_file_newest_monotonic_compare(const void *a, const void *b) {
+        const JournalFile *x = a, *y = b;
+
+        return -CMP(x->newest_monotonic_usec, y->newest_monotonic_usec); /* Invert order, we want newest first! */
+}
+
+static int journal_file_reshuffle_newest_by_boot_id(sd_journal *j, JournalFile *f) {
+        NewestByBootId *found;
+        int r;
+
+        assert(j);
+        assert(f);
+
+        found = typesafe_bsearch(&(NewestByBootId) { .boot_id = f->newest_boot_id },
+                                 j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare);
+        if (found) {
+                /* There's already a priority queue for this boot ID */
+
+                if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) {
+                        r = prioq_put(found->prioq, f, &f->newest_boot_id_prioq_idx); /* Insert if we aren't in there yet */
+                        if (r < 0)
+                                return r;
+                } else
+                        prioq_reshuffle(found->prioq, f, &f->newest_boot_id_prioq_idx); /* Reshuffle otherwise */
+
+        } else {
+                _cleanup_(prioq_freep) Prioq *q = NULL;
+
+                /* No priority queue yet, then allocate one */
+
+                assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); /* we can't be a member either */
+
+                q = prioq_new(journal_file_newest_monotonic_compare);
+                if (!q)
+                        return -ENOMEM;
+
+                r = prioq_put(q, f, &f->newest_boot_id_prioq_idx);
+                if (r < 0)
+                        return r;
+
+                if (!GREEDY_REALLOC(j->newest_by_boot_id, j->n_newest_by_boot_id + 1)) {
+                        f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL;
+                        return -ENOMEM;
+                }
+
+                j->newest_by_boot_id[j->n_newest_by_boot_id++] = (NewestByBootId) {
+                        .boot_id = f->newest_boot_id,
+                        .prioq = TAKE_PTR(q),
+                };
+
+                typesafe_qsort(j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare);
+        }
+
+        return 0;
+}
+
 static int journal_file_find_newest_for_boot_id(
                 sd_journal *j,
                 sd_id128_t id,
@@ -427,16 +521,17 @@ static int journal_file_find_newest_for_boot_id(
         /* Before we use it, let's refresh the timestamp from the header, and reshuffle our prioq
          * accordingly. We do this only a bunch of times, to not be caught in some update loop. */
         for (unsigned n_tries = 0;; n_tries++) {
+                NewestByBootId *found;
                 JournalFile *f;
-                Prioq *q;
 
-                q = hashmap_get(j->newest_by_boot_id, &id);
-                if (!q)
+                found = typesafe_bsearch(&(NewestByBootId) { .boot_id = id },
+                                         j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare);
+
+                f = found ? prioq_peek(found->prioq) : NULL;
+                if (!f)
                         return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
                                                "Requested delta for boot ID %s, but we have no information about that boot ID.", SD_ID128_TO_STRING(id));
 
-                assert_se(f = prioq_peek(q)); /* we delete hashmap entries once the prioq is empty, so this must hold */
-
                 if (f == prev || n_tries >= 5) {
                         /* This was already the best answer in the previous run, or we tried too often, use it */
                         *ret = f;
@@ -449,6 +544,11 @@ static int journal_file_find_newest_for_boot_id(
                 r = journal_file_read_tail_timestamp(j, f);
                 if (r < 0)
                         return log_debug_errno(r, "Failed to read tail timestamp while trying to find newest journal file for boot ID %s.", SD_ID128_TO_STRING(id));
+                if (r == 0) {
+                        /* No new entry found. */
+                        *ret = f;
+                        return 0;
+                }
 
                 /* Refreshing the timestamp we read might have reshuffled the prioq, hence let's check the
                  * prioq again and only use the information once we reached an equilibrium or hit a limit */
@@ -2087,7 +2187,8 @@ static sd_journal *journal_new(int flags, const char *path, const char *namespac
          SD_JOURNAL_SYSTEM |                            \
          SD_JOURNAL_CURRENT_USER |                      \
          SD_JOURNAL_ALL_NAMESPACES |                    \
-         SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE)
+         SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE |         \
+         SD_JOURNAL_ASSUME_IMMUTABLE)
 
 _public_ int sd_journal_open_namespace(sd_journal **ret, const char *namespace, int flags) {
         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
@@ -2113,7 +2214,9 @@ _public_ int sd_journal_open(sd_journal **ret, int flags) {
 }
 
 #define OPEN_CONTAINER_ALLOWED_FLAGS                    \
-        (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM)
+        (SD_JOURNAL_LOCAL_ONLY |                        \
+         SD_JOURNAL_SYSTEM |                            \
+         SD_JOURNAL_ASSUME_IMMUTABLE)
 
 _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) {
         _cleanup_free_ char *root = NULL, *class = NULL;
@@ -2157,7 +2260,9 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in
 
 #define OPEN_DIRECTORY_ALLOWED_FLAGS                    \
         (SD_JOURNAL_OS_ROOT |                           \
-         SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER )
+         SD_JOURNAL_SYSTEM |                            \
+         SD_JOURNAL_CURRENT_USER |                      \
+         SD_JOURNAL_ASSUME_IMMUTABLE)
 
 _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) {
         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
@@ -2182,12 +2287,15 @@ _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int f
         return 0;
 }
 
+#define OPEN_FILES_ALLOWED_FLAGS                        \
+        (SD_JOURNAL_ASSUME_IMMUTABLE)
+
 _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int flags) {
         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
         int r;
 
         assert_return(ret, -EINVAL);
-        assert_return(flags == 0, -EINVAL);
+        assert_return((flags & ~OPEN_FILES_ALLOWED_FLAGS) == 0, -EINVAL);
 
         j = journal_new(flags, NULL, NULL);
         if (!j)
@@ -2209,7 +2317,8 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla
         (SD_JOURNAL_OS_ROOT |                           \
          SD_JOURNAL_SYSTEM |                            \
          SD_JOURNAL_CURRENT_USER |                      \
-         SD_JOURNAL_TAKE_DIRECTORY_FD)
+         SD_JOURNAL_TAKE_DIRECTORY_FD |                 \
+         SD_JOURNAL_ASSUME_IMMUTABLE)
 
 _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) {
         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
@@ -2247,6 +2356,9 @@ _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) {
         return 0;
 }
 
+#define OPEN_FILES_FD_ALLOWED_FLAGS                        \
+        (SD_JOURNAL_ASSUME_IMMUTABLE)
+
 _public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags) {
         JournalFile *f;
         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
@@ -2254,7 +2366,7 @@ _public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fd
 
         assert_return(ret, -EINVAL);
         assert_return(n_fds > 0, -EBADF);
-        assert_return(flags == 0, -EINVAL);
+        assert_return((flags & ~OPEN_FILES_FD_ALLOWED_FLAGS) == 0, -EINVAL);
 
         j = journal_new(flags, NULL, NULL);
         if (!j)
@@ -2298,14 +2410,10 @@ fail:
 }
 
 _public_ void sd_journal_close(sd_journal *j) {
-        Prioq *p;
-
         if (!j || journal_origin_changed(j))
                 return;
 
-        while ((p = hashmap_first(j->newest_by_boot_id)))
-                journal_file_unlink_newest_by_boot_id(j, prioq_peek(p));
-        hashmap_free(j->newest_by_boot_id);
+        journal_clear_newest_by_boot_id(j);
 
         sd_journal_flush_matches(j);
 
@@ -2337,84 +2445,6 @@ _public_ void sd_journal_close(sd_journal *j) {
         free(j);
 }
 
-static void journal_file_unlink_newest_by_boot_id(sd_journal *j, JournalFile *f) {
-        JournalFile *nf;
-        Prioq *p;
-
-        assert(j);
-        assert(f);
-
-        if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) /* not linked currently, hence this is a NOP */
-                return;
-
-        assert_se(p = hashmap_get(j->newest_by_boot_id, &f->newest_boot_id));
-        assert_se(prioq_remove(p, f, &f->newest_boot_id_prioq_idx) > 0);
-
-        nf = prioq_peek(p);
-        if (nf)
-                /* There's still a member in the prioq? Then make sure the hashmap key now points to its
-                 * .newest_boot_id field (and not ours!). Not we only replace the memory of the key here, the
-                 * value of the key (and the data associated with it) remain the same. */
-                assert_se(hashmap_replace(j->newest_by_boot_id, &nf->newest_boot_id, p) >= 0);
-        else {
-                assert_se(hashmap_remove(j->newest_by_boot_id, &f->newest_boot_id) == p);
-                prioq_free(p);
-        }
-
-        f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL;
-}
-
-static int journal_file_newest_monotonic_compare(const void *a, const void *b) {
-        const JournalFile *x = a, *y = b;
-
-        return -CMP(x->newest_monotonic_usec, y->newest_monotonic_usec); /* Invert order, we want newest first! */
-}
-
-static int journal_file_reshuffle_newest_by_boot_id(sd_journal *j, JournalFile *f) {
-        Prioq *p;
-        int r;
-
-        assert(j);
-        assert(f);
-
-        p = hashmap_get(j->newest_by_boot_id, &f->newest_boot_id);
-        if (p) {
-                /* There's already a priority queue for this boot ID */
-
-                if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) {
-                        r = prioq_put(p, f, &f->newest_boot_id_prioq_idx); /* Insert if we aren't in there yet */
-                        if (r < 0)
-                                return r;
-                } else
-                        prioq_reshuffle(p, f, &f->newest_boot_id_prioq_idx); /* Reshuffle otherwise */
-
-        } else {
-                _cleanup_(prioq_freep) Prioq *q = NULL;
-
-                /* No priority queue yet, then allocate one */
-
-                assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); /* we can't be a member either */
-
-                q = prioq_new(journal_file_newest_monotonic_compare);
-                if (!q)
-                        return -ENOMEM;
-
-                r = prioq_put(q, f, &f->newest_boot_id_prioq_idx);
-                if (r < 0)
-                        return r;
-
-                r = hashmap_ensure_put(&j->newest_by_boot_id, &id128_hash_ops, &f->newest_boot_id, q);
-                if (r < 0) {
-                        f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL;
-                        return r;
-                }
-
-                TAKE_PTR(q);
-        }
-
-        return 0;
-}
-
 static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
         uint64_t offset, mo, rt;
         sd_id128_t id;
@@ -2428,11 +2458,13 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
 
         /* Tries to read the timestamp of the most recently written entry. */
 
-        r = journal_file_fstat(f);
-        if (r < 0)
-                return r;
-        if (f->newest_mtime == timespec_load(&f->last_stat.st_mtim))
-                return 0; /* mtime didn't change since last time, don't bother */
+        if (FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE) && f->newest_entry_offset != 0)
+                return 0; /* We have already read the file, and we assume that the file is immutable. */
+
+        if (f->header->state == f->newest_state &&
+            f->header->state == STATE_ARCHIVED &&
+            f->newest_entry_offset != 0)
+                return 0; /* We have already read archived file. */
 
         if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) {
                 offset = le64toh(READ_NOW(f->header->tail_entry_offset));
@@ -2443,6 +2475,8 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
         }
         if (offset == 0)
                 return -ENODATA; /* not a single object/entry, hence no tail timestamp */
+        if (offset == f->newest_entry_offset)
+                return 0; /* No new entry is added after we read last time. */
 
         /* Move to the last object in the journal file, in the hope it is an entry (which it usually will
          * be). If we lack the "tail_entry_offset" field in the header, we specify the type as OBJECT_UNUSED
@@ -2452,6 +2486,7 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
         if (r < 0) {
                 log_debug_errno(r, "Failed to move to last object in journal file, ignoring: %m");
                 o = NULL;
+                offset = 0;
         }
         if (o && o->object.type == OBJECT_ENTRY) {
                 /* Yay, last object is an entry, let's use the data. */
@@ -2469,10 +2504,11 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
                         mo = le64toh(f->header->tail_entry_monotonic);
                         rt = le64toh(f->header->tail_entry_realtime);
                         id = f->header->tail_entry_boot_id;
+                        offset = UINT64_MAX;
                 } else {
                         /* Otherwise let's find the last entry manually (this possibly means traversing the
                          * chain of entry arrays, till the end */
-                        r = journal_file_next_entry(f, 0, DIRECTION_UP, &o, NULL);
+                        r = journal_file_next_entry(f, 0, DIRECTION_UP, &o, offset == 0 ? &offset : NULL);
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -2487,6 +2523,17 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
         if (mo > rt) /* monotonic clock is further ahead than realtime? that's weird, refuse to use the data */
                 return -ENODATA;
 
+        if (offset == f->newest_entry_offset) {
+                /* Cached data and the current one should be equivalent. */
+                if (!sd_id128_equal(f->newest_machine_id, f->header->machine_id) ||
+                    !sd_id128_equal(f->newest_boot_id, id) ||
+                    f->newest_monotonic_usec != mo ||
+                    f->newest_realtime_usec != rt)
+                        return -EBADMSG;
+
+                return 0; /* No new entry is added after we read last time. */
+        }
+
         if (!sd_id128_equal(f->newest_boot_id, id))
                 journal_file_unlink_newest_by_boot_id(j, f);
 
@@ -2494,13 +2541,14 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
         f->newest_monotonic_usec = mo;
         f->newest_realtime_usec = rt;
         f->newest_machine_id = f->header->machine_id;
-        f->newest_mtime = timespec_load(&f->last_stat.st_mtim);
+        f->newest_entry_offset = offset;
+        f->newest_state = f->header->state;
 
         r = journal_file_reshuffle_newest_by_boot_id(j, f);
         if (r < 0)
                 return r;
 
-        return 0;
+        return 1; /* Updated. */
 }
 
 _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
@@ -2771,6 +2819,7 @@ _public_ int sd_journal_get_fd(sd_journal *j) {
 
         assert_return(j, -EINVAL);
         assert_return(!journal_origin_changed(j), -ECHILD);
+        assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH);
 
         if (j->no_inotify)
                 return -EMEDIUMTYPE;
@@ -2797,6 +2846,7 @@ _public_ int sd_journal_get_events(sd_journal *j) {
 
         assert_return(j, -EINVAL);
         assert_return(!journal_origin_changed(j), -ECHILD);
+        assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH);
 
         fd = sd_journal_get_fd(j);
         if (fd < 0)
@@ -2810,6 +2860,7 @@ _public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) {
 
         assert_return(j, -EINVAL);
         assert_return(!journal_origin_changed(j), -ECHILD);
+        assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH);
         assert_return(timeout_usec, -EINVAL);
 
         fd = sd_journal_get_fd(j);
@@ -2937,6 +2988,8 @@ _public_ int sd_journal_process(sd_journal *j) {
         if (j->inotify_fd < 0) /* We have no inotify fd yet? Then there's noting to process. */
                 return 0;
 
+        assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH);
+
         j->last_process_usec = now(CLOCK_MONOTONIC);
         j->last_invalidate_counter = j->current_invalidate_counter;
 
@@ -2965,6 +3018,7 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) {
 
         assert_return(j, -EINVAL);
         assert_return(!journal_origin_changed(j), -ECHILD);
+        assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH);
 
         if (j->inotify_fd < 0) {
                 JournalFile *f;
index 03fe8e2b300a686d245ebae747e4f997c0e00e61..93d4b8f3c74f07bd62569175ee010230ecd1b69b 100644 (file)
@@ -15,7 +15,7 @@ int main(int argc, char *argv[]) {
 
         test_setup_logging(LOG_DEBUG);
 
-        assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY) >= 0);
+        assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE) >= 0);
 
         assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", 0) >= 0);
         assert_se(sd_journal_add_match(j, "_UID=0", 0) >= 0);
index 3f0783571c890ee040df63b61c5d97f8ae253476..95c2757f17fe8694993cf469e8e0829a7fe46ae6 100644 (file)
@@ -36,9 +36,9 @@ static void test_journal_flush_one(int argc, char *argv[]) {
         assert_se(r >= 0);
 
         if (argc > 1)
-                r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), 0);
+                r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), SD_JOURNAL_ASSUME_IMMUTABLE);
         else
-                r = sd_journal_open(&j, 0);
+                r = sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE);
         assert_se(r == 0);
 
         sd_journal_set_data_threshold(j, 0);
@@ -75,7 +75,7 @@ static void test_journal_flush_one(int argc, char *argv[]) {
 
         /* Open the new journal before archiving and offlining the file. */
         sd_journal_close(j);
-        assert_se(sd_journal_open_directory(&j, dn, 0) >= 0);
+        assert_se(sd_journal_open_directory(&j, dn, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0);
 
         /* Read the online journal. */
         assert_se(sd_journal_seek_tail(j) >= 0);
index 5fe4b7a2d68e2f20fc12b2c0feac1f062ea65af7..ef66efdabf1fec4d20d2baf9ddb884b0ca64bceb 100644 (file)
@@ -31,12 +31,12 @@ int main(int argc, char *argv[]) {
         (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
 
         for (i = 0; i < I; i++) {
-                r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+                r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE);
                 assert_se(r == 0);
 
                 sd_journal_close(j);
 
-                r = sd_journal_open_directory(&j, t, 0);
+                r = sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE);
                 assert_se(r == 0);
 
                 assert_se(sd_journal_seek_head(j) == 0);
index 93c2c4aaca07a4c5d6bfad80105a110e7b9d0659..688fea392bf060ccfda3ff95cbe470b1e8c933c2 100644 (file)
@@ -260,14 +260,14 @@ static void test_skip_one(void (*setup)(void)) {
         setup();
 
         /* Seek to head, iterate down. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_head(j));
         assert_se(sd_journal_next(j) == 1);     /* pointing to the first entry */
         test_check_numbers_down(j, 9);
         sd_journal_close(j);
 
         /* Seek to head, iterate down. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_head(j));
         assert_se(sd_journal_next(j) == 1);     /* pointing to the first entry */
         assert_se(sd_journal_previous(j) == 0); /* no-op */
@@ -275,7 +275,7 @@ static void test_skip_one(void (*setup)(void)) {
         sd_journal_close(j);
 
         /* Seek to head twice, iterate down. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_head(j));
         assert_se(sd_journal_next(j) == 1);     /* pointing to the first entry */
         assert_ret(sd_journal_seek_head(j));
@@ -284,7 +284,7 @@ static void test_skip_one(void (*setup)(void)) {
         sd_journal_close(j);
 
         /* Seek to head, move to previous, then iterate down. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_head(j));
         assert_se(sd_journal_previous(j) == 0); /* no-op */
         assert_se(sd_journal_next(j) == 1);     /* pointing to the first entry */
@@ -292,7 +292,7 @@ static void test_skip_one(void (*setup)(void)) {
         sd_journal_close(j);
 
         /* Seek to head, walk several steps, then iterate down. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_head(j));
         assert_se(sd_journal_previous(j) == 0); /* no-op */
         assert_se(sd_journal_previous(j) == 0); /* no-op */
@@ -304,14 +304,14 @@ static void test_skip_one(void (*setup)(void)) {
         sd_journal_close(j);
 
         /* Seek to tail, iterate up. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_tail(j));
         assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */
         test_check_numbers_up(j, 9);
         sd_journal_close(j);
 
         /* Seek to tail twice, iterate up. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_tail(j));
         assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */
         assert_ret(sd_journal_seek_tail(j));
@@ -320,7 +320,7 @@ static void test_skip_one(void (*setup)(void)) {
         sd_journal_close(j);
 
         /* Seek to tail, move to next, then iterate up. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_tail(j));
         assert_se(sd_journal_next(j) == 0);     /* no-op */
         assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */
@@ -328,7 +328,7 @@ static void test_skip_one(void (*setup)(void)) {
         sd_journal_close(j);
 
         /* Seek to tail, walk several steps, then iterate up. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_tail(j));
         assert_se(sd_journal_next(j) == 0);     /* no-op */
         assert_se(sd_journal_next(j) == 0);     /* no-op */
@@ -340,14 +340,14 @@ static void test_skip_one(void (*setup)(void)) {
         sd_journal_close(j);
 
         /* Seek to tail, skip to head, iterate down. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_tail(j));
         assert_se(sd_journal_previous_skip(j, 9) == 9); /* pointing to the first entry. */
         test_check_numbers_down(j, 9);
         sd_journal_close(j);
 
         /* Seek to tail, skip to head in a more complex way, then iterate down. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_tail(j));
         assert_se(sd_journal_next(j) == 0);
         assert_se(sd_journal_previous_skip(j, 4) == 4);
@@ -366,14 +366,14 @@ static void test_skip_one(void (*setup)(void)) {
         sd_journal_close(j);
 
         /* Seek to head, skip to tail, iterate up. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_head(j));
         assert_se(sd_journal_next_skip(j, 9) == 9);
         test_check_numbers_up(j, 9);
         sd_journal_close(j);
 
         /* Seek to head, skip to tail in a more complex way, then iterate up. */
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_ret(sd_journal_seek_head(j));
         assert_se(sd_journal_previous(j) == 0);
         assert_se(sd_journal_next_skip(j, 4) == 4);
@@ -409,14 +409,14 @@ static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) {
 
         setup();
 
-        assert_ret(sd_journal_open_directory(&j, t, 0));
+        assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
         assert_se(journal_get_boots(j, &boots, &n_boots) >= 0);
         assert_se(boots);
         assert_se(n_boots == n_boots_expected);
         sd_journal_close(j);
 
         FOREACH_ARRAY(b, boots, n_boots) {
-                assert_ret(sd_journal_open_directory(&j, t, 0));
+                assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
                 assert_se(journal_find_boot_by_id(j, b->id) == 1);
                 sd_journal_close(j);
         }
@@ -424,7 +424,7 @@ static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) {
         for (int i = - (int) n_boots + 1; i <= (int) n_boots; i++) {
                 sd_id128_t id;
 
-                assert_ret(sd_journal_open_directory(&j, t, 0));
+                assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
                 assert_se(journal_find_boot_by_offset(j, i, &id) == 1);
                 if (i <= 0)
                         assert_se(sd_id128_equal(id, boots[n_boots + i - 1].id));
index 571a88c1ac627ffbee8faea31bb53d6030ca563f..e56b27dde3f6b195eb60a5f852406634d5495036 100644 (file)
@@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
 
         test_setup_logging(LOG_DEBUG);
 
-        assert_se(sd_journal_open(&j, 0) >= 0);
+        assert_se(sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0);
 
         assert_se(sd_journal_add_match(j, "foobar", 0) < 0);
         assert_se(sd_journal_add_match(j, "foobar=waldo", 0) < 0);
index 3a370ef3119d4c1c1c02f161f8f4b2e4b4a1e63f..800001cb93e5c7c3aaa478bb3644e0251d3937c2 100644 (file)
@@ -119,7 +119,7 @@ static void run_test(void) {
         (void) journal_file_offline_close(two);
         (void) journal_file_offline_close(three);
 
-        assert_se(sd_journal_open_directory(&j, t, 0) >= 0);
+        assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0);
 
         assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0);
         SD_JOURNAL_FOREACH_BACKWARDS(j) {
index 6f5100c6056a902b24553a85ddb32da9d9c96157..61fa0ed5566b9715f3f87419a99064b62eb329a5 100644 (file)
@@ -1597,7 +1597,7 @@ static int show_logs(const LinkInfo *info) {
         if (arg_lines == 0)
                 return 0;
 
-        r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+        r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE);
         if (r < 0)
                 return log_error_errno(r, "Failed to open journal: %m");
 
index c0d0f83ca30f9317884932cf70dfccbc2159371b..ee442e3a4bb5408f7721d9d601342ed98c116c34 100644 (file)
@@ -1651,6 +1651,14 @@ static int link_check_initialized(Link *link) {
                 return 0;
         }
 
+        r = device_is_processing(device);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to determine whether the device is being processed: %m");
+        if (r > 0) {
+                log_link_debug(link, "Interface is being processed by udevd, pending initialization.");
+                return 0;
+        }
+
         return link_initialized(link, device);
 }
 
index 63e9dcfbd163247146257706d1c01efd0deb4c6d..7735bd458af33b78c7dd1bd67f231b06600041e3 100644 (file)
@@ -6343,7 +6343,7 @@ static int context_fstab(Context *context) {
                 return false;
 
         if (!need_fstab(context)) {
-                log_notice("MountPoint= is not specified for any elligible partitions, not generating %s",
+                log_notice("MountPoint= is not specified for any eligible partitions, not generating %s",
                            arg_generate_fstab);
                 return 0;
         }
@@ -6437,7 +6437,7 @@ static int context_crypttab(Context *context) {
                 return false;
 
         if (!need_crypttab(context)) {
-                log_notice("EncryptedVolume= is not specified for any elligible partitions, not generating %s",
+                log_notice("EncryptedVolume= is not specified for any eligible partitions, not generating %s",
                            arg_generate_crypttab);
                 return 0;
         }
index efefd9a5259589cca34579031417ea171e303721..0a3b2ce5d3a300e33e9bbb5f0172b21fcdd1027f 100644 (file)
@@ -105,6 +105,34 @@ static int fstab_is_same_node(const char *what_fstab, const char *path) {
         return false;
 }
 
+int fstab_has_mount_point_prefix_strv(char **prefixes) {
+        _cleanup_endmntent_ FILE *f = NULL;
+
+        assert(prefixes);
+
+        /* This function returns true if at least one entry in fstab has a mount point that starts with one
+         * of the passed prefixes. */
+
+        if (!fstab_enabled())
+                return false;
+
+        f = setmntent(fstab_path(), "re");
+        if (!f)
+                return errno == ENOENT ? false : -errno;
+
+        for (;;) {
+                struct mntent *me;
+
+                errno = 0;
+                me = getmntent(f);
+                if (!me)
+                        return errno != 0 ? -errno : false;
+
+                if (path_startswith_strv(me->mnt_dir, prefixes))
+                        return true;
+        }
+}
+
 int fstab_is_mount_point_full(const char *where, const char *path) {
         _cleanup_endmntent_ FILE *f = NULL;
         int r;
index c01b0b93d13d2a43ecb2f9fc3b724cc1db2907d3..040f07670fb1eea7217cf675e1c43f0d378de3f3 100644 (file)
@@ -25,6 +25,8 @@ static inline int fstab_has_node(const char *path) {
         return fstab_is_mount_point_full(NULL, path);
 }
 
+int fstab_has_mount_point_prefix_strv(char **prefixes);
+
 int fstab_filter_options(
                 const char *opts,
                 const char *names,
index d73d7c47d04c46d0e7558dbc3e508527fa44ee60..ab70f4da0d8bb4015d9aa7d49f3bf134cc0c9c0d 100644 (file)
@@ -145,7 +145,7 @@ int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_use
         return r;
 }
 
-int journal_open_machine(sd_journal **ret, const char *machine) {
+int journal_open_machine(sd_journal **ret, const char *machine, int flags) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
@@ -178,7 +178,7 @@ int journal_open_machine(sd_journal **ret, const char *machine) {
         if (machine_fd < 0)
                 return log_error_errno(errno, "Failed to duplicate file descriptor: %m");
 
-        r = sd_journal_open_directory_fd(&j, machine_fd, SD_JOURNAL_OS_ROOT | SD_JOURNAL_TAKE_DIRECTORY_FD);
+        r = sd_journal_open_directory_fd(&j, machine_fd, SD_JOURNAL_OS_ROOT | SD_JOURNAL_TAKE_DIRECTORY_FD | flags);
         if (r < 0)
                 return log_error_errno(r, "Failed to open journal in machine '%s': %m", machine);
 
index afad249c90153157971d00ebde944fe6dae53e40..5bd8e340b2efe754e815c66cef332be914a2560e 100644 (file)
@@ -8,4 +8,4 @@
 
 int journal_access_blocked(sd_journal *j);
 int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_users);
-int journal_open_machine(sd_journal **ret, const char *machine);
+int journal_open_machine(sd_journal **ret, const char *machine, int flags);
index 1f0279cde378abdf30f062580ecdb7b0f5561543..598c8ed12afe0610d1dea4c3ece2238936914f1f 100644 (file)
@@ -453,6 +453,39 @@ static int output_timestamp_realtime(
         return (int) strlen(buf);
 }
 
+static void parse_display_timestamp(
+                sd_journal *j,
+                const char *realtime,
+                const char *monotonic,
+                dual_timestamp *ret_display_ts,
+                sd_id128_t *ret_boot_id) {
+
+        bool realtime_good = false, monotonic_good = false, boot_id_good = false;
+
+        assert(j);
+        assert(ret_display_ts);
+        assert(ret_boot_id);
+
+        if (realtime)
+                realtime_good = safe_atou64(realtime, &ret_display_ts->realtime) >= 0;
+        if (!realtime_good || !VALID_REALTIME(ret_display_ts->realtime))
+                realtime_good = sd_journal_get_realtime_usec(j, &ret_display_ts->realtime) >= 0;
+        if (!realtime_good)
+                ret_display_ts->realtime = USEC_INFINITY;
+
+        if (monotonic)
+                monotonic_good = safe_atou64(monotonic, &ret_display_ts->monotonic) >= 0;
+        if (!monotonic_good || !VALID_MONOTONIC(ret_display_ts->monotonic))
+                monotonic_good = boot_id_good = sd_journal_get_monotonic_usec(j, &ret_display_ts->monotonic, ret_boot_id) >= 0;
+        if (!monotonic_good)
+                ret_display_ts->monotonic = USEC_INFINITY;
+
+        if (!boot_id_good)
+                boot_id_good = sd_journal_get_monotonic_usec(j, NULL, ret_boot_id) >= 0;
+        if (!boot_id_good)
+                *ret_boot_id = SD_ID128_NULL;
+}
+
 static int output_short(
                 FILE *f,
                 sd_journal *j,
@@ -461,42 +494,43 @@ static int output_short(
                 OutputFlags flags,
                 const Set *output_fields,
                 const size_t highlight[2],
-                const dual_timestamp *display_ts,
-                const sd_id128_t *boot_id,
-                const dual_timestamp *previous_display_ts,
-                const sd_id128_t *previous_boot_id) {
+                dual_timestamp *previous_display_ts, /* in and out */
+                sd_id128_t *previous_boot_id) {      /* in and out */
 
         int r;
         const void *data;
         size_t length, n = 0;
         _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL,
                 *message = NULL, *priority = NULL, *transport = NULL,
-                *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL;
+                *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL,
+                *realtime = NULL, *monotonic = NULL;
         size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0,
                 priority_len = 0, transport_len = 0, config_file_len = 0,
                 unit_len = 0, user_unit_len = 0, documentation_url_len = 0;
+        dual_timestamp display_ts;
+        sd_id128_t boot_id;
         int p = LOG_INFO;
         bool ellipsized = false, audit;
         const ParseFieldVec fields[] = {
-                PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len),
-                PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len),
-                PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len),
-                PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len),
-                PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len),
-                PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len),
-                PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len),
-                PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len),
-                PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len),
-                PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len),
-                PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len),
-                PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len),
+                PARSE_FIELD_VEC_ENTRY("_PID=",                        &pid,               &pid_len              ),
+                PARSE_FIELD_VEC_ENTRY("_COMM=",                       &comm,              &comm_len             ),
+                PARSE_FIELD_VEC_ENTRY("MESSAGE=",                     &message,           &message_len          ),
+                PARSE_FIELD_VEC_ENTRY("PRIORITY=",                    &priority,          &priority_len         ),
+                PARSE_FIELD_VEC_ENTRY("_TRANSPORT=",                  &transport,         &transport_len        ),
+                PARSE_FIELD_VEC_ENTRY("_HOSTNAME=",                   &hostname,          &hostname_len         ),
+                PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=",                  &fake_pid,          &fake_pid_len         ),
+                PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=",           &identifier,        &identifier_len       ),
+                PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=",                 &config_file,       &config_file_len      ),
+                PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=",               &unit,              &unit_len             ),
+                PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=",          &user_unit,         &user_unit_len        ),
+                PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=",               &documentation_url, &documentation_url_len),
+                PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=",  &realtime,          NULL                  ),
+                PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic,         NULL                  ),
         };
         size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0};
 
         assert(f);
         assert(j);
-        assert(display_ts);
-        assert(boot_id);
         assert(previous_display_ts);
         assert(previous_boot_id);
 
@@ -526,6 +560,8 @@ static int output_short(
         if (identifier && set_contains(j->exclude_syslog_identifiers, identifier))
                 return 0;
 
+        parse_display_timestamp(j, realtime, monotonic, &display_ts, &boot_id);
+
         if (!(flags & OUTPUT_SHOW_ALL))
                 strip_tab_ansi(&message, &message_len, highlight_shifted);
 
@@ -538,9 +574,9 @@ static int output_short(
         audit = streq_ptr(transport, "audit");
 
         if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA))
-                r = output_timestamp_monotonic(f, mode, display_ts, boot_id, previous_display_ts, previous_boot_id);
+                r = output_timestamp_monotonic(f, mode, &display_ts, &boot_id, previous_display_ts, previous_boot_id);
         else
-                r = output_timestamp_realtime(f, j, mode, flags, display_ts);
+                r = output_timestamp_realtime(f, j, mode, flags, &display_ts);
         if (r < 0)
                 return r;
         n += r;
@@ -661,9 +697,51 @@ static int output_short(
         if (flags & OUTPUT_CATALOG)
                 (void) print_catalog(f, j);
 
+        *previous_display_ts = display_ts;
+        *previous_boot_id = boot_id;
+
         return ellipsized;
 }
 
+static int get_display_timestamp(
+                sd_journal *j,
+                dual_timestamp *ret_display_ts,
+                sd_id128_t *ret_boot_id) {
+
+        const void *data;
+        _cleanup_free_ char *realtime = NULL, *monotonic = NULL;
+        size_t length;
+        const ParseFieldVec message_fields[] = {
+                PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=",  &realtime,  NULL),
+                PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL),
+        };
+        int r;
+
+        assert(j);
+        assert(ret_display_ts);
+        assert(ret_boot_id);
+
+        JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
+                r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields));
+                if (r < 0)
+                        return r;
+
+                if (realtime && monotonic)
+                        break;
+        }
+        if (r < 0)
+                return r;
+
+        (void) parse_display_timestamp(j, realtime, monotonic, ret_display_ts, ret_boot_id);
+
+        /* Restart all data before */
+        sd_journal_restart_data(j);
+        sd_journal_restart_unique(j);
+        sd_journal_restart_fields(j);
+
+        return 0;
+}
+
 static int output_verbose(
                 FILE *f,
                 sd_journal *j,
@@ -672,35 +750,39 @@ static int output_verbose(
                 OutputFlags flags,
                 const Set *output_fields,
                 const size_t highlight[2],
-                const dual_timestamp *display_ts,
-                const sd_id128_t *boot_id,
-                const dual_timestamp *previous_display_ts,
-                const sd_id128_t *previous_boot_id) {
+                dual_timestamp *previous_display_ts, /* unused */
+                sd_id128_t *previous_boot_id) {      /* unused */
 
         const void *data;
         size_t length;
         _cleanup_free_ char *cursor = NULL;
         char buf[FORMAT_TIMESTAMP_MAX + 7];
+        dual_timestamp display_ts;
+        sd_id128_t boot_id;
         const char *timestamp;
         int r;
 
         assert(f);
         assert(j);
-        assert(display_ts);
-        assert(boot_id);
-        assert(previous_display_ts);
-        assert(previous_boot_id);
 
         (void) sd_journal_set_data_threshold(j, 0);
 
-        if (!VALID_REALTIME(display_ts->realtime))
+        r = get_display_timestamp(j, &display_ts, &boot_id);
+        if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
+                log_debug_errno(r, "Skipping message we can't read: %m");
+                return 0;
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to get journal fields: %m");
+
+        if (!VALID_REALTIME(display_ts.realtime))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available");
 
         r = sd_journal_get_cursor(j, &cursor);
         if (r < 0)
                 return log_error_errno(r, "Failed to get cursor: %m");
 
-        timestamp = format_timestamp_style(buf, sizeof buf, display_ts->realtime,
+        timestamp = format_timestamp_style(buf, sizeof buf, display_ts.realtime,
                                            flags & OUTPUT_UTC ? TIMESTAMP_US_UTC : TIMESTAMP_US);
         fprintf(f, "%s%s%s %s[%s]%s\n",
                 timestamp && (flags & OUTPUT_COLOR) ? ANSI_UNDERLINE : "",
@@ -789,10 +871,8 @@ static int output_export(
                 OutputFlags flags,
                 const Set *output_fields,
                 const size_t highlight[2],
-                const dual_timestamp *display_ts,
-                const sd_id128_t *boot_id,
-                const dual_timestamp *previous_display_ts,
-                const sd_id128_t *previous_boot_id) {
+                dual_timestamp *previous_display_ts, /* unused */
+                sd_id128_t *previous_boot_id) {      /* unused */
 
         sd_id128_t journal_boot_id, seqnum_id;
         _cleanup_free_ char *cursor = NULL;
@@ -803,10 +883,6 @@ static int output_export(
         int r;
 
         assert(j);
-        assert(display_ts);
-        assert(boot_id);
-        assert(previous_display_ts);
-        assert(previous_boot_id);
 
         (void) sd_journal_set_data_threshold(j, 0);
 
@@ -1058,10 +1134,8 @@ static int output_json(
                 OutputFlags flags,
                 const Set *output_fields,
                 const size_t highlight[2],
-                const dual_timestamp *display_ts,
-                const sd_id128_t *boot_id,
-                const dual_timestamp *previous_display_ts,
-                const sd_id128_t *previous_boot_id) {
+                dual_timestamp *previous_display_ts, /* unused */
+                sd_id128_t *previous_boot_id) {      /* unused */
 
         char usecbuf[CONST_MAX(DECIMAL_STR_MAX(usec_t), DECIMAL_STR_MAX(uint64_t))];
         _cleanup_(json_variant_unrefp) JsonVariant *object = NULL;
@@ -1076,10 +1150,6 @@ static int output_json(
         int r;
 
         assert(j);
-        assert(display_ts);
-        assert(boot_id);
-        assert(previous_display_ts);
-        assert(previous_boot_id);
 
         (void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD);
 
@@ -1241,20 +1311,14 @@ static int output_cat(
                 OutputFlags flags,
                 const Set *output_fields,
                 const size_t highlight[2],
-                const dual_timestamp *display_ts,
-                const sd_id128_t *boot_id,
-                const dual_timestamp *previous_display_ts,
-                const sd_id128_t *previous_boot_id) {
+                dual_timestamp *previous_display_ts, /* unused */
+                sd_id128_t *previous_boot_id) {      /* unused */
 
         int r, prio = LOG_INFO;
         const char *field;
 
         assert(j);
         assert(f);
-        assert(display_ts);
-        assert(boot_id);
-        assert(previous_display_ts);
-        assert(previous_boot_id);
 
         (void) sd_journal_set_data_threshold(j, 0);
 
@@ -1294,63 +1358,6 @@ static int output_cat(
         return 0;
 }
 
-static int get_display_timestamp(
-                sd_journal *j,
-                dual_timestamp *ret_display_ts,
-                sd_id128_t *ret_boot_id) {
-
-        const void *data;
-        _cleanup_free_ char *realtime = NULL, *monotonic = NULL;
-        size_t length = 0, realtime_len = 0, monotonic_len = 0;
-        const ParseFieldVec message_fields[] = {
-                PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len),
-                PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len),
-        };
-        int r;
-        bool realtime_good = false, monotonic_good = false, boot_id_good = false;
-
-        assert(j);
-        assert(ret_display_ts);
-        assert(ret_boot_id);
-
-        JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
-                r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields));
-                if (r < 0)
-                        return r;
-
-                if (realtime && monotonic)
-                        break;
-        }
-        if (r < 0)
-                return r;
-
-        if (realtime)
-                realtime_good = safe_atou64(realtime, &ret_display_ts->realtime) >= 0;
-        if (!realtime_good || !VALID_REALTIME(ret_display_ts->realtime))
-                realtime_good = sd_journal_get_realtime_usec(j, &ret_display_ts->realtime) >= 0;
-        if (!realtime_good)
-                ret_display_ts->realtime = USEC_INFINITY;
-
-        if (monotonic)
-                monotonic_good = safe_atou64(monotonic, &ret_display_ts->monotonic) >= 0;
-        if (!monotonic_good || !VALID_MONOTONIC(ret_display_ts->monotonic))
-                monotonic_good = boot_id_good = sd_journal_get_monotonic_usec(j, &ret_display_ts->monotonic, ret_boot_id) >= 0;
-        if (!monotonic_good)
-                ret_display_ts->monotonic = USEC_INFINITY;
-
-        if (!boot_id_good)
-                boot_id_good = sd_journal_get_monotonic_usec(j, NULL, ret_boot_id) >= 0;
-        if (!boot_id_good)
-                *ret_boot_id = SD_ID128_NULL;
-
-        /* Restart all data before */
-        sd_journal_restart_data(j);
-        sd_journal_restart_unique(j);
-        sd_journal_restart_fields(j);
-
-        return 0;
-}
-
 typedef int (*output_func_t)(
                 FILE *f,
                 sd_journal *j,
@@ -1359,10 +1366,8 @@ typedef int (*output_func_t)(
                 OutputFlags flags,
                 const Set *output_fields,
                 const size_t highlight[2],
-                const dual_timestamp *display_ts,
-                const sd_id128_t *boot_id,
-                const dual_timestamp *previous_display_ts,
-                const sd_id128_t *previous_boot_id);
+                dual_timestamp *previous_display_ts,
+                sd_id128_t *previous_boot_id);
 
 
 static output_func_t output_funcs[_OUTPUT_MODE_MAX] = {
@@ -1396,8 +1401,6 @@ int show_journal_entry(
                 dual_timestamp *previous_display_ts,
                 sd_id128_t *previous_boot_id) {
 
-        dual_timestamp display_ts = DUAL_TIMESTAMP_NULL;
-        sd_id128_t boot_id = SD_ID128_NULL;
         int r;
 
         assert(mode >= 0);
@@ -1408,14 +1411,6 @@ int show_journal_entry(
         if (n_columns <= 0)
                 n_columns = columns();
 
-        r = get_display_timestamp(j, &display_ts, &boot_id);
-        if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
-                log_debug_errno(r, "Skipping message we can't read: %m");
-                return 0;
-        }
-        if (r < 0)
-                return log_error_errno(r, "Failed to get journal fields: %m");
-
         r = output_funcs[mode](
                         f,
                         j,
@@ -1424,15 +1419,9 @@ int show_journal_entry(
                         flags,
                         output_fields,
                         highlight,
-                        &display_ts,
-                        &boot_id,
                         previous_display_ts,
                         previous_boot_id);
 
-        /* Store timestamp and boot ID for next iteration */
-        *previous_display_ts = display_ts;
-        *previous_boot_id = boot_id;
-
         if (ellipsized && r > 0)
                 *ellipsized = true;
 
@@ -1801,7 +1790,10 @@ int show_journal_by_unit(
         if (how_many <= 0)
                 return 0;
 
-        r = sd_journal_open_namespace(&j, log_namespace, journal_open_flags | SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE);
+        r = sd_journal_open_namespace(&j, log_namespace,
+                                      journal_open_flags |
+                                      SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE |
+                                      SD_JOURNAL_ASSUME_IMMUTABLE);
         if (r < 0)
                 return log_error_errno(r, "Failed to open journal: %m");
 
index 4f4b675a37b9fa3a41f818aa81d2256fc6f79fe2..9acdaeff521e0615d9cf49f99ad6f69198e82218 100644 (file)
@@ -232,13 +232,23 @@ int device_is_renaming(sd_device *dev) {
 
         assert(dev);
 
-        r = sd_device_get_property_value(dev, "ID_RENAMING", NULL);
+        r = device_get_property_bool(dev, "ID_RENAMING");
         if (r == -ENOENT)
-                return false;
-        if (r < 0)
-                return r;
+                return false; /* defaults to false */
 
-        return true;
+        return r;
+}
+
+int device_is_processing(sd_device *dev) {
+        int r;
+
+        assert(dev);
+
+        r = device_get_property_bool(dev, "ID_PROCESSING");
+        if (r == -ENOENT)
+                return false; /* defaults to false */
+
+        return r;
 }
 
 bool device_for_action(sd_device *dev, sd_device_action_t a) {
index 4d27bed1d948f26ca0de104a6a2bb7546b6f027c..13710a3ec1f51e9ab8bac768ef49907c2d544c67 100644 (file)
@@ -13,6 +13,7 @@ int udev_parse_config(void);
 int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout_usec, sd_device **ret);
 int device_wait_for_devlink(const char *path, const char *subsystem, usec_t timeout_usec, sd_device **ret);
 int device_is_renaming(sd_device *dev);
+int device_is_processing(sd_device *dev);
 
 bool device_for_action(sd_device *dev, sd_device_action_t action);
 
index 7d2d75dd89a033b734285f6eee62e7377b8561e7..e4a67f048b8524111d3e1274d96a81844b8d8903 100644 (file)
@@ -72,6 +72,7 @@ enum {
         SD_JOURNAL_ALL_NAMESPACES            = 1 << 5, /* Show all namespaces, not just the default or specified one */
         SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE = 1 << 6, /* Show default namespace in addition to specified one */
         SD_JOURNAL_TAKE_DIRECTORY_FD         = 1 << 7, /* sd_journal_open_directory_fd() will take ownership of the provided file descriptor. */
+        SD_JOURNAL_ASSUME_IMMUTABLE          = 1 << 8, /* Assume the opened journal files are immutable. Journal entries added later may be ignored. */
 
         SD_JOURNAL_SYSTEM_ONLY _sd_deprecated_ = SD_JOURNAL_SYSTEM /* old name */
 };
index 4d75bf34f74766d79913f70f224d839fec9bd4bb..4541b0e0fb1ebefb093cfeea5c03aa6b48f02ff5 100644 (file)
@@ -171,6 +171,12 @@ static int rename_netif(UdevEvent *event) {
                 goto revert;
         }
 
+        r = device_add_property(event->dev_db_clone, "ID_PROCESSING", "1");
+        if (r < 0) {
+                log_device_warning_errno(event->dev_db_clone, r, "Failed to add 'ID_PROCESSING' property: %m");
+                goto revert;
+        }
+
         r = device_update_db(event->dev_db_clone);
         if (r < 0) {
                 log_device_debug_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m");
@@ -197,6 +203,7 @@ static int rename_netif(UdevEvent *event) {
 revert:
         /* Restore 'dev_db_clone' */
         (void) device_add_property(event->dev_db_clone, "ID_RENAMING", NULL);
+        (void) device_add_property(event->dev_db_clone, "ID_PROCESSING", NULL);
         (void) device_update_db(event->dev_db_clone);
 
         /* Restore 'dev' */
@@ -348,6 +355,18 @@ int udev_event_execute_rules(UdevEvent *event, UdevRules *rules) {
         if (r < 0)
                 return log_device_debug_errno(dev, r, "Failed to remove 'ID_RENAMING' property: %m");
 
+        /* If the database file already exists, append ID_PROCESSING property to the existing database,
+         * to indicate that the device is being processed by udevd. */
+        if (device_has_db(event->dev_db_clone) > 0) {
+                r = device_add_property(event->dev_db_clone, "ID_PROCESSING", "1");
+                if (r < 0)
+                        return log_device_warning_errno(event->dev_db_clone, r, "Failed to add 'ID_PROCESSING' property: %m");
+
+                r = device_update_db(event->dev_db_clone);
+                if (r < 0)
+                        return log_device_warning_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m");
+        }
+
         DEVICE_TRACE_POINT(rules_start, dev);
 
         r = udev_rules_apply_to_event(rules, event);
@@ -378,6 +397,15 @@ int udev_event_execute_rules(UdevEvent *event, UdevRules *rules) {
         if (r < 0)
                 return log_device_debug_errno(dev, r, "Failed to update tags under /run/udev/tag/: %m");
 
+        /* If the database file for the device will be created below, add ID_PROCESSING=1 to indicate that
+         * the device is still being processed by udevd, as commands specified in RUN are invoked after
+         * the database is created. See issue #30056. */
+        if (device_should_have_db(dev) && !ordered_hashmap_isempty(event->run_list)) {
+                r = device_add_property(dev, "ID_PROCESSING", "1");
+                if (r < 0)
+                        return log_device_warning_errno(dev, r, "Failed to add 'ID_PROCESSING' property: %m");
+        }
+
         r = device_update_db(dev);
         if (r < 0)
                 return log_device_debug_errno(dev, r, "Failed to update database under /run/udev/data/: %m");
index 63a93e53dc6d96890c60dcacc431c08420d266b6..4563d8807dd90e95f913094a17d9eb2694cad27c 100644 (file)
@@ -212,6 +212,15 @@ static int worker_process_device(UdevWorker *worker, sd_device *dev) {
                         log_device_warning_errno(dev, r, "Failed to add inotify watch, ignoring: %m");
         }
 
+        /* Finalize database. */
+        r = device_add_property(dev, "ID_PROCESSING", NULL);
+        if (r < 0)
+                return log_device_warning_errno(dev, r, "Failed to remove 'ID_PROCESSING' property: %m");
+
+        r = device_update_db(dev);
+        if (r < 0)
+                return log_device_warning_errno(dev, r, "Failed to update database under /run/udev/data/: %m");
+
         log_device_uevent(dev, "Device processed");
         return 0;
 }
index 9a4732c981dc9c78c9cf75edb1ab95d57890b2f5..0a5fa4d22e765ba293b94c6259d96176e8f0acd3 100755 (executable)
@@ -51,13 +51,13 @@ run_network_generator() {
     stderr="$WORK_DIR/stderr"
     if ! "$GENERATOR_BIN" --root "$WORK_DIR" 2>"$stderr"; then
         echo >&2 "Generator failed when parsing $SYSTEMD_PROC_CMDLINE"
-        cat "$stderr"
+        cat >&2 "$stderr"
         return 1
     fi
 
     if [[ -s "$stderr" ]]; then
         echo >&2 "Generator generated unexpected messages on stderr"
-        cat "$stderr"
+        cat >&2 "$stderr"
         return 1
     fi
 
@@ -226,7 +226,7 @@ for f in "$TEST_DATA"/test-*.input; do
     "$GENERATOR_BIN" --root "$out" -- $(cat "$f")
 
     if ! diff -u "$out/run/systemd/network" "${f%.input}.expected"; then
-        echo "**** Unexpected output for $f"
+        echo >&2 "**** Unexpected output for $f"
         exit 1
     fi
 
index 346ed4b2b33cc08b43c30395c007c32790a45c29..52461eef02883fbbdd865fbff067b0c863c15212 100755 (executable)
@@ -3693,7 +3693,6 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertRegex(output, 'inet6 .* scope link')
 
-    @unittest.skip("Re-enable once https://github.com/systemd/systemd/issues/30056 is resolved")
     def test_sysctl(self):
         copy_networkd_conf_dropin('25-global-ipv6-privacy-extensions.conf')
         copy_network_unit('25-sysctl.network', '12-dummy.netdev', copy_dropins=False)
index 5d66c6776d2daa104df9bbcb285bd85caa43130b..a66e33be278bb72feba176dc52bd20b9376e2108 100755 (executable)
@@ -36,12 +36,12 @@ preprocess() {
 
 compare() {
     if ! diff -u "$TESTDIR/etc/passwd" <(preprocess "$1.expected-passwd" "$3"); then
-        echo "**** Unexpected output for $f $2"
+        echo >&2 "**** Unexpected output for $f $2"
         exit 1
     fi
 
     if ! diff -u "$TESTDIR/etc/group" <(preprocess "$1.expected-group" "$3"); then
-        echo "**** Unexpected output for $f $2"
+        echo >&2 "**** Unexpected output for $f $2"
         exit 1
     fi
 }
@@ -168,8 +168,8 @@ for f in $(find "$SOURCE"/unhappy-*.input | sort -V); do
     cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
     $SYSUSERS --root="$TESTDIR" 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >"$TESTDIR/err"
     if ! diff -u "$TESTDIR/err"  "${f%.*}.expected-err"; then
-        echo "**** Unexpected error output for $f"
-        cat "$TESTDIR/err"
+        echo >&2 "**** Unexpected error output for $f"
+        cat >&2 "$TESTDIR/err"
         exit 1
     fi
 done
diff --git a/test/testsuite-23.units/testsuite-23-oneshot-restartforce.sh b/test/testsuite-23.units/testsuite-23-oneshot-restartforce.sh
new file mode 100755 (executable)
index 0000000..4c9e10b
--- /dev/null
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+if [[ -f "$1" ]]; then
+    exit 0
+fi
+
+touch "$1"
+exit 2
index 433cd698187351b4064e52e423474a3bc49b399d..bb4d664945e12622c36ca2ab492492d21281b737 100755 (executable)
@@ -3,12 +3,15 @@
 set -eux
 set -o pipefail
 
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
 # Test oneshot unit restart on failure
 
 # wait this many secs for each test service to succeed in what is being tested
 MAX_SECS=60
 
-systemd-analyze log-level debug
+systemctl log-level debug
 
 # test one: Restart=on-failure should restart the service
 (! systemd-run --unit=oneshot-restart-one -p Type=oneshot -p Restart=on-failure /bin/bash -c "exit 1")
@@ -21,7 +24,7 @@ if [[ "$(systemctl show oneshot-restart-one.service -P NRestarts)" -le 0 ]]; the
     exit 1
 fi
 
-TMP_FILE="/tmp/test-41-oneshot-restart-test"
+TMP_FILE="/tmp/test-23-oneshot-restart-test$RANDOM"
 
 : >$TMP_FILE
 
@@ -32,7 +35,7 @@ TMP_FILE="/tmp/test-41-oneshot-restart-test"
         -p StartLimitBurst=3 \
         -p Type=oneshot \
         -p Restart=on-failure \
-        -p ExecStart="/bin/bash -c \"printf a >>$TMP_FILE\"" /bin/bash -c "exit 1")
+        -p ExecStart="/bin/bash -c 'printf a >>$TMP_FILE'" /bin/bash -c "exit 1")
 
 # wait for at least 3 restarts
 for ((secs = 0; secs < MAX_SECS; secs++)); do
@@ -48,5 +51,51 @@ sleep 5
 if [[ $(cat $TMP_FILE) != "aaa" ]]; then
     exit 1
 fi
+rm "$TMP_FILE"
+
+# Test RestartForceExitStatus=. Note that success exit statuses are meant to be skipped
+
+TMP_FILE="/tmp/test-23-oneshot-restart-test$RANDOM"
+UNIT_NAME="testsuite-23-oneshot-restartforce.service"
+ONSUCCESS_UNIT_NAME="testsuite-23-oneshot-restartforce-onsuccess.service"
+FIFO_FILE="/tmp/test-23-oneshot-restart-test-fifo"
+
+cat >"/run/systemd/system/$UNIT_NAME" <<EOF
+[Unit]
+OnSuccess=$ONSUCCESS_UNIT_NAME
+
+[Service]
+Type=oneshot
+RestartForceExitStatus=0 2
+ExecStart=/usr/lib/systemd/tests/testdata/testsuite-23.units/testsuite-23-oneshot-restartforce.sh "$TMP_FILE"
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+cat >"/run/systemd/system/$ONSUCCESS_UNIT_NAME" <<EOF
+[Service]
+Type=oneshot
+ExecStart=bash -c 'echo finished >$FIFO_FILE'
+EOF
+
+mkfifo "$FIFO_FILE"
+
+# Pin the unit in memory
+systemctl enable "$UNIT_NAME"
+# Initial run should fail
+(! systemctl start "$UNIT_NAME")
+# Wait for OnSuccess=
+read -r x <"$FIFO_FILE"
+assert_eq "$x" "finished"
+
+cmp -b <(systemctl show "$UNIT_NAME" -p Result -p NRestarts -p SubState) <<EOF
+Result=success
+NRestarts=1
+SubState=dead
+EOF
+
+systemctl disable "$UNIT_NAME"
+rm "$TMP_FILE" /run/systemd/system/{"$UNIT_NAME","$ONSUCCESS_UNIT_NAME"} "$FIFO_FILE"
 
-systemd-analyze log-level info
+systemctl log-level info