#include <sys/stat.h>
#include <unistd.h>
+#if HAVE_PCRE2
+# define PCRE2_CODE_UNIT_WIDTH 8
+# include <pcre2.h>
+#endif
+
#include "sd-bus.h"
#include "sd-journal.h"
#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
+#define PROCESS_INOTIFY_INTERVAL 1024 /* Every 1,024 messages processed */
+
+#if HAVE_PCRE2
+DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_match_data*, pcre2_match_data_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_code*, pcre2_code_free);
+
+static int pattern_compile(const char *pattern, unsigned flags, pcre2_code **out) {
+ int errorcode, r;
+ PCRE2_SIZE erroroffset;
+ pcre2_code *p;
+
+ p = pcre2_compile((PCRE2_SPTR8) pattern,
+ PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL);
+ if (!p) {
+ unsigned char buf[LINE_MAX];
+
+ r = pcre2_get_error_message(errorcode, buf, sizeof buf);
+
+ log_error("Bad pattern \"%s\": %s",
+ pattern,
+ r < 0 ? "unknown error" : (char*) buf);
+ return -EINVAL;
+ }
+
+ *out = p;
+ return 0;
+}
+
+#endif
+
enum {
/* Special values for arg_lines */
ARG_LINES_DEFAULT = -2,
static usec_t arg_vacuum_time = 0;
static char **arg_output_fields = NULL;
+#if HAVE_PCRE2
+static const char *arg_pattern = NULL;
+static pcre2_code *arg_compiled_pattern = NULL;
+static int arg_case_sensitive = -1; /* -1 means be smart */
+#endif
+
static enum {
ACTION_SHOW,
ACTION_NEW_ID128,
static void help(void) {
- pager_open(arg_no_pager, arg_pager_end);
+ (void) pager_open(arg_no_pager, arg_pager_end);
printf("%s [OPTIONS...] [MATCHES...]\n\n"
"Query the journal.\n\n"
"Options:\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"
- " -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"
- " --show-cursor Print the cursor after all the entries\n"
- " -b --boot[=ID] Show current boot or the specified boot\n"
- " --list-boots Show terse information about recorded boots\n"
- " -k --dmesg Show kernel message log from the current boot\n"
- " -u --unit=UNIT Show logs from the specified unit\n"
- " --user-unit=UNIT Show logs from the specified user unit\n"
- " -t --identifier=STRING Show entries with the specified syslog identifier\n"
- " -p --priority=RANGE Show entries with the specified priority\n"
- " -e --pager-end Immediately jump to the end in the pager\n"
- " -f --follow Follow the journal\n"
- " -n --lines[=INTEGER] Number of journal entries to show\n"
- " --no-tail Show all lines, even in follow mode\n"
- " -r --reverse Show the newest entries first\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, cat)\n"
- " --utc Express time in Coordinated Universal Time (UTC)\n"
- " -x --catalog Add message explanations where available\n"
- " --no-full Ellipsize fields\n"
- " -a --all Show all fields, including long and unprintable\n"
- " -q --quiet Do not show info messages and privilege warning\n"
- " --no-pager Do not pipe output into a pager\n"
- " --no-hostname Suppress output of hostname field\n"
- " -m --merge Show entries from all available journals\n"
- " -D --directory=PATH Show journal files from directory\n"
- " --file=PATH Show journal file\n"
- " --root=ROOT Operate on files below a root directory\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"
+ " -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"
+ " --show-cursor Print the cursor after all the entries\n"
+ " -b --boot[=ID] Show current boot or the specified boot\n"
+ " --list-boots Show terse information about recorded boots\n"
+ " -k --dmesg Show kernel message log from the current boot\n"
+ " -u --unit=UNIT Show logs from the specified unit\n"
+ " --user-unit=UNIT Show logs from the specified user unit\n"
+ " -t --identifier=STRING Show entries with the specified syslog identifier\n"
+ " -p --priority=RANGE Show entries with the specified priority\n"
+ " -g --grep=PATTERN Show entries with MESSAGE matching PATTERN\n"
+ " --case-sensitive[=BOOL] Force case sensitive or insenstive matching\n"
+ " -e --pager-end Immediately jump to the end in the pager\n"
+ " -f --follow Follow the journal\n"
+ " -n --lines[=INTEGER] Number of journal entries to show\n"
+ " --no-tail Show all lines, even in follow mode\n"
+ " -r --reverse Show the newest entries first\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, cat)\n"
+ " --output-fields=LIST Select fields to print in verbose/export/json modes\n"
+ " --utc Express time in Coordinated Universal Time (UTC)\n"
+ " -x --catalog Add message explanations where available\n"
+ " --no-full Ellipsize fields\n"
+ " -a --all Show all fields, including long and unprintable\n"
+ " -q --quiet Do not show info messages and privilege warning\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --no-hostname Suppress output of hostname field\n"
+ " -m --merge Show entries from all available journals\n"
+ " -D --directory=PATH Show journal files from directory\n"
+ " --file=PATH Show journal file\n"
+ " --root=ROOT Operate on files below a root directory\n"
#if HAVE_GCRYPT
- " --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"
+ " --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"
#endif
"\nCommands:\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"
- " --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"
- " --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"
- " --new-id128 Generate a new 128-bit ID\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"
+ " --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"
+ " --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"
+ " --new-id128 Generate a new 128-bit ID\n"
#if HAVE_GCRYPT
- " --setup-keys Generate a new FSS key pair\n"
+ " --setup-keys Generate a new FSS key pair\n"
#endif
, program_invocation_short_name);
}
ARG_DUMP_CATALOG,
ARG_UPDATE_CATALOG,
ARG_FORCE,
+ ARG_CASE_SENSITIVE,
ARG_UTC,
ARG_SYNC,
ARG_FLUSH,
{ "header", no_argument, NULL, ARG_HEADER },
{ "identifier", required_argument, NULL, 't' },
{ "priority", required_argument, NULL, 'p' },
+ { "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 },
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0)
switch (c) {
break;
}
+#if HAVE_PCRE2
+ 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_sensitive = r;
+ } else
+ arg_case_sensitive = true;
+
+ break;
+#else
+ case 'g':
+ case ARG_CASE_SENSITIVE:
+ return log_error("Compiled without pattern matching support");
+#endif
+
case 'S':
r = parse_timestamp(optarg, &arg_since);
if (r < 0) {
if (!v)
return log_oom();
- if (!arg_output_fields) {
- arg_output_fields = v;
- v = NULL;
- } else {
+ if (!arg_output_fields)
+ arg_output_fields = TAKE_PTR(v);
+ else {
r = strv_extend_strv(&arg_output_fields, v, true);
if (r < 0)
return log_oom();
return -EINVAL;
}
- if (!strv_isempty(arg_system_units) && (arg_journal_type == SD_JOURNAL_CURRENT_USER)) {
-
+ 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. */
arg_system_units = strv_free(arg_system_units);
}
+
+#if HAVE_PCRE2
+ if (arg_pattern) {
+ unsigned flags;
+
+ if (arg_case_sensitive >= 0)
+ flags = !arg_case_sensitive * PCRE2_CASELESS;
+ else {
+ _cleanup_(pcre2_match_data_freep) pcre2_match_data *md = NULL;
+ bool has_case;
+ _cleanup_(pcre2_code_freep) pcre2_code *cs = NULL;
+
+ md = pcre2_match_data_create(1, NULL);
+ if (!md)
+ return log_oom();
+
+ r = pattern_compile("[[:upper:]]", 0, &cs);
+ if (r < 0)
+ return r;
+
+ r = pcre2_match(cs, (PCRE2_SPTR8) arg_pattern, PCRE2_ZERO_TERMINATED, 0, 0, md, NULL);
+ has_case = r >= 0;
+
+ flags = !has_case * PCRE2_CASELESS;
+ }
+
+ log_debug("Doing case %s matching based on %s",
+ flags & PCRE2_CASELESS ? "insensitive" : "sensitive",
+ arg_case_sensitive >= 0 ? "request" : "pattern casing");
+
+ r = pattern_compile(arg_pattern, flags, &arg_compiled_pattern);
+ if (r < 0)
+ return r;
+ }
+#endif
+
return 1;
}
_cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL;
struct stat st;
- r = chase_symlinks(*i, NULL, 0, &p);
+ r = chase_symlinks(*i, NULL, CHASE_TRAIL_SLASH, &p);
if (r < 0)
return log_error_errno(r, "Couldn't canonicalize path: %m");
if (r < 0)
return r;
- *ret = next_boot;
- next_boot = NULL;
+ *ret = TAKE_PTR(next_boot);
return 0;
}
}
}
LIST_INSERT_AFTER(boot_list, head, tail, current);
- tail = current;
- current = NULL;
+ tail = TAKE_PTR(current);
count++;
}
}
if (count == 0)
return count;
- pager_open(arg_no_pager, arg_pager_end);
+ (void) pager_open(arg_no_pager, arg_pager_end);
/* numbers are one less, but we need an extra char for the sign */
w = DECIMAL_STR_WIDTH(count - 1) + 1;
if (!arg_dmesg)
return 0;
- r = sd_journal_add_match(j, "_TRANSPORT=kernel", strlen("_TRANSPORT=kernel"));
+ r = sd_journal_add_match(j, "_TRANSPORT=kernel",
+ STRLEN("_TRANSPORT=kernel"));
if (r < 0)
return log_error_errno(r, "Failed to add match: %m");
}
}
- *units = found;
- found = NULL;
+ *units = TAKE_PTR(found);
+
return 0;
}
STRV_FOREACH(i, arg_system_units) {
_cleanup_free_ char *u = NULL;
- r = unit_name_mangle(*i, UNIT_NAME_GLOB, &u);
+ r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u);
if (r < 0)
return r;
STRV_FOREACH(i, arg_user_units) {
_cleanup_free_ char *u = NULL;
- r = unit_name_mangle(*i, UNIT_NAME_GLOB, &u);
+ r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u);
if (r < 0)
return r;
} else {
bool oneline = arg_action == ACTION_LIST_CATALOG;
- pager_open(arg_no_pager, arg_pager_end);
+ (void) pager_open(arg_no_pager, arg_pager_end);
if (optind < argc)
r = catalog_list_items(stdout, database, oneline, argv + optind);
goto finish;
}
- r = journal_access_check_and_warn(j, arg_quiet);
+ r = journal_access_check_and_warn(j, arg_quiet,
+ !(arg_journal_type == SD_JOURNAL_CURRENT_USER || arg_user_units));
if (r < 0)
goto finish;
if (r < 0)
goto finish;
- if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) {
+ if (DEBUG_LOGGING) {
_cleanup_free_ char *filter;
filter = journal_make_match_string(j);
need_seek = true;
if (!arg_follow)
- pager_open(arg_no_pager, arg_pager_end);
+ (void) pager_open(arg_no_pager, arg_pager_end);
if (!arg_quiet && (arg_lines != 0 || arg_follow)) {
usec_t start, end;
for (;;) {
while (arg_lines < 0 || n_shown < arg_lines || (arg_follow && !first_line)) {
int flags;
+ size_t highlight[2] = {};
if (need_seek) {
if (!arg_reverse)
}
}
+#if HAVE_PCRE2
+ if (arg_compiled_pattern) {
+ _cleanup_(pcre2_match_data_freep) pcre2_match_data *md = NULL;
+ const void *message;
+ size_t len;
+ PCRE2_SIZE *ovec;
+
+ md = pcre2_match_data_create(1, NULL);
+ if (!md)
+ return log_oom();
+
+ r = sd_journal_get_data(j, "MESSAGE", &message, &len);
+ if (r < 0) {
+ if (r == -ENOENT) {
+ need_seek = true;
+ continue;
+ }
+
+ log_error_errno(r, "Failed to get MESSAGE field: %m");
+ goto finish;
+ }
+
+ assert_se(message = startswith(message, "MESSAGE="));
+
+ r = pcre2_match(arg_compiled_pattern,
+ message,
+ len - strlen("MESSAGE="),
+ 0, /* start at offset 0 in the subject */
+ 0, /* default options */
+ md,
+ NULL);
+ if (r == PCRE2_ERROR_NOMATCH) {
+ need_seek = true;
+ continue;
+ }
+ if (r < 0) {
+ unsigned char buf[LINE_MAX];
+ int r2;
+
+ r2 = pcre2_get_error_message(r, buf, sizeof buf);
+ log_error("Pattern matching failed: %s",
+ r2 < 0 ? "unknown error" : (char*) buf);
+ r = -EINVAL;
+ goto finish;
+ }
+
+ ovec = pcre2_get_ovector_pointer(md);
+ highlight[0] = ovec[0];
+ highlight[1] = ovec[1];
+ }
+#endif
+
flags =
arg_all * OUTPUT_SHOW_ALL |
arg_full * OUTPUT_FULL_WIDTH |
arg_utc * OUTPUT_UTC |
arg_no_hostname * OUTPUT_NO_HOSTNAME;
- r = output_journal(stdout, j, arg_output, 0, flags, arg_output_fields, &ellipsized);
+ r = output_journal(stdout, j, arg_output, 0, flags,
+ arg_output_fields, highlight, &ellipsized);
need_seek = true;
if (r == -EADDRNOTAVAIL)
break;
goto finish;
n_shown++;
+
+ /* If journalctl take a long time to process messages, and during that time journal file
+ * rotation occurs, a journalctl client will keep those rotated files open until it calls
+ * sd_journal_process(), which typically happens as a result of calling sd_journal_wait() below
+ * in the "following" case. By periodically calling sd_journal_process() during the processing
+ * loop we shrink the window of time a client instance has open file descriptors for rotated
+ * (deleted) journal files. */
+ if ((n_shown % PROCESS_INOTIFY_INTERVAL) == 0) {
+ r = sd_journal_process(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process inotify events: %m");
+ goto finish;
+ }
+ }
}
if (!arg_follow) {
free(arg_root);
free(arg_verify_key);
+#if HAVE_PCRE2
+ if (arg_compiled_pattern)
+ pcre2_code_free(arg_compiled_pattern);
+#endif
+
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}