From: Zbigniew Jędrzejewski-Szmek Date: Tue, 12 May 2026 11:28:35 +0000 (+0200) Subject: journalctl: convert to OPTION macros X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c3f4c1b8d6b9bad0d88255ce40956a8f513c098a;p=thirdparty%2Fsystemd.git journalctl: convert to OPTION macros Two namespaces are used: "journalctl" and "journalctl-varlink". Help for --user/--system in the latter is added, even though it is not used yet. I think it'll be good to have this for introspection. The four FSS-related options (--interval, --verify-key, --force, --setup-keys) unfortunately each gain an inline #if HAVE_GCRYPT / #else; the EOPNOTSUPP fallback is duplicated four times. The metavar for --identifier/--exclude-identifier is changed to "ID" to make the layout nicer. (And because that seems to make more sense.) Co-developed-by: Claude Opus 4.7 --- diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 2aeeeb55314..2b4995714f5 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-journal.h" @@ -9,7 +8,9 @@ #include "build.h" #include "dissect-image.h" #include "extract-word.h" +#include "format-table.h" #include "glob-util.h" +#include "help-util.h" #include "id128-print.h" #include "image-policy.h" #include "journalctl.h" @@ -24,12 +25,12 @@ #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "pcre2-util.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "set.h" #include "static-destruct.h" @@ -229,106 +230,42 @@ static int help_facilities(void) { } static int help(void) { - _cleanup_free_ char *link = NULL; + static const char *const groups[] = { + "Source Options", + "Filtering Options", + "Output Control Options", + "Pager Control Options", + "Forward Secure Sealing (FSS) Options", + "Commands", + }; + + Table *tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("journalctl", "1", &link); - if (r < 0) - return log_oom(); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_full("journalctl", groups[i], &tables[i]); + if (r < 0) + return r; + } + + assert_se(ELEMENTSOF(tables) == 6); + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], + tables[3], tables[4], tables[5]); - printf("%1$s [OPTIONS...] [MATCHES...]\n\n" - "%5$sQuery the journal.%6$s\n\n" - "%3$sSource Options:%4$s\n" - " --system Show the system journal\n" - " --user Show the user journal for the current user\n" - " -M --machine=CONTAINER Operate on local container\n" - " -m --merge Show entries from all available journals\n" - " -D --directory=PATH Show journal files from directory\n" - " -i --file=PATH Show journal file\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\n" - " --namespace=NAMESPACE Show journal data from specified journal namespace\n" - "\n%3$sFiltering Options:%4$s\n" - " -S --since=DATE Show entries not older than the specified date\n" - " -U --until=DATE Show entries not newer than the specified date\n" - " -c --cursor=CURSOR Show entries starting at the specified cursor\n" - " --after-cursor=CURSOR Show entries after the specified cursor\n" - " --cursor-file=FILE Show entries after cursor in FILE and update FILE\n" - " -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" - " -p --priority=RANGE Show entries within the specified priority range\n" - " --facility=FACILITY... Show entries with the specified facilities\n" - " -g --grep=PATTERN Show entries with MESSAGE matching PATTERN\n" - " --case-sensitive[=BOOL] Force case sensitive or insensitive matching\n" - " -k --dmesg Show kernel message log from the current boot\n" - "\n%3$sOutput Control Options:%4$s\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, verbose, export,\n" - " json, json-pretty, json-sse, json-seq, cat,\n" - " with-unit)\n" - " --output-fields=LIST Select fields to print in verbose/export/json modes\n" - " -n --lines[=[+]INTEGER] Number of journal entries to show\n" - " -r --reverse Show the newest entries first\n" - " --show-cursor Print the cursor after all the entries\n" - " --utc Express time in Coordinated Universal Time (UTC)\n" - " -x --catalog Add message explanations where available\n" - " -W --no-hostname Suppress output of hostname field\n" - " --no-full Ellipsize fields\n" - " -a --all Show all fields, including long and unprintable\n" - " -f --follow Follow the journal\n" - " --no-tail Show all lines, even in follow mode\n" - " --truncate-newline Truncate entries by first newline character\n" - " -q --quiet Do not show info messages and privilege warning\n" - " --synchronize-on-exit=BOOL\n" - " Wait for Journal synchronization before exiting\n" - "\n%3$sPager Control Options:%4$s\n" - " --no-pager Do not pipe output into a pager\n" - " -e --pager-end Immediately jump to the end in the pager\n" - "\n%3$sForward Secure Sealing (FSS) Options:%4$s\n" - " --interval=TIME Time interval for changing the FSS sealing key\n" - " --verify-key=KEY Specify FSS verification key\n" - " --force Override of the FSS key pair with --setup-keys\n" - "\n%3$sCommands:%4$s\n" - " -h --help Show this help text\n" - " --version Show package version\n" - " -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" - " --vacuum-files=INT Leave only the specified number of journal files\n" - " --vacuum-time=TIME Remove journal files older than specified time\n" - " --verify Verify journal file consistency\n" - " --sync Synchronize unwritten journal messages to disk\n" - " --relinquish-var Stop logging to disk, log to temporary file system\n" - " --smart-relinquish-var Similar, but NOP if log directory is on root mount\n" - " --flush Flush all journal data from /run into /var\n" - " --rotate Request immediate rotation of the journal files\n" - " --header Show journal header information\n" - " --list-catalog Show all message IDs in the catalog\n" - " --dump-catalog Show entries in the message catalog\n" - " --update-catalog Update the message catalog database\n" - " --setup-keys Generate a new FSS key pair\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS…] [MATCHES…]"); + help_abstract("Query the journal."); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + help_section(groups[i]); + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + help_man_page_reference("journalctl", "1"); return 0; } @@ -355,134 +292,12 @@ static int vl_server(void) { return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_FULL, - ARG_NO_TAIL, - ARG_NEW_ID128, - ARG_THIS_BOOT, - ARG_LIST_BOOTS, - ARG_LIST_INVOCATIONS, - ARG_USER, - ARG_SYSTEM, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_HEADER, - ARG_FACILITY, - ARG_SETUP_KEYS, - ARG_INTERVAL, - ARG_VERIFY, - ARG_VERIFY_KEY, - ARG_DISK_USAGE, - ARG_AFTER_CURSOR, - ARG_CURSOR_FILE, - ARG_SHOW_CURSOR, - ARG_USER_UNIT, - ARG_INVOCATION, - ARG_LIST_CATALOG, - ARG_DUMP_CATALOG, - ARG_UPDATE_CATALOG, - ARG_FORCE, - ARG_CASE_SENSITIVE, - ARG_UTC, - ARG_SYNC, - ARG_FLUSH, - ARG_RELINQUISH_VAR, - ARG_SMART_RELINQUISH_VAR, - ARG_ROTATE, - ARG_TRUNCATE_NEWLINE, - ARG_VACUUM_SIZE, - ARG_VACUUM_FILES, - ARG_VACUUM_TIME, - ARG_OUTPUT_FIELDS, - ARG_NAMESPACE, - ARG_LIST_NAMESPACES, - ARG_SYNCHRONIZE_ON_EXIT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "pager-end", no_argument, NULL, 'e' }, - { "follow", no_argument, NULL, 'f' }, - { "force", no_argument, NULL, ARG_FORCE }, - { "output", required_argument, NULL, 'o' }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "no-full", no_argument, NULL, ARG_NO_FULL }, - { "lines", optional_argument, NULL, 'n' }, - { "truncate-newline", no_argument, NULL, ARG_TRUNCATE_NEWLINE }, - { "no-tail", no_argument, NULL, ARG_NO_TAIL }, - { "new-id128", no_argument, NULL, ARG_NEW_ID128 }, /* deprecated */ - { "quiet", no_argument, NULL, 'q' }, - { "merge", no_argument, NULL, 'm' }, - { "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 }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, 'i' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "header", no_argument, NULL, ARG_HEADER }, - { "identifier", required_argument, NULL, 't' }, - { "exclude-identifier", required_argument, NULL, 'T' }, - { "priority", required_argument, NULL, 'p' }, - { "facility", required_argument, NULL, ARG_FACILITY }, - { "grep", required_argument, NULL, 'g' }, - { "case-sensitive", optional_argument, NULL, ARG_CASE_SENSITIVE }, - { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, - { "interval", required_argument, NULL, ARG_INTERVAL }, - { "verify", no_argument, NULL, ARG_VERIFY }, - { "verify-key", required_argument, NULL, ARG_VERIFY_KEY }, - { "disk-usage", no_argument, NULL, ARG_DISK_USAGE }, - { "cursor", required_argument, NULL, 'c' }, - { "cursor-file", required_argument, NULL, ARG_CURSOR_FILE }, - { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, - { "show-cursor", no_argument, NULL, ARG_SHOW_CURSOR }, - { "since", required_argument, NULL, 'S' }, - { "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' }, - { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, - { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, - { "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG }, - { "reverse", no_argument, NULL, 'r' }, - { "machine", required_argument, NULL, 'M' }, - { "utc", no_argument, NULL, ARG_UTC }, - { "flush", no_argument, NULL, ARG_FLUSH }, - { "relinquish-var", no_argument, NULL, ARG_RELINQUISH_VAR }, - { "smart-relinquish-var", no_argument, NULL, ARG_SMART_RELINQUISH_VAR }, - { "sync", no_argument, NULL, ARG_SYNC }, - { "rotate", no_argument, NULL, ARG_ROTATE }, - { "vacuum-size", required_argument, NULL, ARG_VACUUM_SIZE }, - { "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES }, - { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME }, - { "no-hostname", no_argument, NULL, 'W' }, - { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, - { "namespace", required_argument, NULL, ARG_NAMESPACE }, - { "list-namespaces", no_argument, NULL, ARG_LIST_NAMESPACES }, - { "synchronize-on-exit", required_argument, NULL, ARG_SYNCHRONIZE_ON_EXIT }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); if (r < 0) @@ -490,228 +305,237 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) { arg_varlink = true; - static const struct option varlink_options[] = { - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - while ((c = getopt_long(argc, argv, "", varlink_options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "journalctl-varlink" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_SYSTEM: + OPTION_NAMESPACE("journalctl-varlink"): {} + + OPTION_COMMON_SYSTEM: arg_varlink_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_COMMON_USER: arg_varlink_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_varlink_runtime_scope < 0) return log_error_errno(arg_varlink_runtime_scope, "Cannot run in Varlink mode with no runtime scope specified."); + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected in Varlink mode."); + + *remaining_args = NULL; return 1; } - while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:INF:xrM:i:W", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "journalctl" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_SYSTEM: + OPTION_NAMESPACE("journalctl"): {} + + OPTION_GROUP("Source Options"): {} + + OPTION_LONG("system", NULL, "Show the system journal"): arg_journal_type |= SD_JOURNAL_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Show the user journal for the current user"): arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; - case 'M': - r = free_and_strdup_warn(&arg_machine, optarg); + OPTION_COMMON_MACHINE: + r = free_and_strdup_warn(&arg_machine, opts.arg); if (r < 0) return r; break; - case 'm': + OPTION('m', "merge", NULL, "Show entries from all available journals"): arg_merge = true; break; - case 'D': - r = free_and_strdup_warn(&arg_directory, optarg); + OPTION('D', "directory", "PATH", "Show journal files from directory"): + r = free_and_strdup_warn(&arg_directory, opts.arg); if (r < 0) return r; break; - case 'i': - if (streq(optarg, "-")) + OPTION('i', "file", "PATH", "Show journal file"): + if (streq(opts.arg, "-")) /* An undocumented feature: we can read journal files from STDIN. We don't document * this though, since after all we only support this for mmap-able, seekable files, and * not for example pipes which are probably the primary use case for reading things from * STDIN. To avoid confusion we hence don't document this feature. */ arg_file_stdin = true; else { - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); } break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_NAMESPACE: - if (streq(optarg, "*")) { + OPTION_LONG("namespace", "NAMESPACE", + "Show journal data from specified journal namespace"): + if (streq(opts.arg, "*")) { arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; arg_namespace = mfree(arg_namespace); - } else if (startswith(optarg, "+")) { + } else if (startswith(opts.arg, "+")) { arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; - r = free_and_strdup_warn(&arg_namespace, optarg + 1); + r = free_and_strdup_warn(&arg_namespace, opts.arg + 1); if (r < 0) return r; - } else if (isempty(optarg)) { + } else if (isempty(opts.arg)) { arg_namespace_flags = 0; arg_namespace = mfree(arg_namespace); } else { arg_namespace_flags = 0; - r = free_and_strdup_warn(&arg_namespace, optarg); + r = free_and_strdup_warn(&arg_namespace, opts.arg); if (r < 0) return r; } break; - case 'S': - r = parse_timestamp(optarg, &arg_since); + OPTION_GROUP("Filtering Options"): {} + + OPTION('S', "since", "DATE", "Show entries not older than the specified date"): + r = parse_timestamp(opts.arg, &arg_since); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse timestamp: %s", optarg); + "Failed to parse timestamp: %s", opts.arg); arg_since_set = true; break; - case 'U': - r = parse_timestamp(optarg, &arg_until); + OPTION('U', "until", "DATE", "Show entries not newer than the specified date"): + r = parse_timestamp(opts.arg, &arg_until); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse timestamp: %s", optarg); + "Failed to parse timestamp: %s", opts.arg); arg_until_set = true; break; - case 'c': - r = free_and_strdup_warn(&arg_cursor, optarg); + OPTION('c', "cursor", "CURSOR", "Show entries starting at the specified cursor"): + r = free_and_strdup_warn(&arg_cursor, opts.arg); if (r < 0) return r; break; - case ARG_AFTER_CURSOR: - r = free_and_strdup_warn(&arg_after_cursor, optarg); + OPTION_LONG("after-cursor", "CURSOR", "Show entries after the specified cursor"): + r = free_and_strdup_warn(&arg_after_cursor, opts.arg); if (r < 0) return r; break; - case ARG_CURSOR_FILE: - r = free_and_strdup_warn(&arg_cursor_file, optarg); + OPTION_LONG("cursor-file", "FILE", "Show entries after cursor in FILE and update FILE"): + r = free_and_strdup_warn(&arg_cursor_file, opts.arg); if (r < 0) return r; break; - case ARG_THIS_BOOT: + OPTION_LONG("this-boot", NULL, /* help= */ NULL): arg_boot = true; arg_boot_id = SD_ID128_NULL; arg_boot_offset = 0; break; - case 'b': + OPTION_FULL(OPTION_OPTIONAL_ARG, 'b', "boot", "ID", + "Show current boot or the specified boot"): arg_boot = true; arg_boot_id = SD_ID128_NULL; arg_boot_offset = 0; - if (optarg) { - r = parse_id_descriptor(optarg, &arg_boot_id, &arg_boot_offset); + if (opts.arg) { + r = parse_id_descriptor(opts.arg, &arg_boot_id, &arg_boot_offset); if (r < 0) - return log_error_errno(r, "Failed to parse boot descriptor '%s'", optarg); + return log_error_errno(r, "Failed to parse boot descriptor '%s'", opts.arg); arg_boot = r; - } else if (optind < argc) { - /* Hmm, no argument? Maybe the next word on the command line is supposed to be the - * argument? Let's see if there is one and is parsable as a boot descriptor... */ - r = parse_id_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset); - if (r >= 0) { - arg_boot = r; - optind++; + } else { + /* Hmm, no argument? Maybe the next word on the command line is supposed to + * be the argument? Let's see if there is one and is parsable as a boot + * descriptor… */ + char *peek = option_parser_peek_next_arg(&opts); + if (peek) { + r = parse_id_descriptor(peek, &arg_boot_id, &arg_boot_offset); + if (r >= 0) { + arg_boot = r; + (void) option_parser_consume_next_arg(&opts); + } } } break; - case 'u': - r = strv_extend(&arg_system_units, optarg); + OPTION('u', "unit", "UNIT", "Show logs from the specified unit"): + r = strv_extend(&arg_system_units, opts.arg); if (r < 0) return log_oom(); break; - case ARG_USER_UNIT: - r = strv_extend(&arg_user_units, optarg); + OPTION_LONG("user-unit", "UNIT", "Show logs from the specified user unit"): + r = strv_extend(&arg_user_units, opts.arg); if (r < 0) return log_oom(); break; - case ARG_INVOCATION: - r = parse_id_descriptor(optarg, &arg_invocation_id, &arg_invocation_offset); + OPTION_LONG("invocation", "ID", "Show logs from the matching invocation ID"): + r = parse_id_descriptor(opts.arg, &arg_invocation_id, &arg_invocation_offset); if (r < 0) - return log_error_errno(r, "Failed to parse invocation descriptor: %s", optarg); + return log_error_errno(r, "Failed to parse invocation descriptor: %s", opts.arg); arg_invocation = r; break; - case 'I': + OPTION_SHORT('I', NULL, "Show logs from the latest invocation of unit"): /* Equivalent to --invocation=0 */ arg_invocation = true; arg_invocation_id = SD_ID128_NULL; arg_invocation_offset = 0; break; - case 't': - r = strv_extend(&arg_syslog_identifier, optarg); + OPTION('t', "identifier", "ID", "Show entries with the specified syslog identifier"): + r = strv_extend(&arg_syslog_identifier, opts.arg); if (r < 0) return log_oom(); break; - case 'T': - r = strv_extend(&arg_exclude_identifier, optarg); + OPTION('T', "exclude-identifier", "ID", + "Hide entries with the specified syslog identifier"): + r = strv_extend(&arg_exclude_identifier, opts.arg); if (r < 0) return log_oom(); break; - case 'p': { + OPTION('p', "priority", "RANGE", "Show entries within the specified priority range"): { const char *dots; - dots = strstr(optarg, ".."); + dots = strstr(opts.arg, ".."); if (dots) { _cleanup_free_ char *a = NULL; int from, to, i; /* a range */ - a = strndup(optarg, dots - optarg); + a = strndup(opts.arg, dots - opts.arg); if (!a) return log_oom(); @@ -720,7 +544,7 @@ static int parse_argv(int argc, char *argv[]) { if (from < 0 || to < 0) return log_error_errno(from < 0 ? from : to, - "Failed to parse log level range %s", optarg); + "Failed to parse log level range %s", opts.arg); arg_priorities = 0; @@ -735,9 +559,9 @@ static int parse_argv(int argc, char *argv[]) { } else { int p, i; - p = log_level_from_string(optarg); + p = log_level_from_string(opts.arg); if (p < 0) - return log_error_errno(p, "Unknown log level %s", optarg); + return log_error_errno(p, "Unknown log level %s", opts.arg); arg_priorities = 0; @@ -748,16 +572,16 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_FACILITY: { + OPTION_LONG("facility", "FACILITY…", "Show entries with the specified facilities"): { const char *p; - for (p = optarg;;) { + for (p = opts.arg;;) { _cleanup_free_ char *fac = NULL; int num; r = extract_first_word(&p, &fac, ",", 0); if (r < 0) - return log_error_errno(r, "Failed to parse facilities: %s", optarg); + return log_error_errno(r, "Failed to parse facilities: %s", opts.arg); if (r == 0) break; @@ -777,34 +601,40 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'g': - r = free_and_strdup_warn(&arg_pattern, optarg); + OPTION('g', "grep", "PATTERN", "Show entries with MESSAGE matching PATTERN"): + r = free_and_strdup_warn(&arg_pattern, opts.arg); if (r < 0) return r; break; - case ARG_CASE_SENSITIVE: - if (optarg) { - r = parse_boolean(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "case-sensitive", "BOOL", + "Force case sensitive or insensitive matching"): + if (opts.arg) { + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", optarg); + return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", opts.arg); arg_case = r ? PATTERN_COMPILE_CASE_SENSITIVE : PATTERN_COMPILE_CASE_INSENSITIVE; } else arg_case = PATTERN_COMPILE_CASE_SENSITIVE; break; - case 'k': + OPTION('k', "dmesg", NULL, "Show kernel message log from the current boot"): arg_dmesg = true; break; - case 'o': - if (streq(optarg, "help")) + OPTION_GROUP("Output Control Options"): {} + + OPTION('o', "output", "STRING", + "Change journal output mode (short, short-precise, short-iso, short-iso-precise, " + "short-full, short-monotonic, short-unix, verbose, export, json, json-pretty, " + "json-sse, json-seq, cat, with-unit)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - arg_output = output_mode_from_string(optarg); + arg_output = output_mode_from_string(opts.arg); if (arg_output < 0) - return log_error_errno(arg_output, "Unknown output format '%s'.", optarg); + return log_error_errno(arg_output, "Unknown output format '%s'.", opts.arg); if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT)) arg_quiet = true; @@ -816,10 +646,10 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_OUTPUT_FIELDS: { + OPTION_LONG("output-fields", "LIST", "Select fields to print in verbose/export/json modes"): { _cleanup_strv_free_ char **v = NULL; - v = strv_split(optarg, ","); + v = strv_split(opts.arg, ","); if (!v) return log_oom(); @@ -830,182 +660,195 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'n': - r = parse_lines(optarg ?: argv[optind], !optarg); + OPTION_FULL(OPTION_OPTIONAL_ARG, 'n', "lines", "[+]INTEGER", + "Number of journal entries to show"): { + const char *p = opts.arg ?: option_parser_peek_next_arg(&opts); + + r = parse_lines(p, /* graceful= */ !opts.arg); if (r < 0) return r; - if (r > 0 && !optarg) - optind++; + if (r > 0 && !opts.arg) + (void) option_parser_consume_next_arg(&opts); break; + } - case 'r': + OPTION('r', "reverse", NULL, "Show the newest entries first"): arg_reverse = true; break; - case ARG_SHOW_CURSOR: + OPTION_LONG("show-cursor", NULL, "Print the cursor after all the entries"): arg_show_cursor = true; break; - case ARG_UTC: + OPTION_LONG("utc", NULL, "Express time in Coordinated Universal Time (UTC)"): arg_utc = true; break; - case 'x': + OPTION('x', "catalog", NULL, "Add message explanations where available"): arg_catalog = true; break; - case 'W': + OPTION('W', "no-hostname", NULL, "Suppress output of hostname field"): arg_no_hostname = true; break; - case 'l': + OPTION('l', "full", NULL, /* help= */ NULL): arg_full = true; break; - case ARG_NO_FULL: + OPTION_LONG("no-full", NULL, "Ellipsize fields"): arg_full = false; break; - case 'a': + OPTION('a', "all", NULL, "Show all fields, including long and unprintable"): arg_all = true; break; - case 'f': + OPTION('f', "follow", NULL, "Follow the journal"): arg_follow = true; break; - case ARG_NO_TAIL: + OPTION_LONG("no-tail", NULL, "Show all lines, even in follow mode"): arg_no_tail = true; break; - case ARG_TRUNCATE_NEWLINE: + OPTION_LONG("truncate-newline", NULL, "Truncate entries by first newline character"): arg_truncate_newline = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not show info messages and privilege warning"): arg_quiet = true; break; - case ARG_SYNCHRONIZE_ON_EXIT: - r = parse_boolean_argument("--synchronize-on-exit", optarg, &arg_synchronize_on_exit); + OPTION_LONG("synchronize-on-exit", "BOOL", + "Wait for Journal synchronization before exiting"): + r = parse_boolean_argument("--synchronize-on-exit", opts.arg, &arg_synchronize_on_exit); if (r < 0) return r; - break; - case ARG_NO_PAGER: + OPTION_GROUP("Pager Control Options"): {} + + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'e': + OPTION('e', "pager-end", NULL, "Immediately jump to the end in the pager"): arg_pager_flags |= PAGER_JUMP_TO_END; break; + OPTION_GROUP("Forward Secure Sealing (FSS) Options"): {} + + OPTION_LONG("interval", "TIME", "Time interval for changing the FSS sealing key"): #if HAVE_GCRYPT - case ARG_INTERVAL: - r = parse_sec(optarg, &arg_interval); + r = parse_sec(opts.arg, &arg_interval); if (r < 0 || arg_interval <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse sealing key change interval: %s", optarg); + "Failed to parse sealing key change interval: %s", opts.arg); break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case ARG_VERIFY_KEY: + OPTION_LONG("verify-key", "KEY", "Specify FSS verification key"): +#if HAVE_GCRYPT erase_and_free(arg_verify_key); - arg_verify_key = strdup(optarg); + arg_verify_key = strdup(opts.arg); if (!arg_verify_key) return log_oom(); /* Use memset not explicit_bzero() or similar so this doesn't look confusing - * in ps or htop output. */ - memset(optarg, 'x', strlen(optarg)); + * in ps or htop output. We need to cast away the const to do this. */ + memset((char*) opts.arg, 'x', strlen(opts.arg)); arg_action = ACTION_VERIFY; arg_merge = false; break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case ARG_FORCE: + OPTION_LONG("force", NULL, "Override of the FSS key pair with --setup-keys"): +#if HAVE_GCRYPT arg_force = true; break; - - case ARG_SETUP_KEYS: - arg_action = ACTION_SETUP_KEYS; - break; #else - case ARG_INTERVAL: - case ARG_VERIFY_KEY: - case ARG_FORCE: - case ARG_SETUP_KEYS: return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without forward-secure sealing support."); #endif - case 'h': + OPTION_GROUP("Commands"): {} + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'N': + OPTION('N', "fields", NULL, "List all field names currently used"): arg_action = ACTION_LIST_FIELD_NAMES; break; - case 'F': + OPTION('F', "field", "FIELD", "List all values that a specified field takes"): arg_action = ACTION_LIST_FIELDS; - r = free_and_strdup_warn(&arg_field, optarg); + r = free_and_strdup_warn(&arg_field, opts.arg); if (r < 0) return r; break; - case ARG_LIST_BOOTS: + OPTION_LONG("list-boots", NULL, "Show terse information about recorded boots"): arg_action = ACTION_LIST_BOOTS; break; - case ARG_LIST_INVOCATIONS: + OPTION_LONG("list-invocations", NULL, "Show invocation IDs of specified unit"): arg_action = ACTION_LIST_INVOCATIONS; break; - case ARG_LIST_NAMESPACES: + OPTION_LONG("list-namespaces", NULL, "Show list of journal namespaces"): arg_action = ACTION_LIST_NAMESPACES; break; - case ARG_DISK_USAGE: + OPTION_LONG("disk-usage", NULL, "Show total disk usage of all journal files"): arg_action = ACTION_DISK_USAGE; break; - case ARG_VACUUM_SIZE: - r = parse_size(optarg, 1024, &arg_vacuum_size); + OPTION_LONG("vacuum-size", "BYTES", "Reduce disk usage below specified size"): + r = parse_size(opts.arg, 1024, &arg_vacuum_size); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum size: %s", optarg); + return log_error_errno(r, "Failed to parse vacuum size: %s", opts.arg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_VACUUM_FILES: - r = safe_atou64(optarg, &arg_vacuum_n_files); + OPTION_LONG("vacuum-files", "INT", "Leave only the specified number of journal files"): + r = safe_atou64(opts.arg, &arg_vacuum_n_files); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum files: %s", optarg); + return log_error_errno(r, "Failed to parse vacuum files: %s", opts.arg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_VACUUM_TIME: - r = parse_sec(optarg, &arg_vacuum_time); + OPTION_LONG("vacuum-time", "TIME", "Remove journal files older than specified time"): + r = parse_sec(opts.arg, &arg_vacuum_time); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum time: %s", optarg); + return log_error_errno(r, "Failed to parse vacuum time: %s", opts.arg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_VERIFY: + OPTION_LONG("verify", NULL, "Verify journal file consistency"): arg_action = ACTION_VERIFY; break; - case ARG_SYNC: + OPTION_LONG("sync", NULL, "Synchronize unwritten journal messages to disk"): arg_action = ACTION_SYNC; break; - case ARG_SMART_RELINQUISH_VAR: { + OPTION_LONG("smart-relinquish-var", NULL, + "Similar, but NOP if log directory is on root mount"): { int root_mnt_id, log_mnt_id; /* Try to be smart about relinquishing access to /var/log/journal/ during shutdown: @@ -1030,45 +873,51 @@ static int parse_argv(int argc, char *argv[]) { _fallthrough_; } - case ARG_RELINQUISH_VAR: + OPTION_LONG("relinquish-var", NULL, "Stop logging to disk, log to temporary file system"): arg_action = ACTION_RELINQUISH_VAR; break; - case ARG_FLUSH: + OPTION_LONG("flush", NULL, "Flush all journal data from /run into /var"): arg_action = ACTION_FLUSH; break; - case ARG_ROTATE: + OPTION_LONG("rotate", NULL, "Request immediate rotation of the journal files"): arg_action = arg_action == ACTION_VACUUM ? ACTION_ROTATE_AND_VACUUM : ACTION_ROTATE; break; - case ARG_HEADER: + OPTION_LONG("header", NULL, "Show journal header information"): arg_action = ACTION_PRINT_HEADER; break; - case ARG_LIST_CATALOG: + OPTION_LONG("list-catalog", NULL, "Show all message IDs in the catalog"): arg_action = ACTION_LIST_CATALOG; break; - case ARG_DUMP_CATALOG: + OPTION_LONG("dump-catalog", NULL, "Show entries in the message catalog"): arg_action = ACTION_DUMP_CATALOG; break; - case ARG_UPDATE_CATALOG: + OPTION_LONG("update-catalog", NULL, "Update the message catalog database"): arg_action = ACTION_UPDATE_CATALOG; break; - case ARG_NEW_ID128: - arg_action = ACTION_NEW_ID128; + OPTION_LONG("setup-keys", NULL, "Generate a new FSS key pair"): +#if HAVE_GCRYPT + arg_action = ACTION_SETUP_KEYS; break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("new-id128", NULL, /* help= */ NULL): + arg_action = ACTION_NEW_ID128; + break; } + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + if (arg_no_tail) arg_lines = ARG_LINES_ALL; @@ -1109,10 +958,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--lines=+N is unsupported when --reverse or --follow is specified."); - if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc) + if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && n_args > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments starting with '%s'", - argv[optind]); + args[0]); if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && arg_merge) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1146,6 +995,11 @@ static int parse_argv(int argc, char *argv[]) { if (!arg_follow) arg_journal_additional_open_flags = SD_JOURNAL_ASSUME_IMMUTABLE; + args = strv_copy(args); + if (!args) + return log_oom(); + + *remaining_args = args; return 1; } @@ -1158,17 +1012,13 @@ static int run(int argc, char *argv[]) { setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return vl_server(); - r = strv_copy_unless_empty(strv_skip(argv, optind), &args); - if (r < 0) - return log_oom(); - if (arg_image) { assert(!arg_root);