logs-show: extend journal_get_boots() and friends to find invocation IDs
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 25 Apr 2024 06:28:19 +0000 (15:28 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 31 Jul 2024 22:31:44 +0000 (07:31 +0900)
Currently the extended features are not used, but will be used later.

src/shared/logs-show.c
src/shared/logs-show.h

index 7f8171af49ee5def9b7b9f24a6677db0ddaab04d..96955497a292ff854a77c62f608593bc0189ee6a 100644 (file)
@@ -1799,8 +1799,117 @@ int show_journal_by_unit(
         return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized);
 }
 
-static int discover_next_boot(
+static int journal_get_id128(sd_journal *j, const char *field, sd_id128_t *ret) {
+        const char *data, *str;
+        size_t len, fl;
+        sd_id128_t v;
+        int r;
+
+        assert(j);
+        assert(field);
+
+        /* This returns 1 when non-null ID is found, 0 when null ID is found, -ENOENT when the field not
+         * found, -EINVAL when the field found but not a valid ID, -EBADMSG or -EADDRNOTAVAIL when journal
+         * corruption detected. On a critical issue, other negative errno may be returned. */
+
+        r = sd_journal_get_data(j, field, (const void**) &data, &len);
+        if (r < 0)
+                return r;
+
+        fl = strlen(field);
+        assert(len > fl);
+        assert(data[fl] == '=');
+
+        if (len > fl + 1 + SD_ID128_UUID_STRING_MAX)
+                return -EINVAL;
+
+        str = memdupa_suffix0(data + fl + 1, len - fl - 1);
+
+        r = sd_id128_from_string(str, ret ?: &v);
+        if (r < 0)
+                return r;
+
+        return !sd_id128_is_null(ret ? *ret : v);
+}
+
+static int journal_get_invocation_id(sd_journal *j, sd_id128_t *ret) {
+        int r;
+
+        assert(j);
+
+        FOREACH_STRING(s,
+                       "_SYSTEMD_INVOCATION_ID",       /* By the systemd unit. */
+                       "OBJECT_SYSTEMD_INVOCATION_ID", /* Added by journald. */
+                       "INVOCATION_ID",                /* By the system service manager (PID 1). */
+                       "USER_INVOCATION_ID") {         /* By the user session service manager. */
+
+                r = journal_get_id128(j, s, ret);
+                if (!IN_SET(r, 0, -ENOENT, -EINVAL, -EBADMSG, -EADDRNOTAVAIL))
+                        return r;
+        }
+
+        /* No invocation ID found in the entry. */
+        if (ret)
+                *ret = SD_ID128_NULL;
+        return 0;
+}
+
+static const char* const log_id_type_table[_LOG_ID_TYPE_MAX] = {
+        [LOG_BOOT_ID]                   = "boot ID",
+        [LOG_SYSTEM_UNIT_INVOCATION_ID] = "system unit invocation ID",
+        [LOG_USER_UNIT_INVOCATION_ID]   = "user unit invocation ID",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(log_id_type, LogIdType);
+
+static int set_matches_for_discover_id(
                 sd_journal *j,
+                LogIdType type,
+                sd_id128_t boot_id,
+                const char *unit,
+                sd_id128_t id) {
+
+        int r;
+
+        assert(j);
+        assert(type >= 0 && type < _LOG_ID_TYPE_MAX);
+
+        sd_journal_flush_matches(j);
+
+        if (type == LOG_BOOT_ID) {
+                if (sd_id128_is_null(id))
+                        return 0;
+
+                return add_match_boot_id(j, id);
+        }
+
+        if (!sd_id128_is_null(boot_id)) {
+                r = add_match_boot_id(j, boot_id);
+                if (r < 0)
+                        return r;
+
+                r = sd_journal_add_conjunction(j);
+                if (r < 0)
+                        return r;
+        }
+
+        if (!sd_id128_is_null(id))
+                return add_matches_for_invocation_id(j, id);
+
+        if (type == LOG_SYSTEM_UNIT_INVOCATION_ID)
+                return add_matches_for_unit_full(j, /* all = */ false, unit);
+
+        if (type == LOG_USER_UNIT_INVOCATION_ID)
+                return add_matches_for_user_unit_full(j, /* all = */ false, unit);
+
+        return -EINVAL;
+}
+
+static int discover_next_id(
+                sd_journal *j,
+                LogIdType type,
+                sd_id128_t boot_id,  /* optional, used when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID */
+                const char *unit,    /* mandatory when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID */
                 sd_id128_t previous_id,
                 bool advance_older,
                 LogId *ret) {
@@ -1809,6 +1918,8 @@ static int discover_next_boot(
         int r;
 
         assert(j);
+        assert(type >= 0 && type < _LOG_ID_TYPE_MAX);
+        assert(type == LOG_BOOT_ID || unit);
         assert(ret);
 
         /* We expect the journal to be on the last position of a boot
@@ -1820,7 +1931,9 @@ static int discover_next_boot(
 
         /* Make sure we aren't restricted by any _BOOT_ID matches, so that
          * we can actually advance to a *different* boot. */
-        sd_journal_flush_matches(j);
+        r = set_matches_for_discover_id(j, type, boot_id, unit, SD_ID128_NULL);
+        if (r < 0)
+                return r;
 
         for (;;) {
                 sd_id128_t *id_dup;
@@ -1835,15 +1948,22 @@ static int discover_next_boot(
                         return 0; /* End of journal, yay. */
                 }
 
-                r = sd_journal_get_monotonic_usec(j, NULL, &id.id);
+                if (type == LOG_BOOT_ID)
+                        r = sd_journal_get_monotonic_usec(j, NULL, &id.id);
+                else
+                        r = journal_get_invocation_id(j, &id.id);
                 if (r < 0)
                         return r;
 
-                /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that
-                 * normally, this will only require a single iteration, as we moved to the last entry of the previous
-                 * boot entry already. However, it might happen that the per-journal-field entry arrays are less
-                 * complete than the main entry array, and hence might reference an entry that's not actually the last
-                 * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to
+                if (sd_id128_is_null(id.id))
+                        continue;
+
+                /* We iterate through this in a loop, until the boot or invocation ID differs from the
+                 * previous one. Note that normally, this will only require a single iteration, as we moved
+                 * to the last entry of the previous boot or invocation entry already. However, it might
+                 * happen that the per-journal-field entry arrays are less complete than the main entry
+                 * array, and hence might reference an entry that's not actually the last one of the boot or
+                 * invocation ID as last one. Let's hence use the per-field array is initial seek position to
                  * speed things up, but let's not trust that it is complete, and hence, manually advance as
                  * necessary. */
 
@@ -1853,16 +1973,17 @@ static int discover_next_boot(
                 if (set_contains(broken_ids, &id.id))
                         continue;
 
-                /* Yay, we found a new boot ID from the entry object. Let's check there exist corresponding
-                 * entries matching with the _BOOT_ID= data. */
+                /* Yay, we found a new boot or invocation ID from the entry object. Let's check there exist
+                 * corresponding entries matching with the _BOOT_ID=, INVOCATION_ID= or friends data. */
 
-                r = add_match_boot_id(j, id.id);
+                r = set_matches_for_discover_id(j, type, boot_id, unit, id.id);
                 if (r < 0)
                         return r;
 
-                /* First, seek to the first (or the last when we are going upwards) occurrence of this boot ID.
-                 * You may think this is redundant. Yes, that's redundant unless the journal is corrupted.
-                 * But when the journal is corrupted, especially, badly 'truncated', then the below may fail.
+                /* First, seek to the first (or the last when we are going upwards) occurrence of this boot
+                 * or invocation ID. You may think this is redundant. Yes, that's redundant unless the
+                 * journal is corrupted. But when the journal is corrupted, especially, badly 'truncated',
+                 * then the below may fail.
                  * See https://github.com/systemd/systemd/pull/29334#issuecomment-1736567951. */
                 if (advance_older)
                         r = sd_journal_seek_tail(j);
@@ -1875,9 +1996,11 @@ static int discover_next_boot(
                 if (r < 0)
                         return r;
                 if (r == 0) {
-                        log_debug("Whoopsie! We found a boot ID %s but can't read its first entry. "
-                                  "The journal seems to be corrupted. Ignoring the boot ID.",
-                                  SD_ID128_TO_STRING(id.id));
+                        log_debug("Whoopsie! We found a %s %s but can't read its first entry. "
+                                  "The journal seems to be corrupted. Ignoring the %s.",
+                                  log_id_type_to_string(type),
+                                  SD_ID128_TO_STRING(id.id),
+                                  log_id_type_to_string(type));
                         goto try_again;
                 }
 
@@ -1885,7 +2008,7 @@ static int discover_next_boot(
                 if (r < 0)
                         return r;
 
-                /* Next, seek to the last occurrence of this boot ID. */
+                /* Next, seek to the last occurrence of this boot or invocation ID. */
                 if (advance_older)
                         r = sd_journal_seek_head(j);
                 else
@@ -1897,9 +2020,11 @@ static int discover_next_boot(
                 if (r < 0)
                         return r;
                 if (r == 0) {
-                        log_debug("Whoopsie! We found a boot ID %s but can't read its last entry. "
-                                  "The journal seems to be corrupted. Ignoring the boot ID.",
-                                  SD_ID128_TO_STRING(id.id));
+                        log_debug("Whoopsie! We found a %s %s but can't read its last entry. "
+                                  "The journal seems to be corrupted. Ignoring the %s.",
+                                  log_id_type_to_string(type),
+                                  SD_ID128_TO_STRING(id.id),
+                                  log_id_type_to_string(type));
                         goto try_again;
                 }
 
@@ -1912,7 +2037,7 @@ static int discover_next_boot(
                 return 1;
 
         try_again:
-                /* Save the bad boot ID. */
+                /* Save the bad boot or invocation ID. */
                 id_dup = newdup(sd_id128_t, &id.id, 1);
                 if (!id_dup)
                         return -ENOMEM;
@@ -1922,13 +2047,9 @@ static int discover_next_boot(
                         return r;
 
                 /* Move to the previous position again. */
-                sd_journal_flush_matches(j);
-
-                if (!sd_id128_is_null(previous_id)) {
-                        r = add_match_boot_id(j, previous_id);
-                        if (r < 0)
-                                return r;
-                }
+                r = set_matches_for_discover_id(j, type, boot_id, unit, previous_id);
+                if (r < 0)
+                        return r;
 
                 if (advance_older)
                         r = sd_journal_seek_head(j);
@@ -1942,31 +2063,41 @@ static int discover_next_boot(
                         return r;
                 if (r == 0)
                         return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
-                                               "Whoopsie! Cannot seek to the last entry of boot %s.",
+                                               "Whoopsie! Cannot seek to the last entry of %s %s.",
+                                               log_id_type_to_string(type),
                                                SD_ID128_TO_STRING(previous_id));
 
                 sd_journal_flush_matches(j);
         }
 }
 
-int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t *ret) {
+int journal_find_log_id(
+                sd_journal *j,
+                LogIdType type,
+                sd_id128_t boot_id,  /* optional, used when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID */
+                const char *unit,    /* mandatory when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID
+                                      * unless an invocation ID is explicitly specified without an offset. */
+                sd_id128_t previous_id,
+                int offset,
+                sd_id128_t *ret) {
+
         bool advance_older;
         int r, offset_start;
 
         assert(j);
+        assert(type >= 0 && type < _LOG_ID_TYPE_MAX);
+        assert(type == LOG_BOOT_ID || (!sd_id128_is_null(previous_id) && offset == 0) || unit);
         assert(ret);
 
-        /* Adjust for the asymmetry that offset 0 is the last (and current) boot, while 1 is considered the
-         * (chronological) first boot in the journal. */
+        /* Adjust for the asymmetry that offset 0 is the last (and current) boot or invocation, while 1 is
+         * considered the (chronological) first boot or invocation in the journal. */
         advance_older = offset <= 0;
 
-        sd_journal_flush_matches(j);
-
-        if (!sd_id128_is_null(boot_id)) {
-                r = add_match_boot_id(j, boot_id);
-                if (r < 0)
-                        return r;
+        r = set_matches_for_discover_id(j, type, boot_id, unit, previous_id);
+        if (r < 0)
+                return r;
 
+        if (!sd_id128_is_null(previous_id)) {
                 if (advance_older)
                         r = sd_journal_seek_head(j); /* seek to oldest */
                 else
@@ -1983,9 +2114,9 @@ int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t
                         return false;
                 }
                 if (offset == 0) {
-                        /* If boot ID is specified without an offset, then let's short cut the loop below. */
+                        /* If a non-null ID is specified without an offset, then let's short cut the loop below. */
                         sd_journal_flush_matches(j);
-                        *ret = boot_id;
+                        *ret = previous_id;
                         return true;
                 }
 
@@ -2001,14 +2132,14 @@ int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t
                 offset_start = advance_older ? 0 : 1;
         }
 
-        /* At this point the cursor is positioned at the newest/oldest entry of the reference boot ID if
-         * specified, or whole journal otherwise. The next invocation of _previous()/_next() will hence
-         * position us at the newest/oldest entry we have. */
+        /* At this point the cursor is positioned at the newest/oldest entry of the reference boot or
+         * invocation ID if specified, or whole journal otherwise. The next invocation of _previous()/_next()
+         * will hence position us at the newest/oldest entry we have. */
 
         for (int off = offset_start; ; off += advance_older ? -1 : 1) {
                 LogId id;
 
-                r = discover_next_boot(j, boot_id, advance_older, &id);
+                r = discover_next_id(j, type, boot_id, unit, previous_id, advance_older, &id);
                 if (r < 0)
                         return r;
                 if (r == 0) {
@@ -2016,18 +2147,22 @@ int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t
                         return false;
                 }
 
-                boot_id = id.id;
-                log_debug("Found boot ID %s by offset %i", SD_ID128_TO_STRING(boot_id), off);
+                previous_id = id.id;
+                log_debug("Found %s %s by offset %i.",
+                          log_id_type_to_string(type), SD_ID128_TO_STRING(previous_id), off);
 
                 if (off == offset) {
-                        *ret = boot_id;
+                        *ret = previous_id;
                         return true;
                 }
         }
 }
 
-int journal_get_boots(
+int journal_get_log_ids(
                 sd_journal *j,
+                LogIdType type,
+                sd_id128_t boot_id,  /* optional, used when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID */
+                const char *unit,    /* mandatory when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID */
                 bool advance_older,
                 size_t max_ids,
                 LogId **ret_ids,
@@ -2038,6 +2173,8 @@ int journal_get_boots(
         int r;
 
         assert(j);
+        assert(type >= 0 && type < _LOG_ID_TYPE_MAX);
+        assert(type == LOG_BOOT_ID || unit);
         assert(ret_ids);
         assert(ret_n_ids);
 
@@ -2062,7 +2199,7 @@ int journal_get_boots(
                 if (n_ids >= max_ids)
                         break;
 
-                r = discover_next_boot(j, previous_id, advance_older, &id);
+                r = discover_next_id(j, type, boot_id, unit, previous_id, advance_older, &id);
                 if (r < 0)
                         return r;
                 if (r == 0)
@@ -2072,8 +2209,9 @@ int journal_get_boots(
 
                 FOREACH_ARRAY(i, ids, n_ids)
                         if (sd_id128_equal(i->id, id.id))
-                                /* The boot id is already stored, something wrong with the journal files.
-                                 * Exiting as otherwise this problem would cause an infinite loop. */
+                                /* The boot or invocation ID is already stored, something wrong with the
+                                 * journal files. Exiting as otherwise this problem would cause an infinite
+                                 * loop. */
                                 goto finish;
 
                 if (!GREEDY_REALLOC_APPEND(ids, n_ids, &id, 1))
index 7d7a918179f74ea6c146bf744a118af970fb9a28..d000978c04751b6bcc4c63223b21ae0f96a5731e 100644 (file)
@@ -20,6 +20,14 @@ typedef struct LogId {
         usec_t last_usec;
 } LogId;
 
+typedef enum LogIdType {
+        LOG_BOOT_ID,
+        LOG_SYSTEM_UNIT_INVOCATION_ID,
+        LOG_USER_UNIT_INVOCATION_ID,
+        _LOG_ID_TYPE_MAX,
+        _LOG_ID_TYPE_INVALID = -EINVAL,
+} LogIdType;
+
 int show_journal_entry(
                 FILE *f,
                 sd_journal *j,
@@ -74,10 +82,45 @@ void json_escape(
                 size_t l,
                 OutputFlags flags);
 
-int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t *ret);
-int journal_get_boots(
+int journal_find_log_id(
+                sd_journal *j,
+                LogIdType type,
+                sd_id128_t boot_id,
+                const char *unit,
+                sd_id128_t id,
+                int offset,
+                sd_id128_t *ret);
+
+static inline int journal_find_boot(
+                sd_journal *j,
+                sd_id128_t id,
+                int offset,
+                sd_id128_t *ret) {
+
+        return journal_find_log_id(j, LOG_BOOT_ID,
+                                   /* boot_id = */ SD_ID128_NULL, /* unit = */ NULL,
+                                   id, offset, ret);
+}
+
+int journal_get_log_ids(
                 sd_journal *j,
+                LogIdType type,
+                sd_id128_t boot_id,
+                const char *unit,
                 bool advance_older,
                 size_t max_ids,
                 LogId **ret_ids,
                 size_t *ret_n_ids);
+
+static inline int journal_get_boots(
+                sd_journal *j,
+                bool advance_older,
+                size_t max_ids,
+                LogId **ret_ids,
+                size_t *ret_n_ids) {
+
+        return journal_get_log_ids(j, LOG_BOOT_ID,
+                                   /* boot_id = */ SD_ID128_NULL, /* unit = */ NULL,
+                                   advance_older, max_ids,
+                                   ret_ids, ret_n_ids);
+}