From 6ecee6cf2b38d9ca35de9fd5e2034099aac1870b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 25 Apr 2024 10:10:39 +0900 Subject: [PATCH 1/8] logs-show: rename BootId -> LogId The struct itself is generic, and can be used for other ID. Let's rename it to more generic one. No functional change, just refactoring and preparation for later commits. --- src/journal/journalctl-misc.c | 16 ++-- .../sd-journal/test-journal-interleaving.c | 56 +++++++------- src/shared/logs-show.c | 74 +++++++++---------- src/shared/logs-show.h | 10 +-- 4 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/journal/journalctl-misc.c b/src/journal/journalctl-misc.c index 2a5d9edfc3..880a0900eb 100644 --- a/src/journal/journalctl-misc.c +++ b/src/journal/journalctl-misc.c @@ -101,8 +101,8 @@ int action_disk_usage(void) { int action_list_boots(void) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_(table_unrefp) Table *table = NULL; - _cleanup_free_ BootId *boots = NULL; - size_t n_boots; + _cleanup_free_ LogId *ids = NULL; + size_t n_ids; int r; assert(arg_action == ACTION_LIST_BOOTS); @@ -115,7 +115,7 @@ int action_list_boots(void) { j, /* advance_older = */ arg_lines_needs_seek_end(), /* max_ids = */ arg_lines >= 0 ? (size_t) arg_lines : SIZE_MAX, - &boots, &n_boots); + &ids, &n_ids); if (r < 0) return log_error_errno(r, "Failed to determine boots: %m"); if (r == 0) @@ -136,7 +136,7 @@ int action_list_boots(void) { (void) table_set_sort(table, (size_t) 0); (void) table_set_reverse(table, 0, arg_reverse); - for (int i = 0; i < (int) n_boots; i++) { + for (int i = 0; i < (int) n_ids; i++) { int index; if (arg_lines_needs_seek_end()) @@ -147,14 +147,14 @@ int action_list_boots(void) { index = i + 1; else /* Otherwise, show negative index. Note, in this case, newer ID is located earlier. */ - index = i + 1 - (int) n_boots; + index = i + 1 - (int) n_ids; r = table_add_many(table, TABLE_INT, index, TABLE_SET_ALIGN_PERCENT, 100, - TABLE_ID128, boots[i].id, - TABLE_TIMESTAMP, boots[i].first_usec, - TABLE_TIMESTAMP, boots[i].last_usec); + TABLE_ID128, ids[i].id, + TABLE_TIMESTAMP, ids[i].first_usec, + TABLE_TIMESTAMP, ids[i].last_usec); if (r < 0) return table_log_add_error(r); } diff --git a/src/libsystemd/sd-journal/test-journal-interleaving.c b/src/libsystemd/sd-journal/test-journal-interleaving.c index d98b3ce8cb..c1b7e23de5 100644 --- a/src/libsystemd/sd-journal/test-journal-interleaving.c +++ b/src/libsystemd/sd-journal/test-journal-interleaving.c @@ -457,11 +457,11 @@ TEST(skip) { test_skip_one(setup_interleaved); } -static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) { +static void test_boot_id_one(void (*setup)(void), size_t n_ids_expected) { char t[] = "/var/tmp/journal-boot-id-XXXXXX"; _cleanup_(sd_journal_closep) sd_journal *j = NULL; - _cleanup_free_ BootId *boots = NULL; - size_t n_boots; + _cleanup_free_ LogId *ids = NULL; + size_t n_ids; mkdtemp_chdir_chattr(t); @@ -471,55 +471,55 @@ static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) { assert_se(journal_get_boots( j, /* advance_older = */ false, /* max_ids = */ SIZE_MAX, - &boots, &n_boots) >= 0); - assert_se(boots); - assert_se(n_boots == n_boots_expected); + &ids, &n_ids) >= 0); + assert_se(ids); + assert_se(n_ids == n_ids_expected); - for (size_t i = 0; i < n_boots; i++) { + for (size_t i = 0; i < n_ids; i++) { sd_id128_t id; /* positive offset */ assert_se(journal_find_boot(j, SD_ID128_NULL, (int) (i + 1), &id) == 1); - assert_se(sd_id128_equal(id, boots[i].id)); + assert_se(sd_id128_equal(id, ids[i].id)); /* negative offset */ - assert_se(journal_find_boot(j, SD_ID128_NULL, (int) (i + 1) - (int) n_boots, &id) == 1); - assert_se(sd_id128_equal(id, boots[i].id)); + assert_se(journal_find_boot(j, SD_ID128_NULL, (int) (i + 1) - (int) n_ids, &id) == 1); + assert_se(sd_id128_equal(id, ids[i].id)); - for (size_t k = 0; k < n_boots; k++) { + for (size_t k = 0; k < n_ids; k++) { int offset = (int) k - (int) i; /* relative offset */ - assert_se(journal_find_boot(j, boots[i].id, offset, &id) == 1); - assert_se(sd_id128_equal(id, boots[k].id)); + assert_se(journal_find_boot(j, ids[i].id, offset, &id) == 1); + assert_se(sd_id128_equal(id, ids[k].id)); } } - for (size_t i = 0; i <= n_boots_expected + 1; i++) { - _cleanup_free_ BootId *boots_limited = NULL; - size_t n_boots_limited; + for (size_t i = 0; i <= n_ids_expected + 1; i++) { + _cleanup_free_ LogId *ids_limited = NULL; + size_t n_ids_limited; assert_se(journal_get_boots( j, /* advance_older = */ false, /* max_ids = */ i, - &boots_limited, &n_boots_limited) >= 0); - assert_se(boots_limited || i == 0); - assert_se(n_boots_limited == MIN(i, n_boots_expected)); - assert_se(memcmp_safe(boots, boots_limited, n_boots_limited * sizeof(BootId)) == 0); + &ids_limited, &n_ids_limited) >= 0); + assert_se(ids_limited || i == 0); + assert_se(n_ids_limited == MIN(i, n_ids_expected)); + assert_se(memcmp_safe(ids, ids_limited, n_ids_limited * sizeof(LogId)) == 0); } - for (size_t i = 0; i <= n_boots_expected + 1; i++) { - _cleanup_free_ BootId *boots_limited = NULL; - size_t n_boots_limited; + for (size_t i = 0; i <= n_ids_expected + 1; i++) { + _cleanup_free_ LogId *ids_limited = NULL; + size_t n_ids_limited; assert_se(journal_get_boots( j, /* advance_older = */ true, /* max_ids = */ i, - &boots_limited, &n_boots_limited) >= 0); - assert_se(boots_limited || i == 0); - assert_se(n_boots_limited == MIN(i, n_boots_expected)); - for (size_t k = 0; k < n_boots_limited; k++) - assert_se(memcmp(&boots[n_boots - k - 1], &boots_limited[k], sizeof(BootId)) == 0); + &ids_limited, &n_ids_limited) >= 0); + assert_se(ids_limited || i == 0); + assert_se(n_ids_limited == MIN(i, n_ids_expected)); + for (size_t k = 0; k < n_ids_limited; k++) + assert_se(memcmp(&ids[n_ids - k - 1], &ids_limited[k], sizeof(LogId)) == 0); } test_done(t); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 7dcc77e114..5dbe0d7727 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -1767,9 +1767,9 @@ int show_journal_by_unit( static int discover_next_boot( sd_journal *j, - sd_id128_t previous_boot_id, + sd_id128_t previous_id, bool advance_older, - BootId *ret) { + LogId *ret) { _cleanup_set_free_ Set *broken_ids = NULL; int r; @@ -1790,18 +1790,18 @@ static int discover_next_boot( for (;;) { sd_id128_t *id_dup; - BootId boot; + LogId id; r = sd_journal_step_one(j, !advance_older); if (r < 0) return r; if (r == 0) { sd_journal_flush_matches(j); - *ret = (BootId) {}; + *ret = (LogId) {}; return 0; /* End of journal, yay. */ } - r = sd_journal_get_monotonic_usec(j, NULL, &boot.id); + r = sd_journal_get_monotonic_usec(j, NULL, &id.id); if (r < 0) return r; @@ -1813,16 +1813,16 @@ static int discover_next_boot( * speed things up, but let's not trust that it is complete, and hence, manually advance as * necessary. */ - if (!sd_id128_is_null(previous_boot_id) && sd_id128_equal(boot.id, previous_boot_id)) + if (!sd_id128_is_null(previous_id) && sd_id128_equal(id.id, previous_id)) continue; - if (set_contains(broken_ids, &boot.id)) + 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. */ - r = add_match_boot_id(j, boot.id); + r = add_match_boot_id(j, id.id); if (r < 0) return r; @@ -1843,11 +1843,11 @@ static int discover_next_boot( 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(boot.id)); + SD_ID128_TO_STRING(id.id)); goto try_again; } - r = sd_journal_get_realtime_usec(j, advance_older ? &boot.last_usec : &boot.first_usec); + r = sd_journal_get_realtime_usec(j, advance_older ? &id.last_usec : &id.first_usec); if (r < 0) return r; @@ -1865,21 +1865,21 @@ static int discover_next_boot( 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(boot.id)); + SD_ID128_TO_STRING(id.id)); goto try_again; } - r = sd_journal_get_realtime_usec(j, advance_older ? &boot.first_usec : &boot.last_usec); + r = sd_journal_get_realtime_usec(j, advance_older ? &id.first_usec : &id.last_usec); if (r < 0) return r; sd_journal_flush_matches(j); - *ret = boot; + *ret = id; return 1; try_again: /* Save the bad boot ID. */ - id_dup = newdup(sd_id128_t, &boot.id, 1); + id_dup = newdup(sd_id128_t, &id.id, 1); if (!id_dup) return -ENOMEM; @@ -1890,8 +1890,8 @@ static int discover_next_boot( /* Move to the previous position again. */ sd_journal_flush_matches(j); - if (!sd_id128_is_null(previous_boot_id)) { - r = add_match_boot_id(j, previous_boot_id); + if (!sd_id128_is_null(previous_id)) { + r = add_match_boot_id(j, previous_id); if (r < 0) return r; } @@ -1909,7 +1909,7 @@ static int discover_next_boot( if (r == 0) return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "Whoopsie! Cannot seek to the last entry of boot %s.", - SD_ID128_TO_STRING(previous_boot_id)); + SD_ID128_TO_STRING(previous_id)); sd_journal_flush_matches(j); } @@ -1972,9 +1972,9 @@ int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t * position us at the newest/oldest entry we have. */ for (int off = offset_start; ; off += advance_older ? -1 : 1) { - BootId boot; + LogId id; - r = discover_next_boot(j, boot_id, advance_older, &boot); + r = discover_next_boot(j, boot_id, advance_older, &id); if (r < 0) return r; if (r == 0) { @@ -1982,7 +1982,7 @@ int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t return false; } - boot_id = boot.id; + boot_id = id.id; log_debug("Found boot ID %s by offset %i", SD_ID128_TO_STRING(boot_id), off); if (off == offset) { @@ -1996,16 +1996,16 @@ int journal_get_boots( sd_journal *j, bool advance_older, size_t max_ids, - BootId **ret_boots, - size_t *ret_n_boots) { + LogId **ret_ids, + size_t *ret_n_ids) { - _cleanup_free_ BootId *boots = NULL; - size_t n_boots = 0; + _cleanup_free_ LogId *ids = NULL; + size_t n_ids = 0; int r; assert(j); - assert(ret_boots); - assert(ret_n_boots); + assert(ret_ids); + assert(ret_n_ids); sd_journal_flush_matches(j); @@ -2021,33 +2021,33 @@ int journal_get_boots( * At this point the read pointer is positioned before the oldest entry in the whole journal. The * next invocation of _next() will hence position us at the oldest entry we have. */ - sd_id128_t previous_boot_id = SD_ID128_NULL; + sd_id128_t previous_id = SD_ID128_NULL; for (;;) { - BootId boot; + LogId id; - if (n_boots >= max_ids) + if (n_ids >= max_ids) break; - r = discover_next_boot(j, previous_boot_id, advance_older, &boot); + r = discover_next_boot(j, previous_id, advance_older, &id); if (r < 0) return r; if (r == 0) break; - previous_boot_id = boot.id; + previous_id = id.id; - FOREACH_ARRAY(i, boots, n_boots) - if (sd_id128_equal(i->id, boot.id)) + 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. */ goto finish; - if (!GREEDY_REALLOC_APPEND(boots, n_boots, &boot, 1)) + if (!GREEDY_REALLOC_APPEND(ids, n_ids, &id, 1)) return -ENOMEM; } finish: - *ret_boots = TAKE_PTR(boots); - *ret_n_boots = n_boots; - return n_boots > 0; + *ret_ids = TAKE_PTR(ids); + *ret_n_ids = n_ids; + return n_ids > 0; } diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index f775feb316..580855a00c 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -14,11 +14,11 @@ #include "set.h" #include "time-util.h" -typedef struct BootId { - sd_id128_t id; +typedef struct LogId { + sd_id128_t id; /* boot ID or invocation ID */ usec_t first_usec; usec_t last_usec; -} BootId; +} LogId; int show_journal_entry( FILE *f, @@ -76,5 +76,5 @@ int journal_get_boots( sd_journal *j, bool advance_older, size_t max_ids, - BootId **ret_boots, - size_t *ret_n_boots); + LogId **ret_ids, + size_t *ret_n_ids); From a581a2ce8d90f350eb1b15eb81acef698d9dc31b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 31 Jul 2024 11:27:28 +0900 Subject: [PATCH 2/8] journalctl: update log messages --- src/journal/journalctl-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/journal/journalctl-util.c b/src/journal/journalctl-util.c index 32cdaf8381..5ff02baeb9 100644 --- a/src/journal/journalctl-util.c +++ b/src/journal/journalctl-util.c @@ -99,12 +99,12 @@ int journal_acquire_boot(sd_journal *j) { r = journal_find_boot(j, arg_boot_id, arg_boot_offset, &boot_id); if (r < 0) - return log_error_errno(r, "Failed to find journal entry from the specified boot (%s%+i): %m", + return log_error_errno(r, "Failed to find journal entry for the specified boot (%s%+i): %m", sd_id128_is_null(arg_boot_id) ? "" : SD_ID128_TO_STRING(arg_boot_id), arg_boot_offset); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(ENODATA), - "No journal boot entry found from the specified boot (%s%+i).", + "No journal boot entry found for the specified boot (%s%+i).", sd_id128_is_null(arg_boot_id) ? "" : SD_ID128_TO_STRING(arg_boot_id), arg_boot_offset); From b3a16f39850d60c372cc13c62228544a1fb27ae6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 25 Apr 2024 13:15:49 +0900 Subject: [PATCH 3/8] journalctl: split out show_log_ids() from action_list_boots() No functional change, just refactoring and prepraration for later change. --- src/journal/journalctl-misc.c | 52 +++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/journal/journalctl-misc.c b/src/journal/journalctl-misc.c index 880a0900eb..bc063d930b 100644 --- a/src/journal/journalctl-misc.c +++ b/src/journal/journalctl-misc.c @@ -98,31 +98,15 @@ int action_disk_usage(void) { return 0; } -int action_list_boots(void) { - _cleanup_(sd_journal_closep) sd_journal *j = NULL; +static int show_log_ids(const LogId *ids, size_t n_ids, const char *name) { _cleanup_(table_unrefp) Table *table = NULL; - _cleanup_free_ LogId *ids = NULL; - size_t n_ids; int r; - assert(arg_action == ACTION_LIST_BOOTS); + assert(ids); + assert(n_ids > 0); + assert(name); - r = acquire_journal(&j); - if (r < 0) - return r; - - r = journal_get_boots( - j, - /* advance_older = */ arg_lines_needs_seek_end(), - /* max_ids = */ arg_lines >= 0 ? (size_t) arg_lines : SIZE_MAX, - &ids, &n_ids); - if (r < 0) - return log_error_errno(r, "Failed to determine boots: %m"); - if (r == 0) - return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENODATA), - "No boot found."); - - table = table_new("idx", "boot id", "first entry", "last entry"); + table = table_new("idx", name, "first entry", "last entry"); if (!table) return log_oom(); @@ -166,6 +150,32 @@ int action_list_boots(void) { return 0; } +int action_list_boots(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + _cleanup_free_ LogId *ids = NULL; + size_t n_ids; + int r; + + assert(arg_action == ACTION_LIST_BOOTS); + + r = acquire_journal(&j); + if (r < 0) + return r; + + r = journal_get_boots( + j, + /* advance_older = */ arg_lines_needs_seek_end(), + /* max_ids = */ arg_lines >= 0 ? (size_t) arg_lines : SIZE_MAX, + &ids, &n_ids); + if (r < 0) + return log_error_errno(r, "Failed to determine boots: %m"); + if (r == 0) + return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENODATA), + "No boot found."); + + return show_log_ids(ids, n_ids, "boot id"); +} + int action_list_fields(void) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; int r, n_shown = 0; From 0a8c1f6212a874b542a57ed5416e7d3575d2da93 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 22 Jun 2024 17:45:57 +0900 Subject: [PATCH 4/8] use int64_t for index in show_log_ids() --- src/journal/journalctl-misc.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/journal/journalctl-misc.c b/src/journal/journalctl-misc.c index bc063d930b..ca0c9ec924 100644 --- a/src/journal/journalctl-misc.c +++ b/src/journal/journalctl-misc.c @@ -104,6 +104,7 @@ static int show_log_ids(const LogId *ids, size_t n_ids, const char *name) { assert(ids); assert(n_ids > 0); + assert(n_ids < INT64_MAX); assert(name); table = table_new("idx", name, "first entry", "last entry"); @@ -120,21 +121,21 @@ static int show_log_ids(const LogId *ids, size_t n_ids, const char *name) { (void) table_set_sort(table, (size_t) 0); (void) table_set_reverse(table, 0, arg_reverse); - for (int i = 0; i < (int) n_ids; i++) { - int index; + for (size_t i = 0; i < n_ids; i++) { + int64_t index; if (arg_lines_needs_seek_end()) /* With --lines=N, we only know the negative index, and the older ID is located earlier. */ - index = -i; + index = - (int64_t) i; else if (arg_lines >= 0) /* With --lines=+N, we only know the positive index, and the newer ID is located earlier. */ index = i + 1; else /* Otherwise, show negative index. Note, in this case, newer ID is located earlier. */ - index = i + 1 - (int) n_ids; + index = (int64_t) (i + 1) - (int64_t) n_ids; r = table_add_many(table, - TABLE_INT, index, + TABLE_INT64, index, TABLE_SET_ALIGN_PERCENT, 100, TABLE_ID128, ids[i].id, TABLE_TIMESTAMP, ids[i].first_usec, From 1a997cb732da154a248c63753aa7a16aebe99407 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 25 Apr 2024 15:27:43 +0900 Subject: [PATCH 5/8] logs-show: introduce several helper functions Currently these are not used, but will be used later. --- src/shared/logs-show.c | 86 +++++++++++++++++++++++++++++------------- src/shared/logs-show.h | 15 +++++--- 2 files changed, 69 insertions(+), 32 deletions(-) diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 5dbe0d7727..7f8171af49 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -11,6 +11,7 @@ #include "sd-id128.h" #include "sd-journal.h" #include "sd-json.h" +#include "sd-messages.h" #include "alloc-util.h" #include "fd-util.h" @@ -1589,34 +1590,63 @@ int show_journal( return 0; } -int add_matches_for_unit(sd_journal *j, const char *unit) { +int add_matches_for_invocation_id(sd_journal *j, sd_id128_t id) { + int r; + + assert(j); + assert(!sd_id128_is_null(id)); + + (void) ( + /* Look for messages from the service itself. */ + (r = journal_add_match_pair(j, "_SYSTEMD_INVOCATION_ID", SD_ID128_TO_STRING(id))) || + + /* Look for messages from authorized daemons about this service. */ + (r = sd_journal_add_disjunction(j)) || + (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_INVOCATION_ID", SD_ID128_TO_STRING(id))) || + + /* Look for messages from system service manager (PID 1) about this service. */ + (r = sd_journal_add_disjunction(j)) || + (r = journal_add_match_pair(j, "INVOCATION_ID", SD_ID128_TO_STRING(id))) || + + /* Look for messages from user-session service manager about this service. */ + (r = sd_journal_add_disjunction(j)) || + (r = journal_add_match_pair(j, "USER_INVOCATION_ID", SD_ID128_TO_STRING(id))) + ); + + return r; +} + +int add_matches_for_unit_full(sd_journal *j, bool all, const char *unit) { int r; assert(j); assert(unit); (void) ( - /* Look for messages from the service itself */ - (r = journal_add_match_pair(j, "_SYSTEMD_UNIT", unit)) || + /* Look for messages from the service itself */ + (r = journal_add_match_pair(j, "_SYSTEMD_UNIT", unit)) || - /* Look for coredumps of the service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", SIZE_MAX)) || - (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || - (r = journal_add_match_pair(j, "COREDUMP_UNIT", unit)) || + /* Look for messages from PID 1 about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_PID=1", SIZE_MAX)) || + (r = journal_add_match_pair(j, "UNIT", unit)) || - /* Look for messages from PID 1 about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_PID=1", SIZE_MAX)) || - (r = journal_add_match_pair(j, "UNIT", unit)) || - - /* Look for messages from authorized daemons about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || - (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_UNIT", unit)) + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || + (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_UNIT", unit)) ); - if (r == 0 && endswith(unit, ".slice")) + if (r == 0 && all) + (void) ( + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR, SIZE_MAX)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || + (r = journal_add_match_pair(j, "COREDUMP_UNIT", unit)) + ); + + if (r == 0 && all && endswith(unit, ".slice")) /* Show all messages belonging to a slice */ (void) ( (r = sd_journal_add_disjunction(j)) || @@ -1626,7 +1656,7 @@ int add_matches_for_unit(sd_journal *j, const char *unit) { return r; } -int add_matches_for_user_unit(sd_journal *j, const char *unit) { +int add_matches_for_user_unit_full(sd_journal *j, bool all, const char *unit) { uid_t uid = getuid(); int r; @@ -1643,12 +1673,6 @@ int add_matches_for_user_unit(sd_journal *j, const char *unit) { (r = journal_add_match_pair(j, "USER_UNIT", unit)) || (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || - /* Look for coredumps of the service */ - (r = sd_journal_add_disjunction(j)) || - (r = journal_add_match_pair(j, "COREDUMP_USER_UNIT", unit)) || - (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || - (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || - /* Look for messages from authorized daemons about this service */ (r = sd_journal_add_disjunction(j)) || (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_USER_UNIT", unit)) || @@ -1656,7 +1680,17 @@ int add_matches_for_user_unit(sd_journal *j, const char *unit) { (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) ); - if (r == 0 && endswith(unit, ".slice")) + if (r == 0 && all) + (void) ( + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = journal_add_match_pair(j, "COREDUMP_USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) + ); + + + if (r == 0 && all && endswith(unit, ".slice")) /* Show all messages belonging to a slice */ (void) ( (r = sd_journal_add_disjunction(j)) || diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index 580855a00c..7d7a918179 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -44,13 +44,16 @@ int show_journal( int add_match_boot_id(sd_journal *j, sd_id128_t id); int add_match_this_boot(sd_journal *j, const char *machine); -int add_matches_for_unit( - sd_journal *j, - const char *unit); +int add_matches_for_invocation_id(sd_journal *j, sd_id128_t id); -int add_matches_for_user_unit( - sd_journal *j, - const char *unit); +int add_matches_for_unit_full(sd_journal *j, bool all, const char *unit); +static inline int add_matches_for_unit(sd_journal *j, const char *unit) { + return add_matches_for_unit_full(j, true, unit); +} +int add_matches_for_user_unit_full(sd_journal *j, bool all, const char *unit); +static inline int add_matches_for_user_unit(sd_journal *j, const char *unit) { + return add_matches_for_user_unit_full(j, true, unit); +} int show_journal_by_unit( FILE *f, From 82721be7d60c337374b1b75c153d9d12791f7f43 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 25 Apr 2024 15:28:19 +0900 Subject: [PATCH 6/8] logs-show: extend journal_get_boots() and friends to find invocation IDs Currently the extended features are not used, but will be used later. --- src/shared/logs-show.c | 242 ++++++++++++++++++++++++++++++++--------- src/shared/logs-show.h | 47 +++++++- 2 files changed, 235 insertions(+), 54 deletions(-) diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 7f8171af49..96955497a2 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -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)) diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index 7d7a918179..d000978c04 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -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); +} From 7d3ae6b21308ca8959d7c70b1f9fa9012db6ec15 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 25 Apr 2024 13:38:24 +0900 Subject: [PATCH 7/8] journalctl: add --list-invocations command and -I/--invocation options The --list-invocations command is similar to --list-boots, but shows invocation IDs of specified unit. This should be useful when showing a specific invocation of a unit. The --invocation option is similar to --boot, but takes a invocation ID or an offset. The -I option is equivalent to --invocation=0. --- man/journalctl.xml | 53 ++++++++++++++++++++++ shell-completion/bash/journalctl | 4 +- src/journal/journalctl-filter.c | 44 ++++++++++++++---- src/journal/journalctl-misc.c | 37 ++++++++++++++++ src/journal/journalctl-misc.h | 1 + src/journal/journalctl-util.c | 76 ++++++++++++++++++++++++++++++++ src/journal/journalctl-util.h | 3 ++ src/journal/journalctl.c | 33 +++++++++++++- src/journal/journalctl.h | 4 ++ 9 files changed, 244 insertions(+), 11 deletions(-) diff --git a/man/journalctl.xml b/man/journalctl.xml index 5fd9263116..0cc2b72acc 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -350,6 +350,42 @@ + + + + + + Show messages from a specific invocation of unit. This will add a match for + _SYSTEMD_INVOCATION_ID=, OBJECT_SYSTEMD_INVOCATION_ID=, + INVOCATION_ID=, USER_INVOCATION_ID=. + + A positive offset will look up the invocations of a systemd unit + from the beginning of the journal, and zero or a negative offset will look up invocations starting + from the end of the journal. Thus, 1 means the first invocation found in the + journal in chronological order, 2 the second and so on; while + 0 is the latest invocation, -1 the invocation before the + latest, and so on. + + If the 32-character ID is specified, it may optionally be followed + by ±offset which identifies the invocation relative to the one given by + invocation ID. Negative values mean earlier invocations and positive + values mean later invocations. If ±offset is not specified, a value of + zero is assumed, and the logs for the invocation given by ID will be + shown. + + is equivalent to , and logs for the latest + invocation will be shown. + + When an offset is specified, a unit name must be specified with + or option. + + When specified with , then invocations are searched within the + specified boot. + + + + + @@ -862,6 +898,23 @@ + + + + + List invocation IDs of a unit. Requires a unit name with or + . Show a tabular list of invocation numbers (relative to the current + or latest invocation), their IDs, and the timestamps of the first and last message pertaining to + the invocation. When is specified, invocations in the boot will be shown. + When specified with + option, only the first (when the number prefixed with +) or the last (without + prefix) N entries will be shown. When specified with + , the list will be shown in the reverse order. + + + + + diff --git a/shell-completion/bash/journalctl b/shell-completion/bash/journalctl index c79a38c352..5d1cc174c9 100644 --- a/shell-completion/bash/journalctl +++ b/shell-completion/bash/journalctl @@ -47,11 +47,11 @@ _journalctl() { --show-cursor --dmesg -k --pager-end -e -r --reverse --utc -x --catalog --no-full --force --dump-catalog --flush --rotate --sync --no-hostname -N --fields - --list-namespaces' + --list-namespaces --list-invocations -I' [ARG]='-b --boot -D --directory --file -F --field -t --identifier -T --exclude-identifier --facility -M --machine -o --output -u --unit --user-unit -p --priority --root --case-sensitive - --namespace' + --namespace --invocation' [ARGUNKNOWN]='-c --cursor --interval -n --lines -S --since -U --until --after-cursor --cursor-file --verify-key -g --grep --vacuum-size --vacuum-time --vacuum-files --output-fields' diff --git a/src/journal/journalctl-filter.c b/src/journal/journalctl-filter.c index f9eb9f8c58..ab65a51451 100644 --- a/src/journal/journalctl-filter.c +++ b/src/journal/journalctl-filter.c @@ -16,6 +16,23 @@ #include "path-util.h" #include "unit-name.h" +static int add_invocation(sd_journal *j) { + int r; + + assert(j); + + if (!arg_invocation) + return 0; + + assert(!sd_id128_is_null(arg_invocation_id)); + + r = add_matches_for_invocation_id(j, arg_invocation_id); + if (r < 0) + return r; + + return sd_journal_add_conjunction(j); +} + static int add_boot(sd_journal *j) { int r; @@ -429,27 +446,38 @@ int add_filters(sd_journal *j, char **matches) { assert(j); - /* First, search boot ID, as that may set and flush matches and seek journal. */ + /* First, search boot or invocation ID, as that may set and flush matches and seek journal. */ r = journal_acquire_boot(j); if (r < 0) return r; + r = journal_acquire_invocation(j); + if (r < 0) + return r; + /* Clear unexpected matches for safety. */ sd_journal_flush_matches(j); /* Then, add filters in the below. */ - r = add_boot(j); - if (r < 0) - return log_error_errno(r, "Failed to add filter for boot: %m"); + if (arg_invocation) { + /* If an invocation ID is found, then it is not necessary to add matches for boot and units. */ + r = add_invocation(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for invocation: %m"); + } else { + r = add_boot(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for boot: %m"); + + r = add_units(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for units: %m"); + } r = add_dmesg(j); if (r < 0) return log_error_errno(r, "Failed to add filter for dmesg: %m"); - r = add_units(j); - if (r < 0) - return log_error_errno(r, "Failed to add filter for units: %m"); - r = add_syslog_identifier(j); if (r < 0) return log_error_errno(r, "Failed to add filter for syslog identifiers: %m"); diff --git a/src/journal/journalctl-misc.c b/src/journal/journalctl-misc.c index ca0c9ec924..01fb83807a 100644 --- a/src/journal/journalctl-misc.c +++ b/src/journal/journalctl-misc.c @@ -236,6 +236,43 @@ int action_list_field_names(void) { return 0; } +int action_list_invocations(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + _cleanup_free_ LogId *ids = NULL; + size_t n_ids; + LogIdType type; + const char *unit; + int r; + + assert(arg_action == ACTION_LIST_INVOCATIONS); + + r = acquire_unit("--list-invocations", &unit, &type); + if (r < 0) + return r; + + r = acquire_journal(&j); + if (r < 0) + return r; + + r = journal_acquire_boot(j); + if (r < 0) + return r; + + r = journal_get_log_ids( + j, type, + /* boot_id = */ arg_boot_id, /* unit = */ unit, + /* advance_older = */ arg_lines_needs_seek_end(), + /* max_ids = */ arg_lines >= 0 ? (size_t) arg_lines : SIZE_MAX, + &ids, &n_ids); + if (r < 0) + return log_error_errno(r, "Failed to list invocation id for %s: %m", unit); + if (r == 0) + return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENODATA), + "No invocation ID for %s found.", unit); + + return show_log_ids(ids, n_ids, "invocation id"); +} + int action_list_namespaces(void) { _cleanup_(table_unrefp) Table *table = NULL; sd_id128_t machine; diff --git a/src/journal/journalctl-misc.h b/src/journal/journalctl-misc.h index 70f851bc14..72036890a8 100644 --- a/src/journal/journalctl-misc.h +++ b/src/journal/journalctl-misc.h @@ -9,4 +9,5 @@ int action_disk_usage(void); int action_list_boots(void); int action_list_fields(void); int action_list_field_names(void); +int action_list_invocations(void); int action_list_namespaces(void); diff --git a/src/journal/journalctl-util.c b/src/journal/journalctl-util.c index 5ff02baeb9..b554f4d78a 100644 --- a/src/journal/journalctl-util.c +++ b/src/journal/journalctl-util.c @@ -9,6 +9,7 @@ #include "logs-show.h" #include "rlimit-util.h" #include "sigbus.h" +#include "strv.h" #include "terminal-util.h" char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) { @@ -118,3 +119,78 @@ int journal_acquire_boot(sd_journal *j) { return 1; } + +int acquire_unit(const char *option_name, const char **ret_unit, LogIdType *ret_type) { + size_t n; + + assert(option_name); + assert(ret_unit); + assert(ret_type); + + n = strv_length(arg_system_units) + strv_length(arg_user_units); + if (n <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Using %s requires a unit. Please specify a unit name with -u/--unit=/--user-unit=.", + option_name); + if (n > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Using %s with multiple units is not supported.", + option_name); + + if (!strv_isempty(arg_system_units)) { + *ret_type = LOG_SYSTEM_UNIT_INVOCATION_ID; + *ret_unit = arg_system_units[0]; + } else { + assert(!strv_isempty(arg_user_units)); + *ret_type = LOG_USER_UNIT_INVOCATION_ID; + *ret_unit = arg_user_units[0]; + } + + return 0; +} + +int journal_acquire_invocation(sd_journal *j) { + LogIdType type = LOG_SYSTEM_UNIT_INVOCATION_ID; + const char *unit = NULL; + sd_id128_t id; + int r; + + assert(j); + + /* journal_acquire_boot() must be called before this. */ + + if (!arg_invocation) { + /* Clear relevant field for safety. */ + arg_invocation_id = SD_ID128_NULL; + arg_invocation_offset = 0; + return 0; + } + + /* When an invocation ID is explicitly specified without an offset, we do not care the ID is about + * system unit or user unit, and calling without unit name is allowed. Otherwise, a unit name must + * be specified. */ + if (arg_invocation_offset != 0 || sd_id128_is_null(arg_invocation_id)) { + r = acquire_unit("-I/--invocation= with an offset", &unit, &type); + if (r < 0) + return r; + } + + r = journal_find_log_id(j, type, arg_boot_id, unit, arg_invocation_id, arg_invocation_offset, &id); + if (r < 0) + return log_error_errno(r, "Failed to find journal entry for the invocation (%s%+i): %m", + sd_id128_is_null(arg_invocation_id) ? "" : SD_ID128_TO_STRING(arg_invocation_id), + arg_invocation_offset); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), + "No journal entry found for the invocation (%s%+i).", + sd_id128_is_null(arg_invocation_id) ? "" : SD_ID128_TO_STRING(arg_invocation_id), + arg_invocation_offset); + + log_debug("Found invocation ID %s for %s%+i", + SD_ID128_TO_STRING(id), + sd_id128_is_null(arg_invocation_id) ? "" : SD_ID128_TO_STRING(arg_invocation_id), + arg_invocation_offset); + + arg_invocation_id = id; + return 1; +} diff --git a/src/journal/journalctl-util.h b/src/journal/journalctl-util.h index ea3e56838d..14e3d569a6 100644 --- a/src/journal/journalctl-util.h +++ b/src/journal/journalctl-util.h @@ -3,9 +3,12 @@ #include "sd-journal.h" +#include "logs-show.h" #include "time-util.h" char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t); int acquire_journal(sd_journal **ret); bool journal_boot_has_effect(sd_journal *j); int journal_acquire_boot(sd_journal *j); +int acquire_unit(const char *option_name, const char **ret_unit, LogIdType *ret_type); +int journal_acquire_invocation(sd_journal *j); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 369280a009..8ed5d98675 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -72,6 +72,9 @@ char **arg_syslog_identifier = NULL; char **arg_exclude_identifier = NULL; char **arg_system_units = NULL; char **arg_user_units = NULL; +bool arg_invocation = false; +sd_id128_t arg_invocation_id = SD_ID128_NULL; +int arg_invocation_offset = 0; const char *arg_field = NULL; bool arg_catalog = false; bool arg_reverse = false; @@ -227,6 +230,8 @@ static int help(void) { " -b --boot[=ID] Show current boot or the specified boot\n" " -u --unit=UNIT Show logs from the specified unit\n" " --user-unit=UNIT Show logs from the specified user unit\n" + " --invocation=ID Show logs from the matching invocation ID\n" + " -I Show logs from the latest invocation of unit\n" " -t --identifier=STRING Show entries with the specified syslog identifier\n" " -T --exclude-identifier=STRING\n" " Hide entries with the specified syslog identifier\n" @@ -267,6 +272,7 @@ static int help(void) { " -N --fields List all field names currently used\n" " -F --field=FIELD List all values that a specified field takes\n" " --list-boots Show terse information about recorded boots\n" + " --list-invocations Show invocation IDs of specified unit\n" " --list-namespaces Show list of journal namespaces\n" " --disk-usage Show total disk usage of all journal files\n" " --vacuum-size=BYTES Reduce disk usage below specified size\n" @@ -304,6 +310,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NEW_ID128, ARG_THIS_BOOT, ARG_LIST_BOOTS, + ARG_LIST_INVOCATIONS, ARG_USER, ARG_SYSTEM, ARG_ROOT, @@ -320,6 +327,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CURSOR_FILE, ARG_SHOW_CURSOR, ARG_USER_UNIT, + ARG_INVOCATION, ARG_LIST_CATALOG, ARG_DUMP_CATALOG, ARG_UPDATE_CATALOG, @@ -361,6 +369,7 @@ static int parse_argv(int argc, char *argv[]) { { "this-boot", no_argument, NULL, ARG_THIS_BOOT }, /* deprecated */ { "boot", optional_argument, NULL, 'b' }, { "list-boots", no_argument, NULL, ARG_LIST_BOOTS }, + { "list-invocations", no_argument, NULL, ARG_LIST_INVOCATIONS }, { "dmesg", no_argument, NULL, 'k' }, { "system", no_argument, NULL, ARG_SYSTEM }, { "user", no_argument, NULL, ARG_USER }, @@ -389,6 +398,7 @@ static int parse_argv(int argc, char *argv[]) { { "until", required_argument, NULL, 'U' }, { "unit", required_argument, NULL, 'u' }, { "user-unit", required_argument, NULL, ARG_USER_UNIT }, + { "invocation", required_argument, NULL, ARG_INVOCATION }, { "field", required_argument, NULL, 'F' }, { "fields", no_argument, NULL, 'N' }, { "catalog", no_argument, NULL, 'x' }, @@ -418,7 +428,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:NF:xrM:i:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:INF:xrM:i:", options, NULL)) >= 0) switch (c) { @@ -540,6 +550,10 @@ static int parse_argv(int argc, char *argv[]) { arg_action = ACTION_LIST_BOOTS; break; + case ARG_LIST_INVOCATIONS: + arg_action = ACTION_LIST_INVOCATIONS; + break; + case 'k': arg_boot = arg_dmesg = true; break; @@ -831,6 +845,20 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; + case ARG_INVOCATION: + r = parse_id_descriptor(optarg, &arg_invocation_id, &arg_invocation_offset); + if (r < 0) + return log_error_errno(r, "Failed to parse invocation descriptor: %s", optarg); + arg_invocation = r; + break; + + case 'I': + /* Equivalent to --invocation=0 */ + arg_invocation = true; + arg_invocation_id = SD_ID128_NULL; + arg_invocation_offset = 0; + break; + case 'F': arg_action = ACTION_LIST_FIELDS; arg_field = optarg; @@ -1074,6 +1102,9 @@ static int run(int argc, char *argv[]) { case ACTION_LIST_FIELD_NAMES: return action_list_field_names(); + case ACTION_LIST_INVOCATIONS: + return action_list_invocations(); + case ACTION_LIST_NAMESPACES: return action_list_namespaces(); diff --git a/src/journal/journalctl.h b/src/journal/journalctl.h index 2d86c16917..409cfe2bca 100644 --- a/src/journal/journalctl.h +++ b/src/journal/journalctl.h @@ -26,6 +26,7 @@ typedef enum JournalctlAction { ACTION_LIST_BOOTS, ACTION_LIST_FIELDS, ACTION_LIST_FIELD_NAMES, + ACTION_LIST_INVOCATIONS, ACTION_LIST_NAMESPACES, ACTION_FLUSH, ACTION_RELINQUISH_VAR, @@ -76,6 +77,9 @@ extern char **arg_syslog_identifier; extern char **arg_exclude_identifier; extern char **arg_system_units; extern char **arg_user_units; +extern bool arg_invocation; +extern sd_id128_t arg_invocation_id; +extern int arg_invocation_offset; extern const char *arg_field; extern bool arg_catalog; extern bool arg_reverse; From b1b30d02263d886cf69dbb180fe85f1f86db5ccc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 12 May 2024 16:15:22 +0900 Subject: [PATCH 8/8] test: add test for journalctl --list-invocations and --invocation= --- test/test-functions | 1 + test/units/TEST-04-JOURNAL.invocation.sh | 67 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100755 test/units/TEST-04-JOURNAL.invocation.sh diff --git a/test/test-functions b/test/test-functions index e219812e72..3c6f763973 100644 --- a/test/test-functions +++ b/test/test-functions @@ -236,6 +236,7 @@ BASICTOOLS=( su sulogin sysctl + tac tail tar tee diff --git a/test/units/TEST-04-JOURNAL.invocation.sh b/test/units/TEST-04-JOURNAL.invocation.sh new file mode 100755 index 0000000000..129d16d570 --- /dev/null +++ b/test/units/TEST-04-JOURNAL.invocation.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2002 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +SERVICE_NAME=invocation-id-test-"$RANDOM".service + +TMP_DIR=$(mktemp -d) + +# FIXME: if the maximum log level of PID1 is debug, then journal entries of +# service stdout will not contain _SYSTEMD_INVOCATION_ID field. +SAVED_LOG_LEVEL=$(systemctl log-level) +systemctl log-level info + +# Note, if the service exits extremely fast, journald cannot find the source of the +# stream. Hence, we need to call 'journalctl --sync' before service exits. +for i in {1..10}; do + systemd-run --wait -u "$SERVICE_NAME" bash -c "echo invocation ${i} \$INVOCATION_ID; journalctl --sync" +done + +journalctl --list-invocation -u "$SERVICE_NAME" | tee "$TMP_DIR"/10 +journalctl --list-invocation -u "$SERVICE_NAME" --reverse | tee "$TMP_DIR"/10-r +journalctl --list-invocation -u "$SERVICE_NAME" -n +10 | tee "$TMP_DIR"/p10 +journalctl --list-invocation -u "$SERVICE_NAME" -n +10 --reverse | tee "$TMP_DIR"/p10-r +journalctl --list-invocation -u "$SERVICE_NAME" -n 5 | tee "$TMP_DIR"/5 +journalctl --list-invocation -u "$SERVICE_NAME" -n 5 --reverse | tee "$TMP_DIR"/5-r +journalctl --list-invocation -u "$SERVICE_NAME" -n +5 | tee "$TMP_DIR"/p5 +journalctl --list-invocation -u "$SERVICE_NAME" -n +5 --reverse | tee "$TMP_DIR"/p5-r + +[[ $(cat "$TMP_DIR"/10 | wc -l) == 11 ]] +[[ $(cat "$TMP_DIR"/10-r | wc -l) == 11 ]] +[[ $(cat "$TMP_DIR"/p10 | wc -l) == 11 ]] +[[ $(cat "$TMP_DIR"/p10-r | wc -l) == 11 ]] +[[ $(cat "$TMP_DIR"/5 | wc -l) == 6 ]] +[[ $(cat "$TMP_DIR"/5-r | wc -l) == 6 ]] +[[ $(cat "$TMP_DIR"/p5 | wc -l) == 6 ]] +[[ $(cat "$TMP_DIR"/p5-r | wc -l) == 6 ]] + +diff <(tail -n 10 "$TMP_DIR"/10 | tac) <(tail -n 10 "$TMP_DIR"/10-r) +diff <(tail -n 5 "$TMP_DIR"/10) <(tail -n 5 "$TMP_DIR"/5) +diff <(tail -n 5 "$TMP_DIR"/10 | tac) <(tail -n 5 "$TMP_DIR"/5-r) +diff <(tail -n 10 "$TMP_DIR"/p10 | tac) <(tail -n 10 "$TMP_DIR"/p10-r) +diff <(tail -n 10 "$TMP_DIR"/p10 | head -n 5) <(tail -n 5 "$TMP_DIR"/p5) +diff <(tail -n 10 "$TMP_DIR"/p10 | head -n 5 | tac) <(tail -n 5 "$TMP_DIR"/p5-r) + +tail -n 10 "$TMP_DIR"/10 | + while read -r idx invocation _; do + i="$(( idx + 10 ))" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${i}" -u "$SERVICE_NAME")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${idx}" -u "$SERVICE_NAME")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${invocation}")" + done + +tail -n 10 "$TMP_DIR"/p10 | + while read -r i invocation _; do + idx="$(( i - 10 ))" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${i}" -u "$SERVICE_NAME")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${idx}" -u "$SERVICE_NAME")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${invocation}")" + done + +# Restore the log level. +systemctl log-level "$SAVED_LOG_LEVEL"