Merge pull request #32448 from yuwata/journalctl-current-invocation
journalctl: introduce --list-invocations, -I, --invocation= options
This commit is contained in:
@@ -350,6 +350,42 @@
|
||||
<xi:include href="version-info.xml" xpointer="v198"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-I</option></term>
|
||||
<term><option>--invocation=<replaceable>ID</replaceable><optional><replaceable>±offset</replaceable></optional>|<replaceable>offset</replaceable></option></term>
|
||||
|
||||
<listitem>
|
||||
<para>Show messages from a specific invocation of unit. This will add a match for
|
||||
<literal>_SYSTEMD_INVOCATION_ID=</literal>, <literal>OBJECT_SYSTEMD_INVOCATION_ID=</literal>,
|
||||
<literal>INVOCATION_ID=</literal>, <literal>USER_INVOCATION_ID=</literal>.</para>
|
||||
|
||||
<para>A positive <replaceable>offset</replaceable> 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, <constant>1</constant> means the first invocation found in the
|
||||
journal in chronological order, <constant>2</constant> the second and so on; while
|
||||
<constant>0</constant> is the latest invocation, <constant>-1</constant> the invocation before the
|
||||
latest, and so on.</para>
|
||||
|
||||
<para>If the 32-character <replaceable>ID</replaceable> is specified, it may optionally be followed
|
||||
by <replaceable>±offset</replaceable> which identifies the invocation relative to the one given by
|
||||
invocation <replaceable>ID</replaceable>. Negative values mean earlier invocations and positive
|
||||
values mean later invocations. If <replaceable>±offset</replaceable> is not specified, a value of
|
||||
zero is assumed, and the logs for the invocation given by <replaceable>ID</replaceable> will be
|
||||
shown.</para>
|
||||
|
||||
<para><option>-I</option> is equivalent to <option>--invocation=0</option>, and logs for the latest
|
||||
invocation will be shown.</para>
|
||||
|
||||
<para>When an offset is specified, a unit name must be specified with <option>-u/--unit=</option>
|
||||
or <option>--user-unit=</option> option.</para>
|
||||
|
||||
<para>When specified with <option>-b/--boot=</option>, then invocations are searched within the
|
||||
specified boot.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-t</option></term>
|
||||
<term><option>--identifier=<replaceable>SYSLOG_IDENTIFIER</replaceable></option></term>
|
||||
@@ -862,6 +898,23 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--list-invocations</option></term>
|
||||
|
||||
<listitem>
|
||||
<para>List invocation IDs of a unit. Requires a unit name with <option>-u/--unit=</option> or
|
||||
<option>--user-unit=</option>. 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 <option>-b/-boot</option> is specified, invocations in the boot will be shown.
|
||||
When specified with <option>-n/--lines=<optional>+</optional><replaceable>N</replaceable></option>
|
||||
option, only the first (when the number prefixed with <literal>+</literal>) or the last (without
|
||||
prefix) <replaceable>N</replaceable> entries will be shown. When specified with
|
||||
<option>-r/--reverse</option>, the list will be shown in the reverse order.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--disk-usage</option></term>
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -98,31 +98,16 @@ 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_ BootId *boots = NULL;
|
||||
size_t n_boots;
|
||||
int r;
|
||||
|
||||
assert(arg_action == ACTION_LIST_BOOTS);
|
||||
assert(ids);
|
||||
assert(n_ids > 0);
|
||||
assert(n_ids < INT64_MAX);
|
||||
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,
|
||||
&boots, &n_boots);
|
||||
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();
|
||||
|
||||
@@ -136,25 +121,25 @@ 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++) {
|
||||
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_boots;
|
||||
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, 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);
|
||||
}
|
||||
@@ -166,6 +151,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;
|
||||
@@ -225,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
@@ -99,12 +100,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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+278
-106
@@ -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)) ||
|
||||
@@ -1765,16 +1799,127 @@ 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,
|
||||
sd_id128_t previous_boot_id,
|
||||
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,
|
||||
BootId *ret) {
|
||||
LogId *ret) {
|
||||
|
||||
_cleanup_set_free_ Set *broken_ids = NULL;
|
||||
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
|
||||
@@ -1786,49 +1931,59 @@ 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;
|
||||
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);
|
||||
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. */
|
||||
|
||||
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. */
|
||||
/* 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, boot.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);
|
||||
@@ -1841,17 +1996,19 @@ 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(boot.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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* 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
|
||||
@@ -1863,23 +2020,25 @@ 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(boot.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;
|
||||
}
|
||||
|
||||
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);
|
||||
/* Save the bad boot or invocation ID. */
|
||||
id_dup = newdup(sd_id128_t, &id.id, 1);
|
||||
if (!id_dup)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -1888,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_boot_id)) {
|
||||
r = add_match_boot_id(j, previous_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 (advance_older)
|
||||
r = sd_journal_seek_head(j);
|
||||
@@ -1908,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.",
|
||||
SD_ID128_TO_STRING(previous_boot_id));
|
||||
"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
|
||||
@@ -1949,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;
|
||||
}
|
||||
|
||||
@@ -1967,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) {
|
||||
BootId boot;
|
||||
LogId id;
|
||||
|
||||
r = discover_next_boot(j, boot_id, advance_older, &boot);
|
||||
r = discover_next_id(j, type, boot_id, unit, previous_id, advance_older, &id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
@@ -1982,30 +2147,36 @@ int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t
|
||||
return false;
|
||||
}
|
||||
|
||||
boot_id = boot.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,
|
||||
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(type >= 0 && type < _LOG_ID_TYPE_MAX);
|
||||
assert(type == LOG_BOOT_ID || unit);
|
||||
assert(ret_ids);
|
||||
assert(ret_n_ids);
|
||||
|
||||
sd_journal_flush_matches(j);
|
||||
|
||||
@@ -2021,33 +2192,34 @@ 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_id(j, type, boot_id, unit, 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))
|
||||
/* The boot id is already stored, something wrong with the journal files.
|
||||
* Exiting as otherwise this problem would cause an infinite loop. */
|
||||
FOREACH_ARRAY(i, ids, n_ids)
|
||||
if (sd_id128_equal(i->id, id.id))
|
||||
/* 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(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;
|
||||
}
|
||||
|
||||
+59
-13
@@ -14,11 +14,19 @@
|
||||
#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;
|
||||
|
||||
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,
|
||||
@@ -44,13 +52,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,
|
||||
@@ -71,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,
|
||||
BootId **ret_boots,
|
||||
size_t *ret_n_boots);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -236,6 +236,7 @@ BASICTOOLS=(
|
||||
su
|
||||
sulogin
|
||||
sysctl
|
||||
tac
|
||||
tail
|
||||
tar
|
||||
tee
|
||||
|
||||
Executable
+67
@@ -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"
|
||||
Reference in New Issue
Block a user