summaryrefslogtreecommitdiffstats
path: root/src/journal/journalctl-filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/journal/journalctl-filter.c')
-rw-r--r--src/journal/journalctl-filter.c484
1 files changed, 484 insertions, 0 deletions
diff --git a/src/journal/journalctl-filter.c b/src/journal/journalctl-filter.c
new file mode 100644
index 0000000..f9eb9f8
--- /dev/null
+++ b/src/journal/journalctl-filter.c
@@ -0,0 +1,484 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-device.h"
+
+#include "chase.h"
+#include "devnum-util.h"
+#include "fileio.h"
+#include "glob-util.h"
+#include "journal-internal.h"
+#include "journalctl.h"
+#include "journalctl-filter.h"
+#include "journalctl-util.h"
+#include "logs-show.h"
+#include "missing_sched.h"
+#include "nulstr-util.h"
+#include "path-util.h"
+#include "unit-name.h"
+
+static int add_boot(sd_journal *j) {
+ int r;
+
+ assert(j);
+
+ if (!arg_boot)
+ return 0;
+
+ assert(!sd_id128_is_null(arg_boot_id));
+
+ r = add_match_boot_id(j, arg_boot_id);
+ if (r < 0)
+ return r;
+
+ return sd_journal_add_conjunction(j);
+}
+
+static int add_dmesg(sd_journal *j) {
+ int r;
+
+ assert(j);
+
+ if (!arg_dmesg)
+ return 0;
+
+ r = sd_journal_add_match(j, "_TRANSPORT=kernel", SIZE_MAX);
+ if (r < 0)
+ return r;
+
+ 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;
+ int r;
+
+ assert(j);
+
+ if (strv_isempty(arg_system_units) && strv_isempty(arg_user_units))
+ return 0;
+
+ STRV_FOREACH(i, arg_system_units) {
+ _cleanup_free_ char *u = NULL;
+
+ r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u);
+ if (r < 0)
+ return r;
+
+ if (string_is_glob(u)) {
+ r = strv_consume(&patterns, TAKE_PTR(u));
+ if (r < 0)
+ return r;
+ } else {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ added = true;
+ }
+ }
+
+ if (!strv_isempty(patterns)) {
+ _cleanup_set_free_ Set *units = NULL;
+ char *u;
+
+ r = get_possible_units(j, SYSTEM_UNITS, patterns, &units);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(u, units) {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ added = true;
+ }
+ }
+
+ patterns = strv_free(patterns);
+
+ STRV_FOREACH(i, arg_user_units) {
+ _cleanup_free_ char *u = NULL;
+
+ r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u);
+ if (r < 0)
+ return r;
+
+ if (string_is_glob(u)) {
+ r = strv_consume(&patterns, TAKE_PTR(u));
+ if (r < 0)
+ return r;
+ } else {
+ r = add_matches_for_user_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ added = true;
+ }
+ }
+
+ if (!strv_isempty(patterns)) {
+ _cleanup_set_free_ Set *units = NULL;
+ char *u;
+
+ r = get_possible_units(j, USER_UNITS, patterns, &units);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(u, units) {
+ r = add_matches_for_user_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ added = true;
+ }
+ }
+
+ /* Complain if the user request matches but nothing whatsoever was found, since otherwise everything
+ * would be matched. */
+ if (!added)
+ return -ENODATA;
+
+ return sd_journal_add_conjunction(j);
+}
+
+static int add_syslog_identifier(sd_journal *j) {
+ int r;
+
+ assert(j);
+
+ if (strv_isempty(arg_syslog_identifier))
+ return 0;
+
+ STRV_FOREACH(i, arg_syslog_identifier) {
+ r = journal_add_match_pair(j, "SYSLOG_IDENTIFIER", *i);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_journal_add_conjunction(j);
+}
+
+static int add_exclude_identifier(sd_journal *j) {
+ _cleanup_set_free_ Set *excludes = NULL;
+ int r;
+
+ assert(j);
+
+ r = set_put_strdupv(&excludes, arg_exclude_identifier);
+ if (r < 0)
+ return r;
+
+ return set_free_and_replace(j->exclude_syslog_identifiers, excludes);
+}
+
+static int add_priorities(sd_journal *j) {
+ int r;
+
+ assert(j);
+
+ if (arg_priorities == 0)
+ return 0;
+
+ for (int i = LOG_EMERG; i <= LOG_DEBUG; i++)
+ if (arg_priorities & (1 << i)) {
+ r = journal_add_matchf(j, "PRIORITY=%d", i);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_journal_add_conjunction(j);
+}
+
+static int add_facilities(sd_journal *j) {
+ int r;
+
+ assert(j);
+
+ if (set_isempty(arg_facilities))
+ return 0;
+
+ void *p;
+ SET_FOREACH(p, arg_facilities) {
+ r = journal_add_matchf(j, "SYSLOG_FACILITY=%d", PTR_TO_INT(p));
+ if (r < 0)
+ return r;
+ }
+
+ return sd_journal_add_conjunction(j);
+}
+
+static int add_matches_for_executable(sd_journal *j, const char *path) {
+ _cleanup_free_ char *interpreter = NULL;
+ int r;
+
+ assert(j);
+ assert(path);
+
+ if (executable_is_script(path, &interpreter) > 0) {
+ _cleanup_free_ char *comm = NULL;
+
+ r = path_extract_filename(path, &comm);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract filename of '%s': %m", path);
+
+ r = journal_add_match_pair(j, "_COMM", strshorten(comm, TASK_COMM_LEN-1));
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+
+ /* Append _EXE only if the interpreter is not a link. Otherwise, it might be outdated often. */
+ path = is_symlink(interpreter) > 0 ? interpreter : NULL;
+ }
+
+ if (path) {
+ r = journal_add_match_pair(j, "_EXE", path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+ }
+
+ return 0;
+}
+
+static int add_matches_for_device(sd_journal *j, const char *devpath) {
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ int r;
+
+ assert(j);
+ assert(devpath);
+
+ r = sd_device_new_from_devname(&device, devpath);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get device '%s': %m", devpath);
+
+ for (sd_device *d = device; d; ) {
+ const char *subsys, *sysname;
+
+ r = sd_device_get_subsystem(d, &subsys);
+ if (r < 0)
+ goto get_parent;
+
+ r = sd_device_get_sysname(d, &sysname);
+ if (r < 0)
+ goto get_parent;
+
+ r = journal_add_matchf(j, "_KERNEL_DEVICE=+%s:%s", subsys, sysname);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+
+ dev_t devnum;
+ if (sd_device_get_devnum(d, &devnum) >= 0) {
+ r = journal_add_matchf(j, "_KERNEL_DEVICE=%c" DEVNUM_FORMAT_STR,
+ streq(subsys, "block") ? 'b' : 'c',
+ DEVNUM_FORMAT_VAL(devnum));
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+ }
+
+get_parent:
+ if (sd_device_get_parent(d, &d) < 0)
+ break;
+ }
+
+ return add_match_boot_id(j, SD_ID128_NULL);
+}
+
+static int add_matches_for_path(sd_journal *j, const char *path) {
+ _cleanup_free_ char *p = NULL;
+ struct stat st;
+ int r;
+
+ assert(j);
+ assert(path);
+
+ if (arg_root || arg_machine)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "An extra path in match filter is currently not supported with --root, --image, or -M/--machine.");
+
+ r = chase_and_stat(path, NULL, 0, &p, &st);
+ if (r < 0)
+ return log_error_errno(r, "Couldn't canonicalize path '%s': %m", path);
+
+ if (S_ISREG(st.st_mode) && (0111 & st.st_mode))
+ return add_matches_for_executable(j, p);
+
+ if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode))
+ return add_matches_for_device(j, p);
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File is neither a device node nor executable: %s", p);
+}
+
+static int add_matches(sd_journal *j, char **args) {
+ bool have_term = false;
+ int r;
+
+ assert(j);
+
+ if (strv_isempty(args))
+ return 0;
+
+ STRV_FOREACH(i, args)
+ if (streq(*i, "+")) {
+ if (!have_term)
+ break;
+
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add disjunction: %m");
+
+ have_term = false;
+
+ } else if (path_is_absolute(*i)) {
+ r = add_matches_for_path(j, *i);
+ if (r < 0)
+ return r;
+ have_term = true;
+
+ } else {
+ r = sd_journal_add_match(j, *i, SIZE_MAX);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match '%s': %m", *i);
+ have_term = true;
+ }
+
+ if (!have_term)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"+\" can only be used between terms.");
+
+ return 0;
+}
+
+int add_filters(sd_journal *j, char **matches) {
+ int r;
+
+ assert(j);
+
+ /* First, search boot ID, as that may set and flush matches and seek journal. */
+ r = journal_acquire_boot(j);
+ if (r < 0)
+ return r;
+
+ /* Clear unexpected matches for safety. */
+ sd_journal_flush_matches(j);
+
+ /* Then, add filters in the below. */
+ r = add_boot(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add filter for boot: %m");
+
+ r = add_dmesg(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add filter for dmesg: %m");
+
+ r = add_units(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add filter for units: %m");
+
+ r = add_syslog_identifier(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add filter for syslog identifiers: %m");
+
+ r = add_exclude_identifier(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add exclude filter for syslog identifiers: %m");
+
+ r = add_priorities(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add filter for priorities: %m");
+
+ r = add_facilities(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add filter for facilities: %m");
+
+ r = add_matches(j, matches);
+ if (r < 0)
+ return r;
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *filter = NULL;
+
+ filter = journal_make_match_string(j);
+ if (!filter)
+ return log_oom();
+
+ log_debug("Journal filter: %s", filter);
+ }
+
+ return 0;
+}