diff options
Diffstat (limited to 'src/journal')
-rw-r--r-- | src/journal/cat.c | 10 | ||||
-rw-r--r-- | src/journal/journal-internal.h | 23 | ||||
-rw-r--r-- | src/journal/journalctl.c | 162 | ||||
-rw-r--r-- | src/journal/sd-journal.c | 190 |
4 files changed, 339 insertions, 46 deletions
diff --git a/src/journal/cat.c b/src/journal/cat.c index 7fd4198df8..07c3df522c 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -34,7 +34,7 @@ #include "syslog-util.h" #include "util.h" -static char *arg_identifier = NULL; +static const char *arg_identifier = NULL; static int arg_priority = LOG_INFO; static bool arg_level_prefix = true; @@ -82,14 +82,10 @@ static int parse_argv(int argc, char *argv[]) { return version(); case 't': - free(arg_identifier); if (isempty(optarg)) arg_identifier = NULL; - else { - arg_identifier = strdup(optarg); - if (!arg_identifier) - return log_oom(); - } + else + arg_identifier = optarg; break; case 'p': diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h index 9ff4fea7fb..a55d1bcc47 100644 --- a/src/journal/journal-internal.h +++ b/src/journal/journal-internal.h @@ -103,18 +103,29 @@ struct sd_journal { unsigned current_invalidate_counter, last_invalidate_counter; usec_t last_process_usec; + /* Iterating through unique fields and their data values */ char *unique_field; JournalFile *unique_file; uint64_t unique_offset; + /* Iterating through known fields */ + JournalFile *fields_file; + uint64_t fields_offset; + uint64_t fields_hash_table_index; + char *fields_buffer; + size_t fields_buffer_allocated; + int flags; - bool on_network; - bool no_new_files; - bool unique_file_lost; /* File we were iterating over got - removed, and there were no more - files, so sd_j_enumerate_unique - will return a value equal to 0. */ + bool on_network:1; + bool no_new_files:1; + bool unique_file_lost:1; /* File we were iterating over got + removed, and there were no more + files, so sd_j_enumerate_unique + will return a value equal to 0. */ + bool fields_file_lost:1; + bool has_runtime_files:1; + bool has_persistent_files:1; size_t data_threshold; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index db11421e7a..20f7082175 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -69,6 +69,8 @@ #include "strv.h" #include "syslog-util.h" #include "terminal-util.h" +#include "udev.h" +#include "udev-util.h" #include "unit-name.h" #include "user-util.h" @@ -136,6 +138,8 @@ static enum { ACTION_SYNC, ACTION_ROTATE, ACTION_VACUUM, + ACTION_LIST_FIELDS, + ACTION_LIST_FIELD_NAMES, } arg_action = ACTION_SHOW; typedef struct BootId { @@ -145,6 +149,84 @@ typedef struct BootId { LIST_FIELDS(struct BootId, boot_list); } BootId; +static int add_matches_for_device(sd_journal *j, const char *devpath) { + int r; + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + struct udev_device *d = NULL; + struct stat st; + + assert(j); + assert(devpath); + + if (!path_startswith(devpath, "/dev/")) { + log_error("Devpath does not start with /dev/"); + return -EINVAL; + } + + udev = udev_new(); + if (!udev) + return log_oom(); + + r = stat(devpath, &st); + if (r < 0) + log_error_errno(errno, "Couldn't stat file: %m"); + + d = device = udev_device_new_from_devnum(udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev); + if (!device) + return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev)); + + while (d) { + _cleanup_free_ char *match = NULL; + const char *subsys, *sysname, *devnode; + + subsys = udev_device_get_subsystem(d); + if (!subsys) { + d = udev_device_get_parent(d); + continue; + } + + sysname = udev_device_get_sysname(d); + if (!sysname) { + d = udev_device_get_parent(d); + continue; + } + + match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname, NULL); + if (!match) + return log_oom(); + + r = sd_journal_add_match(j, match, 0); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + devnode = udev_device_get_devnode(d); + if (devnode) { + _cleanup_free_ char *match1 = NULL; + + r = stat(devnode, &st); + if (r < 0) + return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode); + + r = asprintf(&match1, "_KERNEL_DEVICE=%c%u:%u", S_ISBLK(st.st_mode) ? 'b' : 'c', major(st.st_rdev), minor(st.st_rdev)); + if (r < 0) + return log_oom(); + + r = sd_journal_add_match(j, match1, 0); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + } + + d = udev_device_get_parent(d); + } + + r = add_match_this_boot(j, arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to add match for the current boot: %m"); + + return 0; +} + static void pager_open_if_enabled(void) { if (arg_no_pager) @@ -244,6 +326,7 @@ static void help(void) { "\nCommands:\n" " -h --help Show this help text\n" " --version Show package version\n" + " -N --fields List all field names currently used\n" " -F --field=FIELD List all values that a specified field takes\n" " --disk-usage Show total disk usage of all journal files\n" " --vacuum-size=BYTES Reduce disk usage below specified size\n" @@ -340,6 +423,7 @@ static int parse_argv(int argc, char *argv[]) { { "unit", required_argument, NULL, 'u' }, { "user-unit", required_argument, NULL, ARG_USER_UNIT }, { "field", required_argument, NULL, 'F' }, + { "fields", no_argument, NULL, 'N' }, { "catalog", no_argument, NULL, 'x' }, { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, @@ -361,7 +445,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:F:xrM:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0) switch (c) { @@ -698,9 +782,14 @@ static int parse_argv(int argc, char *argv[]) { break; case 'F': + arg_action = ACTION_LIST_FIELDS; arg_field = optarg; break; + case 'N': + arg_action = ACTION_LIST_FIELD_NAMES; + break; + case 'x': arg_catalog = true; break; @@ -825,13 +914,12 @@ static int add_matches(sd_journal *j, char **args) { have_term = false; } else if (path_is_absolute(*i)) { - _cleanup_free_ char *p, *t = NULL, *t2 = NULL; + _cleanup_free_ char *p, *t = NULL, *t2 = NULL, *interpreter = NULL; const char *path; - _cleanup_free_ char *interpreter = NULL; struct stat st; p = canonicalize_file_name(*i); - path = p ? p : *i; + path = p ?: *i; if (lstat(path, &st) < 0) return log_error_errno(errno, "Couldn't stat file: %m"); @@ -845,34 +933,37 @@ static int add_matches(sd_journal *j, char **args) { return log_oom(); t = strappend("_COMM=", comm); + if (!t) + return log_oom(); /* Append _EXE only if the interpreter is not a link. Otherwise, it might be outdated often. */ - if (lstat(interpreter, &st) == 0 && - !S_ISLNK(st.st_mode)) { + if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) { t2 = strappend("_EXE=", interpreter); if (!t2) return log_oom(); } - } else + } else { t = strappend("_EXE=", path); - } else if (S_ISCHR(st.st_mode)) - (void) asprintf(&t, "_KERNEL_DEVICE=c%u:%u", major(st.st_rdev), minor(st.st_rdev)); - else if (S_ISBLK(st.st_mode)) - (void) asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev)); - else { + if (!t) + return log_oom(); + } + + r = sd_journal_add_match(j, t, 0); + + if (r >=0 && t2) + 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); + if (r < 0) + return r; + } else { log_error("File is neither a device node, nor regular file, nor executable: %s", *i); return -EINVAL; } - if (!t) - return log_oom(); - - r = sd_journal_add_match(j, t, 0); - if (t2) - r = sd_journal_add_match(j, t2, 0); have_term = true; - } else { r = sd_journal_add_match(j, *i, 0); have_term = true; @@ -1136,10 +1227,11 @@ static int add_boot(sd_journal *j) { const char *reason = (r == 0) ? "No such boot ID in journal" : strerror(-r); if (sd_id128_is_null(arg_boot_id)) - log_error("Failed to look up boot %+i: %s", arg_boot_offset, reason); + log_error("Data from the specified boot (%+i) is not available: %s", + arg_boot_offset, reason); else - log_error("Failed to look up boot ID "SD_ID128_FORMAT_STR"%+i: %s", - SD_ID128_FORMAT_VAL(arg_boot_id), arg_boot_offset, reason); + log_error("Data from the specified boot ("SD_ID128_FORMAT_STR") is not available: %s", + SD_ID128_FORMAT_VAL(arg_boot_id), reason); return r == 0 ? -ENODATA : r; } @@ -2002,6 +2094,8 @@ int main(int argc, char *argv[]) { case ACTION_DISK_USAGE: case ACTION_LIST_BOOTS: case ACTION_VACUUM: + case ACTION_LIST_FIELDS: + case ACTION_LIST_FIELD_NAMES: /* These ones require access to the journal files, continue below. */ break; @@ -2084,13 +2178,33 @@ int main(int argc, char *argv[]) { goto finish; } + case ACTION_LIST_FIELD_NAMES: { + const char *field; + + SD_JOURNAL_FOREACH_FIELD(j, field) { + printf("%s\n", field); + n_shown ++; + } + + r = 0; + goto finish; + } + case ACTION_SHOW: + case ACTION_LIST_FIELDS: break; default: assert_not_reached("Unknown action"); } + 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"); + r = 0; + goto finish; + } /* add_boot() must be called first! * It may need to seek the journal to find parent boot IDs. */ r = add_boot(j); @@ -2131,10 +2245,12 @@ int main(int argc, char *argv[]) { log_debug("Journal filter: %s", filter); } - if (arg_field) { + if (arg_action == ACTION_LIST_FIELDS) { const void *data; size_t size; + assert(arg_field); + r = sd_journal_set_data_threshold(j, 0); if (r < 0) { log_error_errno(r, "Failed to unset data size threshold: %m"); diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index cd5160154a..7a3aaf0cab 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -1292,6 +1292,12 @@ static int add_file(sd_journal *j, const char *prefix, const char *filename) { return 0; path = strjoina(prefix, "/", filename); + + if (!j->has_runtime_files && path_startswith(path, "/run/log/journal")) + j->has_runtime_files = true; + else if (!j->has_persistent_files && path_startswith(path, "/var/log/journal")) + j->has_persistent_files = true; + return add_any_file(j, path); } @@ -1332,6 +1338,13 @@ static void remove_file_real(sd_journal *j, JournalFile *f) { j->unique_file_lost = true; } + if (j->fields_file == f) { + j->fields_file = ordered_hashmap_next(j->files, j->fields_file->path); + j->fields_offset = 0; + if (!j->fields_file) + j->fields_file_lost = true; + } + journal_file_close(f); j->current_invalidate_counter ++; @@ -1800,6 +1813,7 @@ _public_ void sd_journal_close(sd_journal *j) { free(j->path); free(j->prefix); free(j->unique_field); + free(j->fields_buffer); free(j); } @@ -2506,24 +2520,20 @@ _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_ * traversed files. */ found = false; ORDERED_HASHMAP_FOREACH(of, j->files, i) { - Object *oo; - uint64_t op; - if (of == j->unique_file) break; - /* Skip this file it didn't have any fields - * indexed */ - if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && - le64toh(of->header->n_fields) <= 0) + /* Skip this file it didn't have any fields indexed */ + if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && le64toh(of->header->n_fields) <= 0) continue; - r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), &oo, &op); + r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), NULL, NULL); if (r < 0) return r; - - if (r > 0) + if (r > 0) { found = true; + break; + } } if (found) @@ -2546,6 +2556,154 @@ _public_ void sd_journal_restart_unique(sd_journal *j) { j->unique_file_lost = false; } +_public_ int sd_journal_enumerate_fields(sd_journal *j, const char **field) { + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(field, -EINVAL); + + if (!j->fields_file) { + if (j->fields_file_lost) + return 0; + + j->fields_file = ordered_hashmap_first(j->files); + if (!j->fields_file) + return 0; + + j->fields_hash_table_index = 0; + j->fields_offset = 0; + } + + for (;;) { + JournalFile *f, *of; + Iterator i; + uint64_t m; + Object *o; + size_t sz; + bool found; + + f = j->fields_file; + + if (j->fields_offset == 0) { + bool eof = false; + + /* We are not yet positioned at any field. Let's pick the first one */ + r = journal_file_map_field_hash_table(f); + if (r < 0) + return r; + + m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); + for (;;) { + if (j->fields_hash_table_index >= m) { + /* Reached the end of the hash table, go to the next file. */ + eof = true; + break; + } + + j->fields_offset = le64toh(f->field_hash_table[j->fields_hash_table_index].head_hash_offset); + + if (j->fields_offset != 0) + break; + + /* Empty hash table bucket, go to next one */ + j->fields_hash_table_index++; + } + + if (eof) { + /* Proceed with next file */ + j->fields_file = ordered_hashmap_next(j->files, f->path); + if (!j->fields_file) { + *field = NULL; + return 0; + } + + j->fields_offset = 0; + j->fields_hash_table_index = 0; + continue; + } + + } else { + /* We are already positioned at a field. If so, let's figure out the next field from it */ + + r = journal_file_move_to_object(f, OBJECT_FIELD, j->fields_offset, &o); + if (r < 0) + return r; + + j->fields_offset = le64toh(o->field.next_hash_offset); + if (j->fields_offset == 0) { + /* Reached the end of the hash table chain */ + j->fields_hash_table_index++; + continue; + } + } + + /* We use OBJECT_UNUSED here, so that the iteator below doesn't remove our mmap window */ + r = journal_file_move_to_object(f, OBJECT_UNUSED, j->fields_offset, &o); + if (r < 0) + return r; + + /* Because we used OBJECT_UNUSED above, we need to do our type check manually */ + if (o->object.type != OBJECT_FIELD) { + log_debug("%s:offset " OFSfmt ": object has type %i, expected %i", f->path, j->fields_offset, o->object.type, OBJECT_FIELD); + return -EBADMSG; + } + + sz = le64toh(o->object.size) - offsetof(Object, field.payload); + + /* Let's see if we already returned this field name before. */ + found = false; + ORDERED_HASHMAP_FOREACH(of, j->files, i) { + if (of == f) + break; + + /* Skip this file it didn't have any fields indexed */ + if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && le64toh(of->header->n_fields) <= 0) + continue; + + r = journal_file_find_field_object_with_hash(of, o->field.payload, sz, le64toh(o->field.hash), NULL, NULL); + if (r < 0) + return r; + if (r > 0) { + found = true; + break; + } + } + + if (found) + continue; + + /* Check if this is really a valid string containing no NUL byte */ + if (memchr(o->field.payload, 0, sz)) + return -EBADMSG; + + if (sz > j->data_threshold) + sz = j->data_threshold; + + if (!GREEDY_REALLOC(j->fields_buffer, j->fields_buffer_allocated, sz + 1)) + return -ENOMEM; + + memcpy(j->fields_buffer, o->field.payload, sz); + j->fields_buffer[sz] = 0; + + if (!field_is_valid(j->fields_buffer)) + return -EBADMSG; + + *field = j->fields_buffer; + return 1; + } +} + +_public_ void sd_journal_restart_fields(sd_journal *j) { + if (!j) + return; + + j->fields_file = NULL; + j->fields_hash_table_index = 0; + j->fields_offset = 0; + j->fields_file_lost = false; +} + _public_ int sd_journal_reliable_fd(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_pid_changed(j), -ECHILD); @@ -2630,3 +2788,15 @@ _public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) { *sz = j->data_threshold; return 0; } + +_public_ int sd_journal_has_runtime_files(sd_journal *j) { + assert_return(j, -EINVAL); + + return j->has_runtime_files; +} + +_public_ int sd_journal_has_persistent_files(sd_journal *j) { + assert_return(j, -EINVAL); + + return j->has_persistent_files; +} |