+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
#include "journal-def.h"
#include "journal-internal.h"
#include "journal-qrcode.h"
+#include "journal-util.h"
#include "journal-vacuum.h"
#include "journal-verify.h"
#include "locale-util.h"
static char **arg_file = NULL;
static bool arg_file_stdin = false;
static int arg_priorities = 0xFF;
-static const char *arg_verify_key = NULL;
-#ifdef HAVE_GCRYPT
+static char *arg_verify_key = NULL;
+#if HAVE_GCRYPT
static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
static bool arg_force = false;
#endif
static uint64_t arg_vacuum_size = 0;
static uint64_t arg_vacuum_n_files = 0;
static usec_t arg_vacuum_time = 0;
+static char **arg_output_fields = NULL;
static enum {
ACTION_SHOW,
continue;
}
- match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname, NULL);
+ match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname);
if (!match)
return log_oom();
if (r >= 0)
x += 32;
- if (*x != '-' && *x != '+' && *x != 0)
+ if (!IN_SET(*x, 0, '-', '+'))
return -EINVAL;
if (*x != 0) {
" --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-precise,\n"
- " short-iso, short-full, short-monotonic, short-unix,\n"
- " verbose, export, json, json-pretty, json-sse, cat)\n"
+ " short-iso, short-iso-precise, short-full,\n"
+ " short-monotonic, short-unix, verbose, export,\n"
+ " 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"
" -m --merge Show entries from all available journals\n"
" -D --directory=PATH Show journal files from directory\n"
" --file=PATH Show journal file\n"
- " --root=ROOT Operate on catalog files below a root directory\n"
-#ifdef HAVE_GCRYPT
+ " --root=ROOT Operate on files below a root directory\n"
+#if HAVE_GCRYPT
" --interval=TIME Time interval for changing the FSS sealing key\n"
" --verify-key=KEY Specify FSS verification key\n"
" --force Override of the FSS key pair with --setup-keys\n"
" --dump-catalog Show entries in the message catalog\n"
" --update-catalog Update the message catalog database\n"
" --new-id128 Generate a new 128-bit ID\n"
-#ifdef HAVE_GCRYPT
+#if HAVE_GCRYPT
" --setup-keys Generate a new FSS key pair\n"
#endif
, program_invocation_short_name);
ARG_VACUUM_FILES,
ARG_VACUUM_TIME,
ARG_NO_HOSTNAME,
+ ARG_OUTPUT_FIELDS,
};
static const struct option options[] = {
{ "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES },
{ "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME },
{ "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME },
+ { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS },
{}
};
return -EINVAL;
}
- if (arg_output == OUTPUT_EXPORT ||
- arg_output == OUTPUT_JSON ||
- arg_output == OUTPUT_JSON_PRETTY ||
- arg_output == OUTPUT_JSON_SSE ||
- arg_output == OUTPUT_CAT)
+ if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_CAT))
arg_quiet = true;
break;
arg_action = ACTION_VACUUM;
break;
-#ifdef HAVE_GCRYPT
+#if HAVE_GCRYPT
case ARG_FORCE:
arg_force = true;
break;
case ARG_VERIFY_KEY:
arg_action = ACTION_VERIFY;
- arg_verify_key = optarg;
+ r = free_and_strdup(&arg_verify_key, optarg);
+ if (r < 0)
+ return r;
+ /* Use memset not string_erase so this doesn't look confusing
+ * in ps or htop output. */
+ memset(optarg, 'x', strlen(optarg));
+
arg_merge = false;
break;
arg_action = ACTION_SYNC;
break;
+ case ARG_OUTPUT_FIELDS: {
+ _cleanup_strv_free_ char **v = NULL;
+
+ v = strv_split(optarg, ",");
+ if (!v)
+ return log_oom();
+
+ if (!arg_output_fields) {
+ arg_output_fields = v;
+ v = NULL;
+ } else {
+ r = strv_extend_strv(&arg_output_fields, v, true);
+ if (r < 0)
+ return log_oom();
+ }
+ break;
+ }
+
case '?':
return -EINVAL;
if (arg_follow && !arg_no_tail && !arg_since && arg_lines == ARG_LINES_DEFAULT)
arg_lines = 10;
- if (!!arg_directory + !!arg_file + !!arg_machine > 1) {
- log_error("Please specify either -D/--directory= or --file= or -M/--machine=, not more than one.");
+ if (!!arg_directory + !!arg_file + !!arg_machine + !!arg_root > 1) {
+ log_error("Please specify at most one of -D/--directory=, --file=, -M/--machine=, --root.");
return -EINVAL;
}
* to users, and automatically turn --unit= into --user-unit= if combined with --user. */
r = strv_extend_strv(&arg_user_units, arg_system_units, true);
if (r < 0)
- return -ENOMEM;
+ return r;
arg_system_units = strv_free(arg_system_units);
}
SD_ID128_FORMAT_STR "\n\n"
"As UUID:\n"
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
- "As macro:\n"
+ "As man:sd-id128(3) macro:\n"
"#define MESSAGE_XYZ SD_ID128_MAKE(",
SD_ID128_FORMAT_VAL(id),
SD_ID128_FORMAT_VAL(id));
have_term = false;
} else if (path_is_absolute(*i)) {
- _cleanup_free_ char *p, *t = NULL, *t2 = NULL, *interpreter = NULL;
- const char *path;
+ _cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL;
struct stat st;
- p = canonicalize_file_name(*i);
- path = p ?: *i;
+ r = chase_symlinks(*i, NULL, 0, &p);
+ if (r < 0)
+ return log_error_errno(r, "Couldn't canonicalize path: %m");
- if (lstat(path, &st) < 0)
+ if (lstat(p, &st) < 0)
return log_error_errno(errno, "Couldn't stat file: %m");
if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
- if (executable_is_script(path, &interpreter) > 0) {
+ if (executable_is_script(p, &interpreter) > 0) {
_cleanup_free_ char *comm;
- comm = strndup(basename(path), 15);
+ comm = strndup(basename(p), 15);
if (!comm)
return log_oom();
return log_oom();
}
} else {
- t = strappend("_EXE=", path);
+ t = strappend("_EXE=", p);
if (!t)
return log_oom();
}
r = sd_journal_add_match(j, t2, 0);
} else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
- r = add_matches_for_device(j, path);
+ r = add_matches_for_device(j, p);
if (r < 0)
return r;
} else {
r = sd_journal_previous(j);
if (r < 0)
return r;
- else if (r == 0)
+ else if (r == 0) {
+ log_debug("Whoopsie! We found a boot ID but can't read its last entry.");
return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */
+ }
r = sd_journal_get_realtime_usec(j, &next_boot->last);
if (r < 0)
bool skip_once;
int r, count = 0;
- BootId *head = NULL, *tail = NULL;
+ BootId *head = NULL, *tail = NULL, *id;
const bool advance_older = boot_id && offset <= 0;
sd_id128_t previous_boot_id;
break;
}
} else {
+ LIST_FOREACH(boot_list, id, head) {
+ if (sd_id128_equal(id->id, current->id)) {
+ /* boot id already stored, something wrong with the journal files */
+ /* exiting as otherwise this problem would cause forever loop */
+ goto finish;
+ }
+ }
LIST_INSERT_AFTER(boot_list, head, tail, current);
tail = current;
current = NULL;
* We can do this only when we logs are coming from the current machine,
* so take the slow path if log location is specified. */
if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) &&
- !arg_directory && !arg_file)
+ !arg_directory && !arg_file && !arg_root)
return add_match_this_boot(j, arg_machine);
}
static int setup_keys(void) {
-#ifdef HAVE_GCRYPT
+#if HAVE_GCRYPT
size_t mpk_size, seed_size, state_size, i;
uint8_t *mpk, *seed, *state;
int fd = -1, r;
struct stat st;
r = stat("/var/log/journal", &st);
- if (r < 0 && errno != ENOENT && errno != ENOTDIR)
+ if (r < 0 && !IN_SET(errno, ENOENT, ENOTDIR))
return log_error_errno(errno, "stat(\"%s\") failed: %m", "/var/log/journal");
if (r < 0 || !S_ISDIR(st.st_mode)) {
n /= arg_interval;
safe_close(fd);
- fd = mkostemp_safe(k, O_WRONLY|O_CLOEXEC);
+ fd = mkostemp_safe(k);
if (fd < 0) {
r = log_error_errno(fd, "Failed to open %s: %m", k);
goto finish;
"at a safe location and should not be saved locally on disk.\n"
"\n\t%s",
ansi_highlight(), ansi_normal(),
+ p,
ansi_highlight(), ansi_normal(),
- ansi_highlight_red(),
- p);
+ ansi_highlight_red());
fflush(stderr);
}
for (i = 0; i < seed_size; i++) {
} else
fprintf(stderr, "\nThe keys have been generated for host " SD_ID128_FORMAT_STR ".\n", SD_ID128_FORMAT_VAL(machine));
-#ifdef HAVE_QRENCODE
+#if HAVE_QRENCODE
/* If this is not an UTF-8 system don't print any QR codes */
if (is_locale_utf8()) {
fputs("\nTo transfer the verification key to your phone please scan the QR code below:\n\n", stderr);
int k;
usec_t first = 0, validated = 0, last = 0;
-#ifdef HAVE_GCRYPT
+#if HAVE_GCRYPT
if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header))
log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path);
#endif
return r;
}
-static int access_check_var_log_journal(sd_journal *j) {
-#ifdef HAVE_ACL
- _cleanup_strv_free_ char **g = NULL;
- const char* dir;
-#endif
- int r;
-
- assert(j);
-
- if (arg_quiet)
- return 0;
-
- /* If we are root, we should have access, don't warn. */
- if (getuid() == 0)
- return 0;
-
- /* If we are in the 'systemd-journal' group, we should have
- * access too. */
- r = in_group("systemd-journal");
- if (r < 0)
- return log_error_errno(r, "Failed to check if we are in the 'systemd-journal' group: %m");
- if (r > 0)
- return 0;
-
-#ifdef HAVE_ACL
- if (laccess("/run/log/journal", F_OK) >= 0)
- dir = "/run/log/journal";
- else
- dir = "/var/log/journal";
-
- /* If we are in any of the groups listed in the journal ACLs,
- * then all is good, too. Let's enumerate all groups from the
- * default ACL of the directory, which generally should allow
- * access to most journal files too. */
- r = acl_search_groups(dir, &g);
- if (r < 0)
- return log_error_errno(r, "Failed to search journal ACL: %m");
- if (r > 0)
- return 0;
-
- /* Print a pretty list, if there were ACLs set. */
- if (!strv_isempty(g)) {
- _cleanup_free_ char *s = NULL;
-
- /* Thre are groups in the ACL, let's list them */
- r = strv_extend(&g, "systemd-journal");
- if (r < 0)
- return log_oom();
-
- strv_sort(g);
- strv_uniq(g);
-
- s = strv_join(g, "', '");
- if (!s)
- return log_oom();
-
- log_notice("Hint: You are currently not seeing messages from other users and the system.\n"
- " Users in groups '%s' can see all messages.\n"
- " Pass -q to turn off this notice.", s);
- return 1;
- }
-#endif
-
- /* If no ACLs were found, print a short version of the message. */
- log_notice("Hint: You are currently not seeing messages from other users and the system.\n"
- " Users in the 'systemd-journal' group can see all messages. Pass -q to\n"
- " turn off this notice.");
-
- return 1;
-}
-
-static int access_check(sd_journal *j) {
- Iterator it;
- void *code;
- char *path;
- int r = 0;
-
- assert(j);
-
- if (hashmap_isempty(j->errors)) {
- if (ordered_hashmap_isempty(j->files))
- log_notice("No journal files were found.");
-
- return 0;
- }
-
- if (hashmap_contains(j->errors, INT_TO_PTR(-EACCES))) {
- (void) access_check_var_log_journal(j);
-
- if (ordered_hashmap_isempty(j->files))
- r = log_error_errno(EACCES, "No journal files were opened due to insufficient permissions.");
- }
-
- HASHMAP_FOREACH_KEY(path, code, j->errors, it) {
- int err;
-
- err = abs(PTR_TO_INT(code));
-
- switch (err) {
- case EACCES:
- continue;
-
- case ENODATA:
- log_warning_errno(err, "Journal file %s is truncated, ignoring file.", path);
- break;
-
- case EPROTONOSUPPORT:
- log_warning_errno(err, "Journal file %s uses an unsupported feature, ignoring file.", path);
- break;
-
- case EBADMSG:
- log_warning_errno(err, "Journal file %s corrupted, ignoring file.", path);
- break;
-
- default:
- log_warning_errno(err, "An error was encountered while opening journal file or directory %s, ignoring file: %m", path);
- break;
- }
- }
-
- return r;
-}
-
static int flush_to_var(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
if (arg_directory)
r = sd_journal_open_directory(&j, arg_directory, arg_journal_type);
+ else if (arg_root)
+ r = sd_journal_open_directory(&j, arg_root, arg_journal_type | SD_JOURNAL_OS_ROOT);
else if (arg_file_stdin) {
int ifd = STDIN_FILENO;
r = sd_journal_open_files_fd(&j, &ifd, 1, 0);
goto finish;
}
- r = access_check(j);
+ r = journal_access_check_and_warn(j, arg_quiet);
if (r < 0)
goto finish;
if (r < 0)
goto finish;
- printf("Archived and active journals take up %s on disk.\n",
+ printf("Archived and active journals take up %s in the file system.\n",
format_bytes(sbytes, sizeof(sbytes), bytes));
goto finish;
}
if (d->is_root)
continue;
- q = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, true);
+ q = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, !arg_quiet);
if (q < 0) {
log_error_errno(q, "Failed to vacuum %s: %m", d->path);
r = q;
if (arg_boot_offset != 0 &&
sd_journal_has_runtime_files(j) > 0 &&
sd_journal_has_persistent_files(j) == 0) {
- log_info("Specifying boot ID has no effect, no persistent journal was found");
+ log_info("Specifying boot ID or boot offset has no effect, no persistent journal was found.");
r = 0;
goto finish;
}
log_error_errno(r, "Failed to iterate through journal: %m");
goto finish;
}
- if (r == 0) {
- if (arg_follow)
- need_seek = true;
- else {
- if (!arg_quiet)
- printf("-- No entries --\n");
- goto finish;
- }
- }
+ if (r == 0)
+ need_seek = true;
if (!arg_follow)
pager_open(arg_no_pager, arg_pager_end);
- if (!arg_quiet) {
+ if (!arg_quiet && (arg_lines != 0 || arg_follow)) {
usec_t start, end;
char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
arg_utc * OUTPUT_UTC |
arg_no_hostname * OUTPUT_NO_HOSTNAME;
- r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized);
+ r = output_journal(stdout, j, arg_output, 0, flags, arg_output_fields, &ellipsized);
need_seek = true;
if (r == -EADDRNOTAVAIL)
break;
}
if (!arg_follow) {
+ if (n_shown == 0 && !arg_quiet)
+ printf("-- No entries --\n");
+
if (arg_show_cursor) {
_cleanup_free_ char *cursor = NULL;
break;
}
+ fflush(stdout);
r = sd_journal_wait(j, (uint64_t) -1);
if (r < 0) {
log_error_errno(r, "Couldn't wait for journal event: %m");
}
finish:
+ fflush(stdout);
pager_close();
strv_free(arg_file);
strv_free(arg_syslog_identifier);
strv_free(arg_system_units);
strv_free(arg_user_units);
+ strv_free(arg_output_fields);
free(arg_root);
+ free(arg_verify_key);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}