We often used a pattern like if (!FLAGS_SET(flags, SD_JSON_FORMAT_OFF)), which is rather verbose and also contains a double negative, which we try to avoid. Add a little helper to avoid an explicit bit check. This change clarifies an aditional thing: in some cases we treated SD_JSON_FORMAT_OFF as a flag (flags & SD_JSON_FORMAT_OFF), while in other cases we treated it as an independent enum value (flags == SD_JSON_FORMAT_OFF). In the first form, flags like SD_JSON_FORMAT_SSE do _not_ turn the json output on, while in the second form they do. Let's use the first form everywhere. No functional change intended. Initially I wasn't sure if this helper should be made public or just internal, but it seems such a common pattern that if we expose the flags, we might just as well expose it too, to make life easier for any consumers.
347 lines
12 KiB
C
347 lines
12 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "dirent-util.h"
|
|
#include "fd-util.h"
|
|
#include "format-table.h"
|
|
#include "format-util.h"
|
|
#include "journal-internal.h"
|
|
#include "journal-verify.h"
|
|
#include "journalctl.h"
|
|
#include "journalctl-misc.h"
|
|
#include "journalctl-util.h"
|
|
#include "logs-show.h"
|
|
#include "syslog-util.h"
|
|
|
|
int action_print_header(void) {
|
|
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
|
|
int r;
|
|
|
|
assert(arg_action == ACTION_PRINT_HEADER);
|
|
|
|
r = acquire_journal(&j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
journal_print_header(j);
|
|
return 0;
|
|
}
|
|
|
|
int action_verify(void) {
|
|
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
|
|
int r;
|
|
|
|
assert(arg_action == ACTION_VERIFY);
|
|
|
|
r = acquire_journal(&j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
log_show_color(true);
|
|
|
|
JournalFile *f;
|
|
ORDERED_HASHMAP_FOREACH(f, j->files) {
|
|
int k;
|
|
usec_t first = 0, validated = 0, last = 0;
|
|
|
|
#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
|
|
|
|
k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, /* show_progress = */ !arg_quiet);
|
|
if (k == -EINVAL)
|
|
/* If the key was invalid give up right-away. */
|
|
return k;
|
|
if (k < 0)
|
|
r = log_warning_errno(k, "FAIL: %s (%m)", f->path);
|
|
else {
|
|
char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
|
|
log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "PASS: %s", f->path);
|
|
|
|
if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) {
|
|
if (validated > 0) {
|
|
log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
|
|
"=> Validated from %s to %s, final %s entries not sealed.",
|
|
format_timestamp_maybe_utc(a, sizeof(a), first),
|
|
format_timestamp_maybe_utc(b, sizeof(b), validated),
|
|
FORMAT_TIMESPAN(last > validated ? last - validated : 0, 0));
|
|
} else if (last > 0)
|
|
log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
|
|
"=> No sealing yet, %s of entries not sealed.",
|
|
FORMAT_TIMESPAN(last - first, 0));
|
|
else
|
|
log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
|
|
"=> No sealing yet, no entries in file.");
|
|
}
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int action_disk_usage(void) {
|
|
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
|
|
uint64_t bytes = 0;
|
|
int r;
|
|
|
|
assert(arg_action == ACTION_DISK_USAGE);
|
|
|
|
r = acquire_journal(&j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_journal_get_usage(j, &bytes);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to get disk usage: %m");
|
|
|
|
printf("Archived and active journals take up %s in the file system.\n", FORMAT_BYTES(bytes));
|
|
return 0;
|
|
}
|
|
|
|
static int show_log_ids(const LogId *ids, size_t n_ids, const char *name) {
|
|
_cleanup_(table_unrefp) Table *table = NULL;
|
|
int r;
|
|
|
|
assert(ids);
|
|
assert(n_ids > 0);
|
|
assert((uint64_t) INT64_MAX - n_ids > 0);
|
|
assert(name);
|
|
|
|
table = table_new("idx", name, "first entry", "last entry");
|
|
if (!table)
|
|
return log_oom();
|
|
|
|
if (arg_full)
|
|
table_set_width(table, 0);
|
|
|
|
r = table_set_json_field_name(table, 0, "index");
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to set JSON field name of column 0: %m");
|
|
|
|
(void) table_set_sort(table, (size_t) 0);
|
|
(void) table_set_reverse(table, 0, arg_reverse);
|
|
|
|
for (size_t i = 0; i < n_ids; i++) {
|
|
int64_t index;
|
|
|
|
if (arg_lines_needs_seek_end())
|
|
/* With --lines=N, we only know the negative index, and the older ID is located earlier. */
|
|
index = - (int64_t) i;
|
|
else if (arg_lines >= 0)
|
|
/* With --lines=+N, we only know the positive index, and the newer ID is located earlier. */
|
|
index = i + 1;
|
|
else
|
|
/* Otherwise, show negative index. Note, in this case, newer ID is located earlier. */
|
|
index = (int64_t) (i + 1) - (int64_t) n_ids;
|
|
|
|
r = table_add_many(table,
|
|
TABLE_INT64, index,
|
|
TABLE_SET_ALIGN_PERCENT, 100,
|
|
TABLE_ID128, ids[i].id,
|
|
TABLE_TIMESTAMP, ids[i].first_usec,
|
|
TABLE_TIMESTAMP, ids[i].last_usec);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
}
|
|
|
|
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet);
|
|
if (r < 0)
|
|
return table_log_print_error(r);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int action_list_boots(void) {
|
|
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
|
|
_cleanup_free_ LogId *ids = NULL;
|
|
size_t n_ids;
|
|
int r;
|
|
|
|
assert(arg_action == ACTION_LIST_BOOTS);
|
|
|
|
r = acquire_journal(&j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = journal_get_boots(
|
|
j,
|
|
/* advance_older = */ arg_lines_needs_seek_end(),
|
|
/* max_ids = */ arg_lines >= 0 ? (size_t) arg_lines : SIZE_MAX,
|
|
&ids, &n_ids);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to determine boots: %m");
|
|
if (r == 0)
|
|
return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENODATA),
|
|
"No boot found.");
|
|
|
|
return show_log_ids(ids, n_ids, "boot id");
|
|
}
|
|
|
|
int action_list_fields(void) {
|
|
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
|
|
int r, n_shown = 0;
|
|
|
|
assert(arg_action == ACTION_LIST_FIELDS);
|
|
assert(arg_field);
|
|
|
|
r = acquire_journal(&j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!journal_boot_has_effect(j))
|
|
return 0;
|
|
|
|
r = sd_journal_set_data_threshold(j, 0);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to unset data size threshold: %m");
|
|
|
|
r = sd_journal_query_unique(j, arg_field);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to query unique data objects: %m");
|
|
|
|
const void *data;
|
|
size_t size;
|
|
SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
|
|
const void *eq;
|
|
|
|
if (arg_lines >= 0 && n_shown >= arg_lines)
|
|
break;
|
|
|
|
eq = memchr(data, '=', size);
|
|
if (eq)
|
|
printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1);
|
|
else
|
|
printf("%.*s\n", (int) size, (const char*) data);
|
|
|
|
n_shown++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int action_list_field_names(void) {
|
|
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
|
|
int r;
|
|
|
|
assert(arg_action == ACTION_LIST_FIELD_NAMES);
|
|
|
|
r = acquire_journal(&j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
const char *field;
|
|
SD_JOURNAL_FOREACH_FIELD(j, field)
|
|
printf("%s\n", field);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int action_list_invocations(void) {
|
|
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
|
|
_cleanup_free_ LogId *ids = NULL;
|
|
size_t n_ids;
|
|
LogIdType type;
|
|
const char *unit;
|
|
int r;
|
|
|
|
assert(arg_action == ACTION_LIST_INVOCATIONS);
|
|
|
|
r = acquire_unit("--list-invocations", &unit, &type);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = acquire_journal(&j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = journal_acquire_boot(j);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = journal_get_log_ids(
|
|
j, type,
|
|
/* boot_id = */ arg_boot_id, /* unit = */ unit,
|
|
/* advance_older = */ arg_lines_needs_seek_end(),
|
|
/* max_ids = */ arg_lines >= 0 ? (size_t) arg_lines : SIZE_MAX,
|
|
&ids, &n_ids);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to list invocation id for %s: %m", unit);
|
|
if (r == 0)
|
|
return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENODATA),
|
|
"No invocation ID for %s found.", unit);
|
|
|
|
return show_log_ids(ids, n_ids, "invocation id");
|
|
}
|
|
|
|
int action_list_namespaces(void) {
|
|
_cleanup_(table_unrefp) Table *table = NULL;
|
|
sd_id128_t machine;
|
|
int r;
|
|
|
|
assert(arg_action == ACTION_LIST_NAMESPACES);
|
|
|
|
r = sd_id128_get_machine(&machine);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to get machine ID: %m");
|
|
|
|
table = table_new("namespace");
|
|
if (!table)
|
|
return log_oom();
|
|
|
|
(void) table_set_sort(table, (size_t) 0);
|
|
|
|
FOREACH_STRING(dir, "/var/log/journal", "/run/log/journal") {
|
|
_cleanup_free_ char *path = NULL;
|
|
_cleanup_closedir_ DIR *dirp = NULL;
|
|
|
|
path = path_join(arg_root, dir);
|
|
if (!path)
|
|
return log_oom();
|
|
|
|
dirp = opendir(path);
|
|
if (!dirp) {
|
|
log_debug_errno(errno, "Failed to open directory %s, ignoring: %m", path);
|
|
continue;
|
|
}
|
|
|
|
FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", path)) {
|
|
|
|
const char *e = strchr(de->d_name, '.');
|
|
if (!e)
|
|
continue;
|
|
|
|
_cleanup_free_ char *ids = strndup(de->d_name, e - de->d_name);
|
|
if (!ids)
|
|
return log_oom();
|
|
|
|
sd_id128_t id;
|
|
r = sd_id128_from_string(ids, &id);
|
|
if (r < 0)
|
|
continue;
|
|
|
|
if (!sd_id128_equal(machine, id))
|
|
continue;
|
|
|
|
e++;
|
|
|
|
if (!log_namespace_name_valid(e))
|
|
continue;
|
|
|
|
r = table_add_cell(table, NULL, TABLE_STRING, e);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
}
|
|
}
|
|
|
|
if (table_isempty(table) && !sd_json_format_enabled(arg_json_format_flags)) {
|
|
if (!arg_quiet)
|
|
log_notice("No namespaces found.");
|
|
} else {
|
|
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|