diff --git a/src/journal/journalctl-filter.c b/src/journal/journalctl-filter.c index 1c6348574c..b171cf1f90 100644 --- a/src/journal/journalctl-filter.c +++ b/src/journal/journalctl-filter.c @@ -12,7 +12,6 @@ #include "journalctl-util.h" #include "logs-show.h" #include "missing_sched.h" -#include "nulstr-util.h" #include "path-util.h" #include "unit-name.h" @@ -65,73 +64,6 @@ static int add_dmesg(sd_journal *j) { return sd_journal_add_conjunction(j); } -static int get_possible_units( - sd_journal *j, - const char *fields, - char **patterns, - Set **ret) { - - _cleanup_set_free_ Set *found = NULL; - int r; - - assert(j); - assert(fields); - assert(ret); - - NULSTR_FOREACH(field, fields) { - const void *data; - size_t size; - - r = sd_journal_query_unique(j, field); - if (r < 0) - return r; - - SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { - _cleanup_free_ char *u = NULL; - char *eq; - - eq = memchr(data, '=', size); - if (eq) { - size -= eq - (char*) data + 1; - data = ++eq; - } - - u = strndup(data, size); - if (!u) - return -ENOMEM; - - size_t i; - if (!strv_fnmatch_full(patterns, u, FNM_NOESCAPE, &i)) - continue; - - log_debug("Matched %s with pattern %s=%s", u, field, patterns[i]); - r = set_ensure_consume(&found, &string_hash_ops_free, TAKE_PTR(u)); - if (r < 0) - return r; - } - } - - *ret = TAKE_PTR(found); - return 0; -} - -/* This list is supposed to return the superset of unit names - * possibly matched by rules added with add_matches_for_unit... */ -#define SYSTEM_UNITS \ - "_SYSTEMD_UNIT\0" \ - "COREDUMP_UNIT\0" \ - "UNIT\0" \ - "OBJECT_SYSTEMD_UNIT\0" \ - "_SYSTEMD_SLICE\0" - -/* ... and add_matches_for_user_unit */ -#define USER_UNITS \ - "_SYSTEMD_USER_UNIT\0" \ - "USER_UNIT\0" \ - "COREDUMP_USER_UNIT\0" \ - "OBJECT_SYSTEMD_USER_UNIT\0" \ - "_SYSTEMD_USER_SLICE\0" - static int add_units(sd_journal *j) { _cleanup_strv_free_ char **patterns = NULL; bool added = false; @@ -175,7 +107,7 @@ static int add_units(sd_journal *j) { _cleanup_set_free_ Set *units = NULL; char *u; - r = get_possible_units(j, SYSTEM_UNITS, patterns, &units); + r = get_possible_units(j, SYSTEM_UNITS_FULL, patterns, &units); if (r < 0) return r; @@ -218,7 +150,7 @@ static int add_units(sd_journal *j) { _cleanup_set_free_ Set *units = NULL; char *u; - r = get_possible_units(j, USER_UNITS, patterns, &units); + r = get_possible_units(j, USER_UNITS_FULL, patterns, &units); if (r < 0) return r; diff --git a/src/journal/journalctl-misc.c b/src/journal/journalctl-misc.c index 3dea69d9fb..46a70ae1ac 100644 --- a/src/journal/journalctl-misc.c +++ b/src/journal/journalctl-misc.c @@ -246,11 +246,11 @@ int action_list_invocations(void) { assert(arg_action == ACTION_LIST_INVOCATIONS); - r = acquire_unit("--list-invocations", &unit, &type); + r = acquire_journal(&j); if (r < 0) return r; - r = acquire_journal(&j); + r = acquire_unit(j, "--list-invocations", &unit, &type); if (r < 0) return r; diff --git a/src/journal/journalctl-util.c b/src/journal/journalctl-util.c index 1e663b0c8c..918e5713f6 100644 --- a/src/journal/journalctl-util.c +++ b/src/journal/journalctl-util.c @@ -2,14 +2,17 @@ #include +#include "glob-util.h" #include "id128-util.h" #include "journal-util.h" #include "journalctl.h" #include "journalctl-util.h" #include "logs-show.h" +#include "nulstr-util.h" #include "rlimit-util.h" #include "strv.h" #include "terminal-util.h" +#include "unit-name.h" char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) { assert(buf); @@ -111,9 +114,61 @@ 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; +int get_possible_units( + sd_journal *j, + const char *fields, + char * const *patterns, + Set **ret) { + _cleanup_set_free_ Set *found = NULL; + int r; + + assert(j); + assert(fields); + assert(ret); + + NULSTR_FOREACH(field, fields) { + const void *data; + size_t size; + + r = sd_journal_query_unique(j, field); + if (r < 0) + return r; + + SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { + _cleanup_free_ char *u = NULL; + char *eq; + + eq = memchr(data, '=', size); + if (eq) { + size -= eq - (char*) data + 1; + data = ++eq; + } + + u = strndup(data, size); + if (!u) + return -ENOMEM; + + size_t i; + if (!strv_fnmatch_full(patterns, u, FNM_NOESCAPE, &i)) + continue; + + log_debug("Matched %s with pattern %s=%s", u, field, patterns[i]); + r = set_ensure_consume(&found, &string_hash_ops_free, TAKE_PTR(u)); + if (r < 0) + return r; + } + } + + *ret = TAKE_PTR(found); + return 0; +} + +int acquire_unit(sd_journal *j, const char *option_name, const char **ret_unit, LogIdType *ret_type) { + size_t n; + int r; + + assert(j); assert(option_name); assert(ret_unit); assert(ret_type); @@ -128,15 +183,44 @@ int acquire_unit(const char *option_name, const char **ret_unit, LogIdType *ret_ "Using %s with multiple units is not supported.", option_name); + LogIdType type; + char **units; if (!strv_isempty(arg_system_units)) { - *ret_type = LOG_SYSTEM_UNIT_INVOCATION_ID; - *ret_unit = arg_system_units[0]; + type = LOG_SYSTEM_UNIT_INVOCATION_ID; + units = arg_system_units; } else { assert(!strv_isempty(arg_user_units)); - *ret_type = LOG_USER_UNIT_INVOCATION_ID; - *ret_unit = arg_user_units[0]; + type = LOG_USER_UNIT_INVOCATION_ID; + units = arg_user_units; } + _cleanup_free_ char *u = NULL; + r = unit_name_mangle(units[0], UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name '%s': %m", units[0]); + + if (string_is_glob(u)) { + _cleanup_set_free_ Set *s = NULL; + + r = get_possible_units(j, type == LOG_SYSTEM_UNIT_INVOCATION_ID ? SYSTEM_UNITS : USER_UNITS, + STRV_MAKE(u), &s); + if (r < 0) + return log_error_errno(r, "Failed to get matching unit '%s' from journal: %m", u); + if (set_isempty(s)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No matching unit found for '%s' in journal.", u); + if (set_size(s) > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple matching units found for '%s' in journal.", u); + + char *found = set_steal_first(s); + log_debug("Found matching unit '%s' for '%s'.", found, u); + + free_and_replace(units[0], found); + assert(set_isempty(s)); + } else + free_and_replace(units[0], u); + + *ret_type = type; + *ret_unit = units[0]; return 0; } @@ -161,7 +245,7 @@ int journal_acquire_invocation(sd_journal *j) { * 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); + r = acquire_unit(j, "-I/--invocation= with an offset", &unit, &type); if (r < 0) return r; } diff --git a/src/journal/journalctl-util.h b/src/journal/journalctl-util.h index 14e3d569a6..14cdfb5136 100644 --- a/src/journal/journalctl-util.h +++ b/src/journal/journalctl-util.h @@ -4,11 +4,35 @@ #include "sd-journal.h" #include "logs-show.h" +#include "set.h" #include "time-util.h" +/* The lists below are supposed to return the superset of unit names possibly matched by rules added with + * add_matches_for_unit() and add_matches_for_user_unit(). */ +#define SYSTEM_UNITS \ + "_SYSTEMD_UNIT\0" \ + "UNIT\0" \ + "OBJECT_SYSTEMD_UNIT\0" + +#define SYSTEM_UNITS_FULL \ + SYSTEM_UNITS \ + "COREDUMP_UNIT\0" \ + "_SYSTEMD_SLICE\0" + +#define USER_UNITS \ + "_SYSTEMD_USER_UNIT\0" \ + "USER_UNIT\0" \ + "OBJECT_SYSTEMD_USER_UNIT\0" + +#define USER_UNITS_FULL \ + USER_UNITS \ + "COREDUMP_USER_UNIT\0" \ + "_SYSTEMD_USER_SLICE\0" + 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 get_possible_units(sd_journal *j, const char *fields, char * const *patterns, Set **ret); +int acquire_unit(sd_journal *j, const char *option_name, const char **ret_unit, LogIdType *ret_type); int journal_acquire_invocation(sd_journal *j); diff --git a/test/units/TEST-04-JOURNAL.invocation.sh b/test/units/TEST-04-JOURNAL.invocation.sh index e7a6f547b4..74db1040fd 100755 --- a/test/units/TEST-04-JOURNAL.invocation.sh +++ b/test/units/TEST-04-JOURNAL.invocation.sh @@ -7,7 +7,10 @@ set -o pipefail # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh -SERVICE_NAME=invocation-id-test-"$RANDOM".service +BASE=test-"$RANDOM" +SERVICE_NAME_SHORT=invocation-id-"$BASE" +SERVICE_NAME="$SERVICE_NAME_SHORT".service +SERVICE_NAME_GLOB="invocation-*-${BASE}.service" TMP_DIR=$(mktemp -d) @@ -26,6 +29,8 @@ done # systemd[1]: invocation-id-test-26448.service: Deactivated successfully. journalctl --sync +journalctl --list-invocation -u "$SERVICE_NAME_SHORT" | tee "$TMP_DIR"/short +journalctl --list-invocation -u "$SERVICE_NAME_GLOB" | tee "$TMP_DIR"/glob 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 @@ -44,6 +49,8 @@ journalctl --list-invocation -u "$SERVICE_NAME" -n +5 --reverse | tee "$TMP_DIR" [[ $(cat "$TMP_DIR"/p5 | wc -l) == 6 ]] [[ $(cat "$TMP_DIR"/p5-r | wc -l) == 6 ]] +diff "$TMP_DIR"/10 "$TMP_DIR"/short +diff "$TMP_DIR"/10 "$TMP_DIR"/glob 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) @@ -54,6 +61,10 @@ 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_SHORT")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${idx}" -u "$SERVICE_NAME_SHORT")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${i}" -u "$SERVICE_NAME_GLOB")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${idx}" -u "$SERVICE_NAME_GLOB")" 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}")" @@ -62,6 +73,10 @@ tail -n 10 "$TMP_DIR"/10 | 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_SHORT")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${idx}" -u "$SERVICE_NAME_SHORT")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${i}" -u "$SERVICE_NAME_GLOB")" + assert_in "invocation ${i} ${invocation}" "$(journalctl --no-hostname -n 1 -t bash --invocation="${idx}" -u "$SERVICE_NAME_GLOB")" 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}")"