diff --git a/shell-completion/bash/journalctl b/shell-completion/bash/journalctl index 0b40f04eb1..550e2be686 100644 --- a/shell-completion/bash/journalctl +++ b/shell-completion/bash/journalctl @@ -45,7 +45,8 @@ _journalctl() { --version --list-catalog --update-catalog --list-boots --show-cursor --dmesg -k --pager-end -e -r --reverse --utc -x --catalog --no-full --force --dump-catalog - --flush --rotate --sync --no-hostname -N --fields' + --flush --rotate --sync --no-hostname -N --fields + --list-namespaces' [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' diff --git a/shell-completion/zsh/_journalctl b/shell-completion/zsh/_journalctl index 5dba1e7dfe..7e18d43158 100644 --- a/shell-completion/zsh/_journalctl +++ b/shell-completion/zsh/_journalctl @@ -131,6 +131,7 @@ _arguments -s \ '--header[Show journal header information]' \ '--interval=[Time interval for changing the FSS sealing key]:time interval' \ '--list-catalog[List messages in catalog]' \ + '--list-namespaces[List available journal namespaces]' \ '--new-id128[Generate a new 128 Bit ID]' \ '--rotate[Request immediate rotation of the journal files]' \ '--setup-keys[Generate a new FSS key pair]' \ diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index c26d7f1def..cc476c30db 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -28,6 +28,7 @@ #include "chattr-util.h" #include "constants.h" #include "devnum-util.h" +#include "dirent-util.h" #include "dissect-image.h" #include "fd-util.h" #include "fileio.h" @@ -175,6 +176,7 @@ static enum { ACTION_ROTATE_AND_VACUUM, ACTION_LIST_FIELDS, ACTION_LIST_FIELD_NAMES, + ACTION_LIST_NAMESPACES, } arg_action = ACTION_SHOW; static int add_matches_for_device(sd_journal *j, const char *devpath) { @@ -491,6 +493,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_HOSTNAME, ARG_OUTPUT_FIELDS, ARG_NAMESPACE, + ARG_LIST_NAMESPACES, }; static const struct option options[] = { @@ -561,6 +564,7 @@ static int parse_argv(int argc, char *argv[]) { { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME }, { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, { "namespace", required_argument, NULL, ARG_NAMESPACE }, + { "list-namespaces", no_argument, NULL, ARG_LIST_NAMESPACES }, {} }; @@ -727,6 +731,10 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_LIST_NAMESPACES: + arg_action = ACTION_LIST_NAMESPACES; + break; + case 'D': arg_directory = optarg; break; @@ -1229,6 +1237,64 @@ static int add_matches(sd_journal *j, char **args) { return 0; } +static int list_namespaces(const char *root) { + _cleanup_(table_unrefp) Table *table = NULL; + sd_id128_t machine; + char machine_id[SD_ID128_STRING_MAX]; + int r; + + r = sd_id128_get_machine(&machine); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + sd_id128_to_string(machine, machine_id); + + table = table_new("namespace"); + if (!table) + return log_oom(); + + (void) table_set_sort(table, (size_t) 0); + + FOREACH_STRING(dir, "/var/log/journal", "/run/log/journal") { + _cleanup_free_ char *path = NULL; + _cleanup_closedir_ DIR *dirp = NULL; + + path = path_join(root, dir); + if (!path) + return log_oom(); + + dirp = opendir(path); + if (!dirp) { + log_debug_errno(errno, "Failed to open directory %s, ignoring: %m", path); + continue; + } + + FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", path)) { + char *dot; + + if (!startswith(de->d_name, machine_id)) + continue; + + dot = strchr(de->d_name, '.'); + if (!dot) + continue; + + if (!log_namespace_name_valid(dot + 1)) + continue; + + r = table_add_cell(table, NULL, TABLE_STRING, dot + 1); + if (r < 0) + return table_log_add_error(r); + } + } + + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet); + if (r < 0) + return table_log_print_error(r); + + return 0; +} + static int list_boots(sd_journal *j) { _cleanup_(table_unrefp) Table *table = NULL; _cleanup_free_ BootId *boots = NULL; @@ -2288,6 +2354,9 @@ static int run(int argc, char *argv[]) { case ACTION_ROTATE: return rotate(); + case ACTION_LIST_NAMESPACES: + return list_namespaces(arg_root); + case ACTION_SHOW: case ACTION_PRINT_HEADER: case ACTION_VERIFY: diff --git a/test/units/testsuite-44.sh b/test/units/testsuite-44.sh index fbd4ae692c..cc5f08f32a 100755 --- a/test/units/testsuite-44.sh +++ b/test/units/testsuite-44.sh @@ -4,12 +4,20 @@ set -eux systemd-analyze log-level debug +journalctl --list-namespaces -o json | jq . + systemd-run --wait -p LogNamespace=foobar echo "hello world" +systemd-run --wait -p LogNamespace=foobaz echo "hello world" journalctl --namespace=foobar --sync journalctl -o cat --namespace=foobar >/tmp/hello-world journalctl -o cat >/tmp/no-hello-world +journalctl --list-namespaces | grep foobar +journalctl --list-namespaces | grep foobaz +journalctl --list-namespaces -o json | jq . +[[ "$(journalctl --root=/tmp --list-namespaces --quiet)" == "" ]] + grep "^hello world$" /tmp/hello-world (! grep "^hello world$" /tmp/no-hello-world)