/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include "sd-journal.h" #include "build.h" #include "glob-util.h" #include "id128-print.h" #include "journalctl.h" #include "journalctl-authenticate.h" #include "journalctl-catalog.h" #include "journalctl-misc.h" #include "journalctl-show.h" #include "journalctl-varlink.h" #include "locale-util.h" #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" #include "parse-argument.h" #include "pretty-print.h" #include "static-destruct.h" #include "string-table.h" #include "syslog-util.h" #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) enum { /* Special values for arg_lines */ ARG_LINES_DEFAULT = -2, ARG_LINES_ALL = -1, }; JournalctlAction arg_action = ACTION_SHOW; OutputMode arg_output = OUTPUT_SHORT; sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; PagerFlags arg_pager_flags = 0; bool arg_utc = false; bool arg_follow = false; bool arg_full = true; bool arg_all = false; int arg_lines = ARG_LINES_DEFAULT; bool arg_lines_oldest = false; bool arg_no_tail = false; bool arg_truncate_newline = false; bool arg_quiet = false; bool arg_merge = false; bool arg_boot = false; sd_id128_t arg_boot_id = {}; int arg_boot_offset = 0; bool arg_dmesg = false; bool arg_no_hostname = false; const char *arg_cursor = NULL; const char *arg_cursor_file = NULL; const char *arg_after_cursor = NULL; bool arg_show_cursor = false; const char *arg_directory = NULL; char **arg_file = NULL; bool arg_file_stdin = false; int arg_priorities = 0; Set *arg_facilities = NULL; char *arg_verify_key = NULL; #if HAVE_GCRYPT usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; bool arg_force = false; #endif usec_t arg_since = 0; usec_t arg_until = 0; bool arg_since_set = false; bool arg_until_set = false; 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; int arg_journal_type = 0; int arg_journal_additional_open_flags = 0; int arg_namespace_flags = 0; char *arg_root = NULL; char *arg_image = NULL; const char *arg_machine = NULL; const char *arg_namespace = NULL; uint64_t arg_vacuum_size = 0; uint64_t arg_vacuum_n_files = 0; usec_t arg_vacuum_time = 0; Set *arg_output_fields = NULL; const char *arg_pattern = NULL; pcre2_code *arg_compiled_pattern = NULL; PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO; static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_verify_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_syslog_identifier, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_exclude_identifier, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_system_units, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_user_units, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_output_fields, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_compiled_pattern, pattern_freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int parse_id_descriptor(const char *x, sd_id128_t *ret_id, int *ret_offset) { sd_id128_t id = SD_ID128_NULL; int off = 0, r; assert(x); assert(ret_id); assert(ret_offset); if (streq(x, "all")) { *ret_id = SD_ID128_NULL; *ret_offset = 0; return 0; } if (strlen(x) >= SD_ID128_STRING_MAX - 1) { char *t; t = strndupa_safe(x, SD_ID128_STRING_MAX - 1); r = sd_id128_from_string(t, &id); if (r >= 0) x += SD_ID128_STRING_MAX - 1; if (!IN_SET(*x, 0, '-', '+')) return -EINVAL; if (*x != 0) { r = safe_atoi(x, &off); if (r < 0) return r; } } else { r = safe_atoi(x, &off); if (r < 0) return r; } *ret_id = id; *ret_offset = off; return 1; } static int parse_lines(const char *arg, bool graceful) { const char *l; int n, r; assert(arg || graceful); if (!arg) goto default_noarg; if (streq(arg, "all")) { arg_lines = ARG_LINES_ALL; return 1; } l = startswith(arg, "+"); r = safe_atoi(l ?: arg, &n); if (r < 0 || n < 0) { if (graceful) goto default_noarg; return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --lines='%s'.", arg); } arg_lines = n; arg_lines_oldest = l; return 1; default_noarg: arg_lines = 10; arg_lines_oldest = false; return 0; } static int help_facilities(void) { if (!arg_quiet) puts("Available facilities:"); for (int i = 0; i < LOG_NFACILITIES; i++) { _cleanup_free_ char *t = NULL; if (log_facility_unshifted_to_string_alloc(i, &t) < 0) return log_oom(); puts(t); } return 0; } static int help(void) { _cleanup_free_ char *link = NULL; int r; pager_open(arg_pager_flags); r = terminal_urlify_man("journalctl", "1", &link); if (r < 0) return log_oom(); 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" " --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" "\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()); 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_NO_HOSTNAME, ARG_OUTPUT_FIELDS, ARG_NAMESPACE, ARG_LIST_NAMESPACES, }; 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, 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 }, {} }; int c, r; assert(argc >= 0); assert(argv); 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) { case 'h': return help(); case ARG_VERSION: return version(); case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; case 'e': arg_pager_flags |= PAGER_JUMP_TO_END; if (arg_lines == ARG_LINES_DEFAULT) arg_lines = 1000; arg_boot = true; break; case 'f': arg_follow = true; break; case 'o': if (streq(optarg, "help")) { DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); return 0; } arg_output = output_mode_from_string(optarg); if (arg_output < 0) return log_error_errno(arg_output, "Unknown output format '%s'.", optarg); if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT)) arg_quiet = true; if (OUTPUT_MODE_IS_JSON(arg_output)) arg_json_format_flags = output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO; else arg_json_format_flags = SD_JSON_FORMAT_OFF; break; case 'l': arg_full = true; break; case ARG_NO_FULL: arg_full = false; break; case 'a': arg_all = true; break; case 'n': r = parse_lines(optarg ?: argv[optind], !optarg); if (r < 0) return r; if (r > 0 && !optarg) optind++; break; case ARG_NO_TAIL: arg_no_tail = true; break; case ARG_TRUNCATE_NEWLINE: arg_truncate_newline = true; break; case ARG_NEW_ID128: arg_action = ACTION_NEW_ID128; break; case 'q': arg_quiet = true; break; case 'm': arg_merge = true; break; case ARG_THIS_BOOT: arg_boot = true; arg_boot_id = SD_ID128_NULL; arg_boot_offset = 0; break; case 'b': 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 (r < 0) return log_error_errno(r, "Failed to parse boot descriptor '%s'", optarg); 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++; } } break; case ARG_LIST_BOOTS: arg_action = ACTION_LIST_BOOTS; break; case ARG_LIST_INVOCATIONS: arg_action = ACTION_LIST_INVOCATIONS; break; case 'k': arg_boot = arg_dmesg = true; break; case ARG_SYSTEM: arg_journal_type |= SD_JOURNAL_SYSTEM; break; case ARG_USER: arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; case 'M': arg_machine = optarg; break; case ARG_NAMESPACE: if (streq(optarg, "*")) { arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; arg_namespace = NULL; } else if (startswith(optarg, "+")) { arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; arg_namespace = optarg + 1; } else if (isempty(optarg)) { arg_namespace_flags = 0; arg_namespace = NULL; } else { arg_namespace_flags = 0; arg_namespace = optarg; } break; case ARG_LIST_NAMESPACES: arg_action = ACTION_LIST_NAMESPACES; break; case 'D': arg_directory = optarg; break; case 'i': if (streq(optarg, "-")) /* 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); 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); if (r < 0) return r; break; case ARG_IMAGE: r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; case ARG_IMAGE_POLICY: r = parse_image_policy_argument(optarg, &arg_image_policy); if (r < 0) return r; break; case 'c': arg_cursor = optarg; break; case ARG_CURSOR_FILE: arg_cursor_file = optarg; break; case ARG_AFTER_CURSOR: arg_after_cursor = optarg; break; case ARG_SHOW_CURSOR: arg_show_cursor = true; break; case ARG_HEADER: arg_action = ACTION_PRINT_HEADER; break; case ARG_VERIFY: arg_action = ACTION_VERIFY; break; case ARG_DISK_USAGE: arg_action = ACTION_DISK_USAGE; break; case ARG_VACUUM_SIZE: r = parse_size(optarg, 1024, &arg_vacuum_size); if (r < 0) return log_error_errno(r, "Failed to parse vacuum size: %s", optarg); 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); if (r < 0) return log_error_errno(r, "Failed to parse vacuum files: %s", optarg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; case ARG_VACUUM_TIME: r = parse_sec(optarg, &arg_vacuum_time); if (r < 0) return log_error_errno(r, "Failed to parse vacuum time: %s", optarg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; #if HAVE_GCRYPT case ARG_FORCE: arg_force = true; break; case ARG_SETUP_KEYS: arg_action = ACTION_SETUP_KEYS; break; case ARG_VERIFY_KEY: r = free_and_strdup(&arg_verify_key, optarg); if (r < 0) return r; /* Use memset not explicit_bzero() or similar so this doesn't look confusing * in ps or htop output. */ memset(optarg, 'x', strlen(optarg)); arg_action = ACTION_VERIFY; arg_merge = false; break; case ARG_INTERVAL: r = parse_sec(optarg, &arg_interval); if (r < 0 || arg_interval <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse sealing key change interval: %s", optarg); break; #else case ARG_SETUP_KEYS: case ARG_VERIFY_KEY: case ARG_INTERVAL: case ARG_FORCE: return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without forward-secure sealing support."); #endif case 'p': { const char *dots; dots = strstr(optarg, ".."); if (dots) { _cleanup_free_ char *a = NULL; int from, to, i; /* a range */ a = strndup(optarg, dots - optarg); if (!a) return log_oom(); from = log_level_from_string(a); to = log_level_from_string(dots + 2); if (from < 0 || to < 0) return log_error_errno(from < 0 ? from : to, "Failed to parse log level range %s", optarg); arg_priorities = 0; if (from < to) { for (i = from; i <= to; i++) arg_priorities |= 1 << i; } else { for (i = to; i <= from; i++) arg_priorities |= 1 << i; } } else { int p, i; p = log_level_from_string(optarg); if (p < 0) return log_error_errno(p, "Unknown log level %s", optarg); arg_priorities = 0; for (i = 0; i <= p; i++) arg_priorities |= 1 << i; } break; } case ARG_FACILITY: { const char *p; for (p = optarg;;) { _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); if (r == 0) break; if (streq(fac, "help")) { help_facilities(); return 0; } num = log_facility_unshifted_from_string(fac); if (num < 0) return log_error_errno(num, "Bad --facility= argument \"%s\".", fac); if (set_ensure_put(&arg_facilities, NULL, INT_TO_PTR(num)) < 0) return log_oom(); } break; } case 'g': arg_pattern = optarg; break; case ARG_CASE_SENSITIVE: if (optarg) { r = parse_boolean(optarg); if (r < 0) return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", optarg); arg_case = r ? PATTERN_COMPILE_CASE_SENSITIVE : PATTERN_COMPILE_CASE_INSENSITIVE; } else arg_case = PATTERN_COMPILE_CASE_SENSITIVE; break; case 'S': r = parse_timestamp(optarg, &arg_since); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse timestamp: %s", optarg); arg_since_set = true; break; case 'U': r = parse_timestamp(optarg, &arg_until); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse timestamp: %s", optarg); arg_until_set = true; break; case 't': r = strv_extend(&arg_syslog_identifier, optarg); if (r < 0) return log_oom(); break; case 'T': r = strv_extend(&arg_exclude_identifier, optarg); if (r < 0) return log_oom(); break; case 'u': r = strv_extend(&arg_system_units, optarg); if (r < 0) return log_oom(); break; case ARG_USER_UNIT: r = strv_extend(&arg_user_units, optarg); if (r < 0) 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; break; case 'N': arg_action = ACTION_LIST_FIELD_NAMES; break; case ARG_NO_HOSTNAME: arg_no_hostname = true; break; case 'x': arg_catalog = true; break; case ARG_LIST_CATALOG: arg_action = ACTION_LIST_CATALOG; break; case ARG_DUMP_CATALOG: arg_action = ACTION_DUMP_CATALOG; break; case ARG_UPDATE_CATALOG: arg_action = ACTION_UPDATE_CATALOG; break; case 'r': arg_reverse = true; break; case ARG_UTC: arg_utc = true; break; case ARG_FLUSH: arg_action = ACTION_FLUSH; break; case ARG_SMART_RELINQUISH_VAR: { int root_mnt_id, log_mnt_id; /* Try to be smart about relinquishing access to /var/log/journal/ during shutdown: * if it's on the same mount as the root file system there's no point in * relinquishing access and we can leave journald write to it until the very last * moment. */ r = path_get_mnt_id("/", &root_mnt_id); if (r < 0) log_debug_errno(r, "Failed to get root mount ID, ignoring: %m"); else { r = path_get_mnt_id("/var/log/journal/", &log_mnt_id); if (r < 0) log_debug_errno(r, "Failed to get journal directory mount ID, ignoring: %m"); else if (root_mnt_id == log_mnt_id) { log_debug("/var/log/journal/ is on root file system, not relinquishing access to /var."); return 0; } else log_debug("/var/log/journal/ is not on the root file system, relinquishing access to it."); } _fallthrough_; } case ARG_RELINQUISH_VAR: arg_action = ACTION_RELINQUISH_VAR; break; case ARG_ROTATE: arg_action = arg_action == ACTION_VACUUM ? ACTION_ROTATE_AND_VACUUM : ACTION_ROTATE; break; case ARG_SYNC: arg_action = ACTION_SYNC; break; case ARG_OUTPUT_FIELDS: { _cleanup_strv_free_ char **v = NULL; v = strv_split(optarg, ","); if (!v) return log_oom(); r = set_put_strdupv(&arg_output_fields, v); if (r < 0) return log_oom(); break; } case '?': return -EINVAL; default: assert_not_reached(); } if (arg_no_tail) arg_lines = ARG_LINES_ALL; if (arg_follow && !arg_since_set && arg_lines == ARG_LINES_DEFAULT) arg_lines = 10; if (arg_follow && !arg_merge && !arg_boot) { arg_boot = true; arg_boot_id = SD_ID128_NULL; arg_boot_offset = 0; } if (!!arg_directory + !!arg_file + arg_file_stdin + !!arg_machine + !!arg_root + !!arg_image > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify at most one of -D/--directory=, --file=, -M/--machine=, --root=, --image=."); if (arg_since_set && arg_until_set && arg_since > arg_until) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--since= must be before --until=."); if (!!arg_cursor + !!arg_after_cursor + !!arg_cursor_file + !!arg_since_set > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify only one of --since=, --cursor=, --cursor-file=, and --after-cursor=."); if (arg_follow && arg_reverse) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --reverse or --follow, not both."); if (arg_action == ACTION_SHOW && arg_lines >= 0 && arg_lines_oldest && (arg_reverse || arg_follow)) 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) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments starting with '%s'", argv[optind]); if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && arg_merge) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Using --boot or --list-boots with --merge is not supported."); if (!strv_isempty(arg_system_units) && arg_journal_type == SD_JOURNAL_CURRENT_USER) { /* Specifying --user and --unit= at the same time makes no sense (as the former excludes the user * journal, but the latter excludes the system journal, thus resulting in empty output). Let's be nice * to users, and automatically turn --unit= into --user-unit= if combined with --user. */ r = strv_extend_strv(&arg_user_units, arg_system_units, true); if (r < 0) return r; arg_system_units = strv_free(arg_system_units); } if (arg_pattern) { r = pattern_compile_and_log(arg_pattern, arg_case, &arg_compiled_pattern); if (r < 0) return r; /* When --grep is used along with --lines without '+', i.e. when we start from the end of the * journal, we don't know how many lines we can print. So we search backwards and count until * enough lines have been printed or we hit the head. * An exception is that --follow might set arg_lines, so let's not imply --reverse * if that is specified. */ if (arg_lines_needs_seek_end() && !arg_follow) arg_reverse = true; } if (!arg_follow) arg_journal_additional_open_flags = SD_JOURNAL_ASSUME_IMMUTABLE; return 1; } static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; int r; setlocale(LC_ALL, ""); log_setup(); r = parse_argv(argc, argv); if (r <= 0) return r; char **args = strv_skip(argv, optind); if (arg_image) { assert(!arg_root); r = mount_image_privately_interactively( arg_image, arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_VALIDATE_OS | DISSECT_IMAGE_RELAX_VAR_CHECK | (arg_action == ACTION_UPDATE_CATALOG ? DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS : DISSECT_IMAGE_READ_ONLY) | DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); if (r < 0) return r; arg_root = strdup(mounted_dir); if (!arg_root) return log_oom(); } switch (arg_action) { case ACTION_SHOW: return action_show(args); case ACTION_NEW_ID128: return id128_print_new(ID128_PRINT_PRETTY); case ACTION_SETUP_KEYS: return action_setup_keys(); case ACTION_LIST_CATALOG: case ACTION_DUMP_CATALOG: return action_list_catalog(args); case ACTION_UPDATE_CATALOG: return action_update_catalog(); case ACTION_PRINT_HEADER: return action_print_header(); case ACTION_VERIFY: return action_verify(); case ACTION_DISK_USAGE: return action_disk_usage(); case ACTION_LIST_BOOTS: return action_list_boots(); case ACTION_LIST_FIELDS: return action_list_fields(); 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(); case ACTION_FLUSH: return action_flush_to_var(); case ACTION_RELINQUISH_VAR: return action_relinquish_var(); case ACTION_SYNC: return action_sync(); case ACTION_ROTATE: return action_rotate(); case ACTION_VACUUM: return action_vacuum(); case ACTION_ROTATE_AND_VACUUM: return action_rotate_and_vacuum(); default: assert_not_reached(); } } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_SIGNAL(run);