journalctl: reject field listing with filters

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.
This commit is contained in:
dongshengyuan
2026-07-16 01:52:39 +09:00
committed by Yu Watanabe
parent 7cb8728d59
commit cb7b533b92
5 changed files with 35 additions and 0 deletions
+19
View File
@@ -23,6 +23,25 @@
#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;
+1
View 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);
+10
View 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.");
+1
View 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;
+4
View File
@@ -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)