#include <locale.h>
#include <fcntl.h>
+#include <fnmatch.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include "acl-util.h"
#endif
-#include <systemd/sd-journal.h>
+#include "systemd/sd-journal.h"
#include "log.h"
#include "logs-show.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"
#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
static OutputMode arg_output = OUTPUT_SHORT;
+static bool arg_utc = false;
static bool arg_pager_end = false;
static bool arg_follow = false;
static bool arg_full = true;
static bool arg_all = false;
static bool arg_no_pager = false;
-static int arg_lines = -1;
+static int arg_lines = -2;
static bool arg_no_tail = false;
static bool arg_quiet = false;
static bool arg_merge = false;
#endif
static usec_t arg_since, arg_until;
static bool arg_since_set = false, arg_until_set = false;
+static char **arg_syslog_identifier = NULL;
static char **arg_system_units = NULL;
static char **arg_user_units = NULL;
static const char *arg_field = NULL;
pager_open(arg_pager_end);
}
+static char *format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) {
+
+ if (arg_utc)
+ return format_timestamp_utc(buf, l, t);
+
+ return format_timestamp(buf, l, t);
+}
+
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;
return 0;
}
-static int help(void) {
+static void 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 the current user\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"
" --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"
" -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"
+ " -t --identifier=STRING Show only messages with the specified syslog identifier\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 the journal\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"
+ " --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"
" --verify Verify journal file consistency\n"
#endif
, program_invocation_short_name);
-
- return 0;
}
static int parse_argv(int argc, char *argv[]) {
ARG_DUMP_CATALOG,
ARG_UPDATE_CATALOG,
ARG_FORCE,
+ ARG_UTC,
};
static const struct option options[] = {
{ "file", required_argument, NULL, ARG_FILE },
{ "root", required_argument, NULL, ARG_ROOT },
{ "header", no_argument, NULL, ARG_HEADER },
+ { "identifier", required_argument, NULL, 't' },
{ "priority", required_argument, NULL, 'p' },
{ "setup-keys", no_argument, NULL, ARG_SETUP_KEYS },
{ "interval", required_argument, NULL, ARG_INTERVAL },
{ "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG },
{ "reverse", no_argument, NULL, 'r' },
{ "machine", required_argument, NULL, 'M' },
+ { "utc", no_argument, NULL, ARG_UTC },
{}
};
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:u:F:xrM:", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:t:u:F:xrM:", options, NULL)) >= 0)
switch (c) {
case 'h':
- return help();
+ help();
+ return 0;
case ARG_VERSION:
puts(PACKAGE_STRING);
case 'e':
arg_pager_end = true;
- if (arg_lines < 0)
+ if (arg_lines < -1)
arg_lines = 1000;
break;
case 'n':
if (optarg) {
- r = safe_atoi(optarg, &arg_lines);
- if (r < 0 || arg_lines < 0) {
- log_error("Failed to parse lines '%s'", optarg);
- return -EINVAL;
+ if (streq(optarg, "all"))
+ arg_lines = -1;
+ else {
+ r = safe_atoi(optarg, &arg_lines);
+ if (r < 0 || arg_lines < 0) {
+ log_error("Failed to parse lines '%s'", optarg);
+ return -EINVAL;
+ }
}
} else {
- int n;
+ arg_lines = 10;
/* 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 positive
- * integer... */
-
- if (optind < argc &&
- safe_atoi(argv[optind], &n) >= 0 &&
- n >= 0) {
-
- arg_lines = n;
- optind++;
- } else
- arg_lines = 10;
+ * parsable. */
+ if (optind < argc) {
+ int n;
+ if (streq(argv[optind], "all")) {
+ arg_lines = -1;
+ optind++;
+ } else if (safe_atoi(argv[optind], &n) >= 0 && n >= 0) {
+ arg_lines = n;
+ optind++;
+ }
+ }
}
break;
arg_until_set = true;
break;
+ case 't':
+ r = strv_extend(&arg_syslog_identifier, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
case 'u':
r = strv_extend(&arg_system_units, optarg);
if (r < 0)
arg_reverse = true;
break;
+ case ARG_UTC:
+ arg_utc = true;
+ break;
+
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
- }
- if (arg_follow && !arg_no_tail && arg_lines < 0)
+ if (arg_follow && !arg_no_tail && arg_lines < -1)
arg_lines = 10;
if (!!arg_directory + !!arg_file + !!arg_machine > 1) {
return -EINVAL;
}
+ if (arg_action != ACTION_SHOW && optind < argc) {
+ log_error("Extraneous arguments starting with '%s'", argv[optind]);
+ return -EINVAL;
+ }
+
return 1;
}
static int add_matches(sd_journal *j, char **args) {
char **i;
+ bool have_term = false;
assert(j);
STRV_FOREACH(i, args) {
int r;
- if (streq(*i, "+"))
+ if (streq(*i, "+")) {
+ if (!have_term)
+ break;
r = sd_journal_add_disjunction(j);
- else if (path_is_absolute(*i)) {
+ have_term = false;
+
+ } else if (path_is_absolute(*i)) {
_cleanup_free_ char *p, *t = NULL, *t2 = NULL;
const char *path;
_cleanup_free_ char *interpreter = NULL;
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);
}
} else
t = strappend("_EXE=", path);
- } else if (S_ISCHR(st.st_mode))
- asprintf(&t, "_KERNEL_DEVICE=c%u:%u", major(st.st_rdev), minor(st.st_rdev));
- else if (S_ISBLK(st.st_mode))
- asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev));
- else {
+ } else if (S_ISCHR(st.st_mode)) {
+ if (asprintf(&t, "_KERNEL_DEVICE=c%u:%u",
+ major(st.st_rdev),
+ minor(st.st_rdev)) < 0)
+ return -ENOMEM;
+ } else if (S_ISBLK(st.st_mode)) {
+ if (asprintf(&t, "_KERNEL_DEVICE=b%u:%u",
+ major(st.st_rdev),
+ minor(st.st_rdev)) < 0)
+ return -ENOMEM;
+ } else {
log_error("File is neither a device node, nor regular file, nor executable: %s", *i);
return -EINVAL;
}
r = sd_journal_add_match(j, t, 0);
if (t2)
r = sd_journal_add_match(j, t2, 0);
- } else
+ have_term = true;
+
+ } else {
r = sd_journal_add_match(j, *i, 0);
+ have_term = true;
+ }
if (r < 0) {
log_error("Failed to add match '%s': %s", *i, strerror(-r));
}
}
+ if (!strv_isempty(args) && !have_term) {
+ log_error("\"+\" can only be used between terms");
+ return -EINVAL;
+ }
+
return 0;
}
return r;
SD_JOURNAL_FOREACH_UNIQUE(j, data, length) {
- if (length < strlen("_BOOT_ID="))
- continue;
+ assert(startswith(data, "_BOOT_ID="));
if (!GREEDY_REALLOC(all_ids, allocated, count + 1))
return log_oom();
printf("% *i " SD_ID128_FORMAT_STR " %s—%s\n",
w, i - count + 1,
SD_ID128_FORMAT_VAL(id->id),
- format_timestamp(a, sizeof(a), id->first),
- format_timestamp(b, sizeof(b), id->last));
+ format_timestamp_maybe_utc(a, sizeof(a), id->first),
+ format_timestamp_maybe_utc(b, sizeof(b), id->last));
}
return 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_ops);
+ 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, MANGLE_NOGLOB);
+ _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, MANGLE_NOGLOB);
+ _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 ++;
+ }
+ }
- r = sd_journal_add_disjunction(j);
+ if (!strv_isempty(patterns)) {
+ _cleanup_set_free_free_ Set *units = NULL;
+ Iterator it;
+ char *u;
+
+ 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;
return 0;
}
+
+static int add_syslog_identifier(sd_journal *j) {
+ int r;
+ char **i;
+
+ assert(j);
+
+ STRV_FOREACH(i, arg_syslog_identifier) {
+ char *u;
+
+ u = strappenda("SYSLOG_IDENTIFIER=", *i);
+ r = sd_journal_add_match(j, u, 0);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
static int setup_keys(void) {
#ifdef HAVE_GCRYPT
size_t mpk_size, seed_size, state_size, i;
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);
if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) {
if (validated > 0) {
log_info("=> Validated from %s to %s, final %s entries not sealed.",
- format_timestamp(a, sizeof(a), first),
- format_timestamp(b, sizeof(b), validated),
+ format_timestamp_maybe_utc(a, sizeof(a), first),
+ format_timestamp_maybe_utc(b, sizeof(b), validated),
format_timespan(c, sizeof(c), last > validated ? last - validated : 0, 0));
} else if (last > 0)
log_info("=> No sealing yet, %s of entries not sealed.",
arg_action == ACTION_LIST_CATALOG ||
arg_action == ACTION_DUMP_CATALOG) {
- const char* database = CATALOG_DATABASE;
- _cleanup_free_ char *copy = NULL;
- if (arg_root) {
- copy = strjoin(arg_root, "/", CATALOG_DATABASE, NULL);
- if (!copy) {
- r = log_oom();
- goto finish;
- }
- path_kill_slashes(copy);
- database = copy;
+ _cleanup_free_ char *database;
+
+ database = path_join(arg_root, CATALOG_DATABASE, NULL);
+ if (!database) {
+ r = log_oom();
+ goto finish;
}
if (arg_action == ACTION_UPDATE_CATALOG) {
}
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_syslog_identifier(j);
+ if (r < 0) {
+ log_error("Failed to add filter for syslog identifiers: %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;
}
if (arg_cursor || arg_after_cursor) {
- r = sd_journal_seek_cursor(j, arg_cursor ? arg_cursor : arg_after_cursor);
+ r = sd_journal_seek_cursor(j, arg_cursor ?: arg_after_cursor);
if (r < 0) {
log_error("Failed to seek to cursor: %s", strerror(-r));
return EXIT_FAILURE;
if (r > 0) {
if (arg_follow)
printf("-- Logs begin at %s. --\n",
- format_timestamp(start_buf, sizeof(start_buf), start));
+ format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start));
else
printf("-- Logs begin at %s, end at %s. --\n",
- format_timestamp(start_buf, sizeof(start_buf), start),
- format_timestamp(end_buf, sizeof(end_buf), end));
+ format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start),
+ format_timestamp_maybe_utc(end_buf, sizeof(end_buf), end));
}
}
goto finish;
}
- if (!arg_merge) {
+ if (!arg_merge && !arg_quiet) {
sd_id128_t boot_id;
r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
arg_all * OUTPUT_SHOW_ALL |
arg_full * OUTPUT_FULL_WIDTH |
on_tty() * OUTPUT_COLOR |
- arg_catalog * OUTPUT_CATALOG;
+ arg_catalog * OUTPUT_CATALOG |
+ arg_utc * OUTPUT_UTC;
r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized);
need_seek = true;
finish:
pager_close();
+ strv_free(arg_file);
+
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}