#include <locale.h>
#include <fcntl.h>
+#include <fnmatch.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include "build.h"
#include "pager.h"
#include "strv.h"
+#include "set.h"
#include "journal-internal.h"
#include "journal-def.h"
#include "journal-verify.h"
static bool arg_quiet = false;
static bool arg_merge = false;
static bool arg_boot = false;
-static char *arg_boot_descriptor = NULL;
+static sd_id128_t arg_boot_id = {};
+static int arg_boot_offset = 0;
static bool arg_dmesg = false;
static const char *arg_cursor = NULL;
static const char *arg_after_cursor = NULL;
static bool arg_reverse = false;
static int arg_journal_type = 0;
static const char *arg_root = NULL;
+static const char *arg_machine = NULL;
static enum {
ACTION_SHOW,
uint64_t last;
} boot_id_t;
+static void pager_open_if_enabled(void) {
+
+ if (arg_no_pager)
+ return;
+
+ pager_open(arg_pager_end);
+}
+
+static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset) {
+ sd_id128_t id = SD_ID128_NULL;
+ int off = 0, r;
+
+ if (strlen(x) >= 32) {
+ char *t;
+
+ t = strndupa(x, 32);
+ r = sd_id128_from_string(t, &id);
+ if (r >= 0)
+ x += 32;
+
+ if (*x != '-' && *x != '+' && *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;
+ }
+
+ if (boot_id)
+ *boot_id = id;
+
+ if (offset)
+ *offset = off;
+
+ return 0;
+}
+
static int help(void) {
+ pager_open_if_enabled();
+
printf("%s [OPTIONS...] [MATCHES...]\n\n"
"Query the journal.\n\n"
"Flags:\n"
" --system Show only the system journal\n"
- " --user Show only the user journal for current user\n"
- " --since=DATE Start showing entries newer or of the specified date\n"
- " --until=DATE Stop showing entries older or of the specified date\n"
- " -c --cursor=CURSOR Start showing entries from specified cursor\n"
- " --after-cursor=CURSOR Start showing entries from specified cursor\n"
+ " --user Show only the user journal for the current user\n"
+ " -M --machine=CONTAINER Operate on local container\n"
+ " --since=DATE Start showing entries on or newer than the specified date\n"
+ " --until=DATE Stop showing entries on or older than the specified date\n"
+ " -c --cursor=CURSOR Start showing entries from the specified cursor\n"
+ " --after-cursor=CURSOR Start showing entries from after the specified cursor\n"
" --show-cursor Print the cursor after all the entries\n"
- " -b --boot[=ID] Show data only from ID or current boot if unspecified\n"
+ " -b --boot[=ID] Show data only from ID or, if unspecified, the current boot\n"
" --list-boots Show terse information about recorded boots\n"
- " -k --dmesg Show kernel message log from current boot\n"
+ " -k --dmesg Show kernel message log from the current boot\n"
" -u --unit=UNIT Show data only from the specified unit\n"
" --user-unit=UNIT Show data only from the specified user session unit\n"
" -p --priority=RANGE Show only messages within the specified priority range\n"
" -e --pager-end Immediately jump to end of the journal in the pager\n"
- " -f --follow Follow journal\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-iso,\n"
- " short-precise, short-monotonic, verbose,\n"
- " export, json, json-pretty, json-sse, cat)\n"
+ " short-precise, short-monotonic, verbose,\n"
+ " export, json, json-pretty, json-sse, cat)\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 Don't show privilege warning\n"
+ " -q --quiet Do not show privilege warning\n"
" --no-pager Do not pipe output into a pager\n"
" -m --merge Show entries from all available journals\n"
" -D --directory=PATH Show journal files from directory\n"
#ifdef HAVE_GCRYPT
" --interval=TIME Time interval for changing the FSS sealing key\n"
" --verify-key=KEY Specify FSS verification key\n"
- " --force Force overriding new FSS key pair with --setup-keys\n"
+ " --force Force overriding of the FSS key pair with --setup-keys\n"
#endif
"\nCommands:\n"
- " -h --help Show this help\n"
+ " -h --help Show this help text\n"
" --version Show package version\n"
- " --new-id128 Generate a new 128 Bit ID\n"
+ " --new-id128 Generate a new 128-bit ID\n"
" --header Show journal header information\n"
- " --disk-usage Show total disk usage\n"
- " -F --field=FIELD List all values a certain field takes\n"
+ " --disk-usage Show total disk usage of all journal files\n"
+ " -F --field=FIELD List all values that a specified field takes\n"
" --list-catalog Show message IDs of all entries in the message catalog\n"
" --dump-catalog Show entries in the message catalog\n"
" --update-catalog Update the message catalog database\n"
#ifdef HAVE_GCRYPT
- " --setup-keys Generate new FSS key pair\n"
+ " --setup-keys Generate a new FSS key pair\n"
" --verify Verify journal file consistency\n"
#endif
, program_invocation_short_name);
{ "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' },
{}
};
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:u:F:xr", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:u:F:xrM:", options, NULL)) >= 0) {
switch (c) {
case 'b':
arg_boot = true;
- if (optarg)
- arg_boot_descriptor = optarg;
- else if (optind < argc) {
- int boot;
+ if (optarg) {
+ r = parse_boot_descriptor(optarg, &arg_boot_id, &arg_boot_offset);
+ if (r < 0) {
+ log_error("Failed to parse boot descriptor '%s'", optarg);
+ return -EINVAL;
+ }
+ } else {
+
+ /* 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... */
- if (argv[optind][0] != '-' ||
- safe_atoi(argv[optind], &boot) >= 0) {
- arg_boot_descriptor = argv[optind];
+ if (optind < argc &&
+ parse_boot_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset) >= 0)
optind++;
- }
}
break;
arg_journal_type |= SD_JOURNAL_CURRENT_USER;
break;
+ case 'M':
+ arg_machine = optarg;
+ break;
+
case 'D':
arg_directory = optarg;
break;
if (arg_follow && !arg_no_tail && arg_lines < 0)
arg_lines = 10;
- if (arg_directory && arg_file) {
- log_error("Please specify either -D/--directory= or --file=, not both.");
+ if (!!arg_directory + !!arg_file + !!arg_machine > 1) {
+ log_error("Please specify either -D/--directory= or --file= or -M/--machine=, not more than one.");
return -EINVAL;
}
return -EINVAL;
}
+ if (arg_action != ACTION_SHOW && optind < argc) {
+ log_error("Extraneous arguments starting with '%s'", argv[optind]);
+ return -EINVAL;
+ }
+
return 1;
}
if (executable_is_script(path, &interpreter) > 0) {
_cleanup_free_ char *comm;
- comm = strndup(path_get_file_name(path), 15);
+ comm = strndup(basename(path), 15);
if (!comm)
return log_oom();
t = strappend("_COMM=", comm);
/* Append _EXE only if the interpreter is not a link.
- Otherwise it might be outdated often. */
+ Otherwise, it might be outdated often. */
if (lstat(interpreter, &st) == 0 &&
!S_ISLNK(st.st_mode)) {
t2 = strappend("_EXE=", interpreter);
assert(j);
assert(boot_id);
- if (relative == 0 && !sd_id128_equal(*boot_id, SD_ID128_NULL))
- return 0;
-
r = sd_journal_query_unique(j, "_BOOT_ID");
if (r < 0)
return r;
static int add_boot(sd_journal *j) {
char match[9+32+1] = "_BOOT_ID=";
- char *offset;
- sd_id128_t boot_id = SD_ID128_NULL;
- int r, relative = 0;
+ int r;
assert(j);
if (!arg_boot)
return 0;
- if (!arg_boot_descriptor)
- return add_match_this_boot(j);
-
- if (strlen(arg_boot_descriptor) >= 32) {
- char tmp = arg_boot_descriptor[32];
- arg_boot_descriptor[32] = '\0';
- r = sd_id128_from_string(arg_boot_descriptor, &boot_id);
- arg_boot_descriptor[32] = tmp;
-
- if (r < 0) {
- log_error("Failed to parse boot ID '%.32s': %s",
- arg_boot_descriptor, strerror(-r));
- return r;
- }
-
- offset = arg_boot_descriptor + 32;
+ if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL))
+ return add_match_this_boot(j, arg_machine);
- if (*offset && *offset != '-' && *offset != '+') {
- log_error("Relative boot ID offset must start with a '+' or a '-', found '%s' ", offset);
- return -EINVAL;
- }
- } else
- offset = arg_boot_descriptor;
-
- if (*offset) {
- r = safe_atoi(offset, &relative);
- if (r < 0) {
- log_error("Failed to parse relative boot ID number '%s'", offset);
- return -EINVAL;
- }
- }
-
- r = get_relative_boot_id(j, &boot_id, relative);
+ r = get_relative_boot_id(j, &arg_boot_id, arg_boot_offset);
if (r < 0) {
- if (sd_id128_equal(boot_id, SD_ID128_NULL))
- log_error("Failed to look up boot %+d: %s", relative, strerror(-r));
+ if (sd_id128_equal(arg_boot_id, SD_ID128_NULL))
+ log_error("Failed to look up boot %+i: %s", arg_boot_offset, strerror(-r));
else
- log_error("Failed to look up boot ID "SD_ID128_FORMAT_STR"%+d: %s",
- SD_ID128_FORMAT_VAL(boot_id), relative, strerror(-r));
+ log_error("Failed to look up boot ID "SD_ID128_FORMAT_STR"%+i: %s",
+ SD_ID128_FORMAT_VAL(arg_boot_id), arg_boot_offset, strerror(-r));
return r;
}
- sd_id128_to_string(boot_id, match + 9);
+ sd_id128_to_string(arg_boot_id, match + 9);
r = sd_journal_add_match(j, match, sizeof(match) - 1);
if (r < 0) {
return 0;
}
-static int add_units(sd_journal *j) {
- _cleanup_free_ char *u = NULL;
+static int get_possible_units(sd_journal *j,
+ const char *fields,
+ char **patterns,
+ Set **units) {
+ _cleanup_set_free_free_ Set *found;
+ const char *field;
int r;
+
+ found = set_new(string_hash_func, string_compare_func);
+ if (!found)
+ return log_oom();
+
+ 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) {
+ char **pattern, *eq;
+ size_t prefix;
+ _cleanup_free_ char *u = NULL;
+
+ eq = memchr(data, '=', size);
+ if (eq)
+ prefix = eq - (char*) data + 1;
+ else
+ prefix = 0;
+
+ u = strndup((char*) data + prefix, size - prefix);
+ if (!u)
+ return log_oom();
+
+ STRV_FOREACH(pattern, patterns)
+ if (fnmatch(*pattern, u, FNM_NOESCAPE) == 0) {
+ log_debug("Matched %s with pattern %s=%s", u, field, *pattern);
+
+ r = set_consume(found, u);
+ u = NULL;
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ break;
+ }
+ }
+ }
+
+ *units = found;
+ found = NULL;
+ 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"
+
+static int add_units(sd_journal *j) {
+ _cleanup_strv_free_ char **patterns = NULL;
+ int r, count = 0;
char **i;
assert(j);
STRV_FOREACH(i, arg_system_units) {
- u = unit_name_mangle(*i);
+ _cleanup_free_ char *u = NULL;
+
+ u = unit_name_mangle(*i, MANGLE_GLOB);
if (!u)
return log_oom();
- r = add_matches_for_unit(j, u);
- if (r < 0)
- return r;
- r = sd_journal_add_disjunction(j);
+
+ if (string_is_glob(u)) {
+ r = strv_push(&patterns, u);
+ if (r < 0)
+ return r;
+ u = NULL;
+ } else {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count ++;
+ }
+ }
+
+ if (!strv_isempty(patterns)) {
+ _cleanup_set_free_free_ Set *units = NULL;
+ Iterator it;
+ char *u;
+
+ r = get_possible_units(j, SYSTEM_UNITS, patterns, &units);
if (r < 0)
return r;
+
+ SET_FOREACH(u, units, it) {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count ++;
+ }
}
+ strv_free(patterns);
+ patterns = NULL;
+
STRV_FOREACH(i, arg_user_units) {
- u = unit_name_mangle(*i);
+ _cleanup_free_ char *u = NULL;
+
+ u = unit_name_mangle(*i, MANGLE_GLOB);
if (!u)
return log_oom();
- r = add_matches_for_user_unit(j, u, getuid());
- if (r < 0)
- return r;
+ if (string_is_glob(u)) {
+ r = strv_push(&patterns, u);
+ if (r < 0)
+ return r;
+ u = NULL;
+ } else {
+ r = add_matches_for_user_unit(j, u, getuid());
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count ++;
+ }
+ }
+
+ if (!strv_isempty(patterns)) {
+ _cleanup_set_free_free_ Set *units = NULL;
+ Iterator it;
+ char *u;
- r = sd_journal_add_disjunction(j);
+ r = get_possible_units(j, USER_UNITS, patterns, &units);
if (r < 0)
return r;
+ SET_FOREACH(u, units, it) {
+ r = add_matches_for_user_unit(j, u, getuid());
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count ++;
+ }
}
+ /* Complain if the user request matches but nothing whatsoever was
+ * found, since otherwise everything would be matched. */
+ if (!(strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) && count == 0)
+ return -ENODATA;
+
r = sd_journal_add_conjunction(j);
if (r < 0)
return r;
n = now(CLOCK_REALTIME);
n /= arg_interval;
- close_nointr_nofail(fd);
- fd = mkostemp(k, O_WRONLY|O_CLOEXEC|O_NOCTTY);
+ safe_close(fd);
+ fd = mkostemp_safe(k, O_WRONLY|O_CLOEXEC);
if (fd < 0) {
log_error("Failed to open %s: %m", k);
r = -errno;
r = 0;
finish:
- if (fd >= 0)
- close_nointr_nofail(fd);
+ safe_close(fd);
if (k) {
unlink(k);
r = sd_journal_open_directory(&j, arg_directory, arg_journal_type);
else if (arg_file)
r = sd_journal_open_files(&j, (const char**) arg_file, 0);
+ else if (arg_machine)
+ r = sd_journal_open_container(&j, arg_machine, 0);
else
r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
if (r < 0) {
}
if (arg_action == ACTION_DISK_USAGE) {
- uint64_t bytes;
+ uint64_t bytes = 0;
char sbytes[FORMAT_BYTES_MAX];
r = sd_journal_get_usage(j, &bytes);
strv_free(arg_system_units);
strv_free(arg_user_units);
- if (r < 0)
+ if (r < 0) {
+ log_error("Failed to add filter for units: %s", strerror(-r));
return EXIT_FAILURE;
+ }
r = add_priorities(j);
- if (r < 0)
+ if (r < 0) {
+ log_error("Failed to add filter for priorities: %s", strerror(-r));
return EXIT_FAILURE;
+ }
r = add_matches(j, argv + optind);
- if (r < 0)
+ if (r < 0) {
+ log_error("Failed to add filters: %s", strerror(-r));
return EXIT_FAILURE;
+ }
if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
_cleanup_free_ char *filter;
return EXIT_FAILURE;
}
- if (!arg_no_pager && !arg_follow)
- pager_open(arg_pager_end);
+ if (!arg_follow)
+ pager_open_if_enabled();
if (!arg_quiet) {
usec_t start, end;
finish:
pager_close();
+ strv_free(arg_file);
+
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}