]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journalctl: reject field listing with filters
authordongshengyuan <545258830@qq.com>
Mon, 13 Jul 2026 07:33:09 +0000 (15:33 +0800)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 15 Jul 2026 16:52:39 +0000 (01:52 +0900)
Reproducer:
  journalctl -F _SYSTEMD_UNIT -u test.service --no-pager
  journalctl -u test.service --no-pager -n 1

Before, the field listing ignored the unit filter and listed unrelated
units, while the normal journal query applied the filter.

sd_journal_query_unique() cannot represent journalctl filters such as
unit, boot, time, cursor, or grep filters. Walking the journal line by
line would make field listing linear in the number of entries and
duplicate unique-value handling.

Keep the scope-option predicate next to add_filters(), and reject field
listings combined with options that actually limit the journal.
Display-only options such as --pager-end are left alone.

src/journal/journalctl-filter.c
src/journal/journalctl-filter.h
src/journal/journalctl.c
src/journal/journalctl.h
test/units/TEST-04-JOURNAL.journal.sh

index ce2a6cb18c6eda6aaed92e3c74fb33f564185cf0..01c82ca4059d9fbfc8a037b484a6effcd0df3440 100644 (file)
 #include "strv.h"
 #include "unit-name.h"
 
+bool field_list_has_scope_options(void) {
+        return
+                arg_boot_filter ||
+                arg_invocation ||
+                arg_dmesg ||
+                arg_cursor ||
+                arg_after_cursor ||
+                arg_cursor_file ||
+                !strv_isempty(arg_system_units) ||
+                !strv_isempty(arg_user_units) ||
+                !strv_isempty(arg_syslog_identifier) ||
+                !strv_isempty(arg_exclude_identifier) ||
+                arg_priorities != 0 ||
+                !set_isempty(arg_facilities) ||
+                arg_since_set ||
+                arg_until_set ||
+                arg_pattern;
+}
+
 static int add_invocation(sd_journal *j) {
         int r;
 
index bf2f7b839cdf1758a00d8c9bb411f5b4eee906fa..f0ac453d3ce9d65238abc2bd1372475fecd4b910 100644 (file)
@@ -11,4 +11,5 @@ int journal_add_unit_matches(
                 uid_t uid,
                 char * const *user_units);
 
+bool field_list_has_scope_options(void);
 int add_filters(sd_journal *j, char **matches);
index b407a77d97058051df8a0fce7313371e4606dc2d..4bc648ceffe44a75fb07cc5348c6939de763493c 100644 (file)
@@ -17,6 +17,7 @@
 #include "journalctl.h"
 #include "journalctl-authenticate.h"
 #include "journalctl-catalog.h"
+#include "journalctl-filter.h"
 #include "journalctl-metrics.h"
 #include "journalctl-misc.h"
 #include "journalctl-show.h"
@@ -70,6 +71,7 @@ bool arg_merge = false;
 int arg_boot = -1; /* tristate */
 sd_id128_t arg_boot_id = {};
 int arg_boot_offset = 0;
+bool arg_boot_filter = false;
 bool arg_dmesg = false;
 bool arg_no_hostname = false;
 char *arg_cursor = NULL;
@@ -532,6 +534,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
 
                 OPTION_LONG("this-boot", NULL, /* help= */ NULL):
                         arg_boot = true;
+                        arg_boot_filter = true;
                         arg_boot_id = SD_ID128_NULL;
                         arg_boot_offset = 0;
                         break;
@@ -539,6 +542,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
                 OPTION_FULL(OPTION_OPTIONAL_ARG, 'b', "boot", "ID",
                             "Show current boot or the specified boot"):
                         arg_boot = true;
+                        arg_boot_filter = true;
                         arg_boot_id = SD_ID128_NULL;
                         arg_boot_offset = 0;
 
@@ -548,6 +552,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
                                         return log_error_errno(r, "Failed to parse boot descriptor '%s'", opts.arg);
 
                                 arg_boot = r;
+                                arg_boot_filter = r > 0;
 
                         } else {
                                 /* Hmm, no argument? Maybe the next word on the command line is supposed to
@@ -558,6 +563,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
                                         r = parse_id_descriptor(peek, &arg_boot_id, &arg_boot_offset);
                                         if (r >= 0) {
                                                 arg_boot = r;
+                                                arg_boot_filter = r > 0;
                                                 (void) option_parser_consume_next_arg(&opts);
                                         }
                                 }
@@ -976,6 +982,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
                                        "Extraneous arguments starting with '%s'",
                                        args[0]);
 
+        if (IN_SET(arg_action, ACTION_LIST_FIELDS, ACTION_LIST_FIELD_NAMES) && field_list_has_scope_options())
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "-F/--field= and -N/--fields cannot be combined with options that limit the journal.");
+
         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.");
index 59353253d499b89856ee0bb1e66973f94a1fa15e..23535ab0ce859f3db5ee50e10f67efdb0cb4e400 100644 (file)
@@ -45,6 +45,7 @@ extern bool arg_merge;
 extern int arg_boot;
 extern sd_id128_t arg_boot_id;
 extern int arg_boot_offset;
+extern bool arg_boot_filter;
 extern bool arg_dmesg;
 extern bool arg_no_hostname;
 extern char *arg_cursor;
index 03613b5805df917a75cd6afff31631c183339432..403164016fd8f32a91d9486bd6a7ced2a7da630c 100755 (executable)
@@ -158,6 +158,10 @@ journalctl --root / | grep . >/dev/null
 journalctl --cursor "t=0;t=-1;t=0;t=0x0" | grep . >/dev/null
 journalctl --header | grep system.journal
 journalctl --field _EXE | grep . >/dev/null
+journalctl --field _EXE --pager-end | grep . >/dev/null
+(! journalctl -q --field _SYSTEMD_UNIT --unit "test-no-such-unit-$RANDOM.service" --no-pager)
+(! journalctl -q --field _SYSTEMD_UNIT --since "9999-01-01" --no-pager)
+(! journalctl -q --fields --unit "test-no-such-unit-$RANDOM.service" --no-pager)
 journalctl --no-hostname --utc --catalog | grep . >/dev/null
 # Exercise executable_is_script() and the related code, e.g. `journalctl -b /path/to/a/script.sh` should turn
 # into ((_EXE=/usr/bin/bash AND _COMM=script.sh) AND _BOOT_ID=c002e3683ba14fa8b6c1e12878386514)