journalctl: add new --sync switch for syncing the journal to disk
authorLennart Poettering <lennart@poettering.net>
Wed, 11 Nov 2015 11:59:09 +0000 (12:59 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 11 Nov 2015 12:39:18 +0000 (13:39 +0100)
With this new "--sync" switch we add a synchronous way to sync
everything queued to disk, and return only after that's complete. This
command gives the guarantee that anything queued before has hit the disk
before the command returns.

While we are at it, also improve the man pages and help text for
journalctl a bit.

man/journalctl.xml
man/systemd-journald.service.xml
src/journal/journalctl.c
src/journal/journald-server.c
src/journal/journald-server.h

index a319253..2160f3c 100644 (file)
       </varlistentry>
 
       <varlistentry>
+        <term><option>--sync</option></term>
+
+        <listitem><para>Ask the journal daemon to write all yet
+        unwritten journal data to the backing file system and
+        synchronize all journals. This call does not return until the
+        operation is complete. This command guarantees that any log
+        messages written before its invocation are safely stored on
+        disk at the time it returns.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><option>--flush</option></term>
 
         <listitem><para>Asks the journal daemon to flush any log data
         stored in <filename>/run/log/journal</filename> into
-        <filename>/var/log/journal</filename>, if persistent storage is
-        enabled. This call does not return until the operation is
-        complete.</para></listitem>
+        <filename>/var/log/journal</filename>, if persistent storage
+        is enabled. This call does not return until the operation is
+        complete. Note that this call is idempotent: the data is only
+        flushed from <filename>/run/log/journal</filename> into
+        <filename>/var/log/journal</filename> once during system
+        runtime, and this command exits cleanly without executing any
+        operation if this has already has happened. This command
+        effectively guarantees that all data is flushed to
+        <filename>/var/log/journal</filename> at the time it
+        returns.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index 21fd684..f1054b0 100644 (file)
@@ -131,15 +131,30 @@ systemd-tmpfiles --create --prefix /var/log/journal</programlisting>
         this is enabled). This must be used after
         <filename>/var/</filename> is mounted, as otherwise log data
         from <filename>/run</filename> is never flushed to
-        <filename>/var</filename> regardless of the
-        configuration.</para></listitem>
+        <filename>/var</filename> regardless of the configuration. The
+        <command>journalctl --flush</command> command uses this signal
+        to request flushing of the journal files, and then waits for
+        the operation to complete. See
+        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        for details.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term>SIGUSR2</term>
 
         <listitem><para>Request immediate rotation of the journal
-        files.</para></listitem>
+        files. The <command>journalctl --rotate</command> command uses
+        this signal to request journal file
+        rotation.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>SIGRTMIN+1</term>
+
+        <listitem><para>Request that all unwritten log data is written
+        to disk. The <command>journalctl --sync</command> command uses
+        this signal to trigger journal synchronization, and then waits
+        for the operation to complete.</para></listitem>
       </varlistentry>
     </variablelist>
   </refsect1>
@@ -261,7 +276,7 @@ systemd-tmpfiles --create --prefix /var/log/journal</programlisting>
       <citerefentry><refentrytitle>systemd-coredump</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
       <citerefentry project='die-net'><refentrytitle>setfacl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>sd_journal_print</refentrytitle><manvolnum>4</manvolnum></citerefentry>,
-      <command>pydoc systemd.journal</command>.
+      <command>pydoc systemd.journal</command>
     </para>
   </refsect1>
 
index 277adba..0713496 100644 (file)
@@ -135,6 +135,7 @@ static enum {
         ACTION_FLUSH,
         ACTION_ROTATE,
         ACTION_VACUUM,
+        ACTION_SYNC,
 } arg_action = ACTION_SHOW;
 
 typedef struct BootId {
@@ -201,7 +202,7 @@ static void help(void) {
 
         printf("%s [OPTIONS...] [MATCHES...]\n\n"
                "Query the journal.\n\n"
-               "Flags:\n"
+               "Options:\n"
                "     --system              Show the system journal\n"
                "     --user                Show the user journal for the current user\n"
                "  -M --machine=CONTAINER   Operate on local container\n"
@@ -234,7 +235,7 @@ static void help(void) {
                "  -m --merge               Show entries from all available journals\n"
                "  -D --directory=PATH      Show journal files from directory\n"
                "     --file=PATH           Show journal file\n"
-               "     --root=ROOT           Operate on catalog files underneath the root ROOT\n"
+               "     --root=ROOT           Operate on catalog files below a root directory\n"
 #ifdef HAVE_GCRYPT
                "     --interval=TIME       Time interval for changing the FSS sealing key\n"
                "     --verify-key=KEY      Specify FSS verification key\n"
@@ -244,20 +245,21 @@ static void help(void) {
                "  -h --help                Show this help text\n"
                "     --version             Show package version\n"
                "  -F --field=FIELD         List all values that a specified field takes\n"
-               "     --new-id128           Generate a new 128-bit ID\n"
                "     --disk-usage          Show total disk usage of all journal files\n"
                "     --vacuum-size=BYTES   Reduce disk usage below specified size\n"
                "     --vacuum-files=INT    Leave only the specified number of journal files\n"
                "     --vacuum-time=TIME    Remove journal files older than specified time\n"
+               "     --verify              Verify journal file consistency\n"
+               "     --sync                Synchronize unwritten journal messages to disk\n"
                "     --flush               Flush all journal data from /run into /var\n"
                "     --rotate              Request immediate rotation of the journal files\n"
                "     --header              Show journal header information\n"
                "     --list-catalog        Show all message IDs in the catalog\n"
                "     --dump-catalog        Show entries in the message catalog\n"
                "     --update-catalog      Update the message catalog database\n"
+               "     --new-id128           Generate a new 128-bit ID\n"
 #ifdef HAVE_GCRYPT
                "     --setup-keys          Generate a new FSS key pair\n"
-               "     --verify              Verify journal file consistency\n"
 #endif
                , program_invocation_short_name);
 }
@@ -289,6 +291,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UPDATE_CATALOG,
                 ARG_FORCE,
                 ARG_UTC,
+                ARG_SYNC,
                 ARG_FLUSH,
                 ARG_ROTATE,
                 ARG_VACUUM_SIZE,
@@ -345,6 +348,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "machine",        required_argument, NULL, 'M'                },
                 { "utc",            no_argument,       NULL, ARG_UTC            },
                 { "flush",          no_argument,       NULL, ARG_FLUSH          },
+                { "sync",           no_argument,       NULL, ARG_SYNC           },
                 { "rotate",         no_argument,       NULL, ARG_ROTATE         },
                 { "vacuum-size",    required_argument, NULL, ARG_VACUUM_SIZE    },
                 { "vacuum-files",   required_argument, NULL, ARG_VACUUM_FILES   },
@@ -729,6 +733,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_action = ACTION_ROTATE;
                         break;
 
+                case ARG_SYNC:
+                        arg_action = ACTION_SYNC;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1782,10 +1790,8 @@ static int flush_to_var(void) {
                         &error,
                         NULL,
                         "ssi", "systemd-journald.service", "main", SIGUSR1);
-        if (r < 0) {
-                log_error("Failed to kill journal service: %s", bus_error_message(&error, r));
-                return r;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r));
 
         mkdir_p("/run/systemd/journal", 0755);
 
@@ -1840,6 +1846,85 @@ static int rotate(void) {
         return 0;
 }
 
+static int sync_journal(void) {
+        _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
+        _cleanup_close_ int watch_fd = -1;
+        usec_t start;
+        int r;
+
+        start = now(CLOCK_REALTIME);
+
+        /* Let's watch /run/systemd/sync until it's mtime is above
+         * the time we started the sync. Let's enqueue SIGRTMIN+1 to
+         * start the sync. */
+
+        for (;;) {
+                struct stat st;
+
+                /* See if a sync happened by now. */
+                if (stat("/run/systemd/journal/synced", &st) < 0) {
+                        if (errno != ENOENT)
+                                return log_error_errno(errno, "Failed to stat /run/systemd/journal/synced: %m");
+                } else {
+                        if (timespec_load(&st.st_mtim) >= start)
+                                return 0;
+                }
+
+                /* Let's ask for a sync, but only once. */
+                if (!bus) {
+                        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+                        r = bus_connect_system_systemd(&bus);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to get D-Bus connection: %m");
+
+                        r = sd_bus_call_method(
+                                        bus,
+                                        "org.freedesktop.systemd1",
+                                        "/org/freedesktop/systemd1",
+                                        "org.freedesktop.systemd1.Manager",
+                                        "KillUnit",
+                                        &error,
+                                        NULL,
+                                        "ssi", "systemd-journald.service", "main", SIGRTMIN+1);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r));
+
+                        continue;
+                }
+
+                /* Let's install the inotify watch, if we didn't do that yet. */
+                if (watch_fd < 0) {
+
+                        mkdir_p("/run/systemd/journal", 0755);
+
+                        watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+                        if (watch_fd < 0)
+                                return log_error_errno(errno, "Failed to create inotify watch: %m");
+
+                        r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_CREATE|IN_ATTRIB|IN_DONT_FOLLOW|IN_ONLYDIR);
+                        if (r < 0)
+                                return log_error_errno(errno, "Failed to watch journal directory: %m");
+
+                        /* Recheck the flag file immediately, so that we don't miss any event since the last check. */
+                        continue;
+                }
+
+                /* OK, all preparatory steps done, let's wait until
+                 * inotify reports an event. */
+
+                r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to wait for event: %m");
+
+                r = flush_fd(watch_fd);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to flush inotify events: %m");
+        }
+
+        return 0;
+}
+
 int main(int argc, char *argv[]) {
         int r;
         _cleanup_journal_close_ sd_journal *j = NULL;
@@ -1875,6 +1960,11 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
+        if (arg_action == ACTION_SYNC) {
+                r = sync_journal();
+                goto finish;
+        }
+
         if (arg_action == ACTION_ROTATE) {
                 r = rotate();
                 goto finish;
index a6e5e4a..f0a3c82 100644 (file)
@@ -1243,7 +1243,7 @@ static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *
 
         assert(s);
 
-        log_info("Received request to flush runtime journal from PID %"PRIu32, si->ssi_pid);
+        log_info("Received request to flush runtime journal from PID " PID_FMT, si->ssi_pid);
 
         server_flush_to_var(s);
         server_sync(s);
@@ -1259,7 +1259,7 @@ static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *
 
         assert(s);
 
-        log_info("Received request to rotate journal from PID %"PRIu32, si->ssi_pid);
+        log_info("Received request to rotate journal from PID " PID_FMT, si->ssi_pid);
         server_rotate(s);
         server_vacuum(s, true, true);
 
@@ -1277,12 +1277,27 @@ static int dispatch_sigterm(sd_event_source *es, const struct signalfd_siginfo *
         return 0;
 }
 
+static int dispatch_sigrtmin1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
+        Server *s = userdata;
+
+        assert(s);
+
+        log_debug("Received request to sync from PID " PID_FMT, si->ssi_pid);
+
+        server_sync(s);
+
+        /* Let clients know when the most recent sync happened. */
+        (void) touch("/run/systemd/journal/synced");
+
+        return 0;
+}
+
 static int setup_signals(Server *s) {
         int r;
 
         assert(s);
 
-        assert(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, -1) >= 0);
+        assert(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGRTMIN+1, -1) >= 0);
 
         r = sd_event_add_signal(s->event, &s->sigusr1_event_source, SIGUSR1, dispatch_sigusr1, s);
         if (r < 0)
@@ -1312,6 +1327,19 @@ static int setup_signals(Server *s) {
         if (r < 0)
                 return r;
 
+        /* SIGRTMIN+1 causes an immediate sync. We process this very
+         * late, so that everything else queued at this point is
+         * really written to disk. Clients can watch
+         * /run/systemd/journal/synced with inotify until its mtime
+         * changes to see when a sync happened. */
+        r = sd_event_add_signal(s->event, &s->sigrtmin1_event_source, SIGRTMIN+1, dispatch_sigrtmin1, s);
+        if (r < 0)
+                return r;
+
+        r = sd_event_source_set_priority(s->sigrtmin1_event_source, SD_EVENT_PRIORITY_NORMAL+15);
+        if (r < 0)
+                return r;
+
         return 0;
 }
 
@@ -1869,6 +1897,7 @@ void server_done(Server *s) {
         sd_event_source_unref(s->sigusr2_event_source);
         sd_event_source_unref(s->sigterm_event_source);
         sd_event_source_unref(s->sigint_event_source);
+        sd_event_source_unref(s->sigrtmin1_event_source);
         sd_event_source_unref(s->hostname_event_source);
         sd_event_source_unref(s->notify_event_source);
         sd_event_source_unref(s->watchdog_event_source);
index 03a61bd..dcc21bb 100644 (file)
@@ -72,6 +72,7 @@ struct Server {
         sd_event_source *sigusr2_event_source;
         sd_event_source *sigterm_event_source;
         sd_event_source *sigint_event_source;
+        sd_event_source *sigrtmin1_event_source;
         sd_event_source *hostname_event_source;
         sd_event_source *notify_event_source;
         sd_event_source *watchdog_event_source;