From 033135218df105bb1b79d78c56fe72fdb240a528 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:07 +0000 Subject: [PATCH 1/15] journal: Add --convert= command to journalctl --convert writes the journal files read by journalctl to the given location. The location should be specified as a full journal file path (e.g. /a/b/c/converted.journal). The directory specifies where the converted journal files will be stored. The filename specifies the naming convention the converted journal files will follow. --- diff --git a/man/journalctl.xml b/man/journalctl.xml index 9e784f3..6515ade 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -979,6 +979,17 @@ above. + + + + Converts the specified journal files to the latest supported journal format. Takes + the path to store the converted journal files. The path should include the filename to be used for + the converted files, with the .journal extension (e.g. + /a/b/c/converted.journal will store the journal files in the + /a/b/c directory using converted.journal as the filename). + + + diff --git a/meson.build b/meson.build index e07875a..741a9e6 100644 --- a/meson.build +++ b/meson.build @@ -2085,7 +2085,8 @@ public_programs += executable( 'journalctl', journalctl_sources, include_directories : includes, - link_with : [libshared], + link_with : [libjournal_core, + libshared], dependencies : [threads, libdl, libxz, diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 3c4a7c0..86af482 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -48,6 +48,7 @@ #include "locale-util.h" #include "log.h" #include "logs-show.h" +#include "managed-journal-file.h" #include "memory-util.h" #include "mkdir.h" #include "mount-util.h" @@ -130,6 +131,7 @@ 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 const char *arg_convert = NULL; #if HAVE_PCRE2 static const char *arg_pattern = NULL; static pcre2_code *arg_compiled_pattern = NULL; @@ -155,6 +157,7 @@ static enum { ACTION_ROTATE_AND_VACUUM, ACTION_LIST_FIELDS, ACTION_LIST_FIELD_NAMES, + ACTION_CONVERT, } arg_action = ACTION_SHOW; typedef struct BootId { @@ -402,6 +405,7 @@ static int help(void) { " --dump-catalog Show entries in the message catalog\n" " --update-catalog Update the message catalog database\n" " --setup-keys Generate a new FSS key pair\n" + " --convert=PATH Convert the journal to the latest journal format\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -456,6 +460,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_HOSTNAME, ARG_OUTPUT_FIELDS, ARG_NAMESPACE, + ARG_CONVERT, }; static const struct option options[] = { @@ -523,6 +528,7 @@ static int parse_argv(int argc, char *argv[]) { { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME }, { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, { "namespace", required_argument, NULL, ARG_NAMESPACE }, + { "convert", required_argument, NULL, ARG_CONVERT }, {} }; @@ -1050,6 +1056,11 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_CONVERT: + arg_action = ACTION_CONVERT; + arg_convert = optarg; + break; + case '?': return -EINVAL; @@ -2127,6 +2138,54 @@ static int wait_for_change(sd_journal *j, int poll_fd) { return 0; } +static int journal_convert(sd_journal *j) { + _cleanup_(managed_journal_file_closep) ManagedJournalFile *to = NULL; + _cleanup_(mmap_cache_unrefp) MMapCache *mmap = NULL; + int r; + + assert(arg_convert); + + mmap = mmap_cache_new(); + if (!mmap) + return -ENOMEM; + + r = managed_journal_file_open(-1, arg_convert, O_RDWR | O_CREAT, 0640, true, UINT64_MAX, false, + &(JournalMetrics) { -1, -1, -1, -1, -1, -1 }, mmap, NULL, NULL, &to); + if (r < 0) + return log_error_errno(r, "Failed to open journal: %m"); + + SD_JOURNAL_FOREACH(j) { + Object *o; + JournalFile *from; + + from = j->current_file; + assert(from && from->current_offset > 0); + + r = journal_file_move_to_object(from, OBJECT_ENTRY, from->current_offset, &o); + if (r < 0) + return log_error_errno(r, "Can't read entry: %m"); + + r = journal_file_copy_entry(from, to->file, o, from->current_offset); + if (r >= 0) + continue; + + if (!journal_shall_try_append_again(to->file, r)) + return log_error_errno(r, "Can't write entry: %m"); + + log_info("Rotating journal."); + + r = managed_journal_file_rotate(&to, mmap, true, UINT64_MAX, false, NULL); + if (r < 0) + return log_error_errno(r, "Failed to rotate %s: %m", to->file->path); + + r = journal_file_copy_entry(from, to->file, o, from->current_offset); + if (r < 0) + return log_error_errno(r, "Can't write entry: %m"); + } + + return 0; +} + int main(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; @@ -2238,6 +2297,7 @@ int main(int argc, char *argv[]) { case ACTION_ROTATE_AND_VACUUM: case ACTION_LIST_FIELDS: case ACTION_LIST_FIELD_NAMES: + case ACTION_CONVERT: /* These ones require access to the journal files, continue below. */ break; @@ -2392,6 +2452,10 @@ int main(int argc, char *argv[]) { case ACTION_LIST_FIELDS: break; + case ACTION_CONVERT: + r = journal_convert(j); + goto finish; + default: assert_not_reached(); } diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index a7858ee..c15bece 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -30,6 +30,7 @@ #include "io-util.h" #include "journal-authenticate.h" #include "journal-internal.h" +#include "journal-util.h" #include "journal-vacuum.h" #include "journald-audit.h" #include "journald-context.h" @@ -744,55 +745,6 @@ static void server_cache_hostname(Server *s) { free_and_replace(s->hostname_field, x); } -static bool shall_try_append_again(JournalFile *f, int r) { - switch(r) { - - case -E2BIG: /* Hit configured limit */ - case -EFBIG: /* Hit fs limit */ - case -EDQUOT: /* Quota limit hit */ - case -ENOSPC: /* Disk full */ - log_debug("%s: Allocation limit reached, rotating.", f->path); - return true; - - case -EIO: /* I/O error of some kind (mmap) */ - log_warning("%s: IO error, rotating.", f->path); - return true; - - case -EHOSTDOWN: /* Other machine */ - log_info("%s: Journal file from other machine, rotating.", f->path); - return true; - - case -EBUSY: /* Unclean shutdown */ - log_info("%s: Unclean shutdown, rotating.", f->path); - return true; - - case -EPROTONOSUPPORT: /* Unsupported feature */ - log_info("%s: Unsupported feature, rotating.", f->path); - return true; - - case -EBADMSG: /* Corrupted */ - case -ENODATA: /* Truncated */ - case -ESHUTDOWN: /* Already archived */ - log_warning("%s: Journal file corrupted, rotating.", f->path); - return true; - - case -EIDRM: /* Journal file has been deleted */ - log_warning("%s: Journal file has been deleted, rotating.", f->path); - return true; - - case -ETXTBSY: /* Journal file is from the future */ - log_warning("%s: Journal file is from the future, rotating.", f->path); - return true; - - case -EAFNOSUPPORT: - log_warning("%s: underlying file system does not support memory mapping or another required file system feature.", f->path); - return false; - - default: - return false; - } -} - static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, size_t n, int priority) { bool vacuumed = false, rotate = false; struct dual_timestamp ts; @@ -847,7 +799,7 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, size_t n return; } - if (vacuumed || !shall_try_append_again(f->file, r)) { + if (vacuumed || !journal_shall_try_append_again(f->file, r)) { log_error_errno(r, "Failed to write entry (%zu items, %zu bytes), ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n)); return; } @@ -1176,7 +1128,7 @@ int server_flush_to_var(Server *s, bool require_flag_file) { if (r >= 0) continue; - if (!shall_try_append_again(s->system_journal->file, r)) { + if (!journal_shall_try_append_again(s->system_journal->file, r)) { log_error_errno(r, "Can't write entry: %m"); goto finish; } diff --git a/src/shared/journal-util.c b/src/shared/journal-util.c index 9e1870e..656085b 100644 --- a/src/shared/journal-util.c +++ b/src/shared/journal-util.c @@ -137,3 +137,52 @@ int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_use return r; } + +bool journal_shall_try_append_again(JournalFile *f, int r) { + switch(r) { + + case -E2BIG: /* Hit configured limit */ + case -EFBIG: /* Hit fs limit */ + case -EDQUOT: /* Quota limit hit */ + case -ENOSPC: /* Disk full */ + log_debug("%s: Allocation limit reached, rotating.", f->path); + return true; + + case -EIO: /* I/O error of some kind (mmap) */ + log_warning("%s: IO error, rotating.", f->path); + return true; + + case -EHOSTDOWN: /* Other machine */ + log_info("%s: Journal file from other machine, rotating.", f->path); + return true; + + case -EBUSY: /* Unclean shutdown */ + log_info("%s: Unclean shutdown, rotating.", f->path); + return true; + + case -EPROTONOSUPPORT: /* Unsupported feature */ + log_info("%s: Unsupported feature, rotating.", f->path); + return true; + + case -EBADMSG: /* Corrupted */ + case -ENODATA: /* Truncated */ + case -ESHUTDOWN: /* Already archived */ + log_warning("%s: Journal file corrupted, rotating.", f->path); + return true; + + case -EIDRM: /* Journal file has been deleted */ + log_warning("%s: Journal file has been deleted, rotating.", f->path); + return true; + + case -ETXTBSY: /* Journal file is from the future */ + log_warning("%s: Journal file is from the future, rotating.", f->path); + return true; + + case -EAFNOSUPPORT: + log_warning("%s: underlying file system does not support memory mapping or another required file system feature.", f->path); + return false; + + default: + return false; + } +} diff --git a/src/shared/journal-util.h b/src/shared/journal-util.h index 86fcba0..54d5167 100644 --- a/src/shared/journal-util.h +++ b/src/shared/journal-util.h @@ -6,5 +6,9 @@ #include "sd-journal.h" +#include "journal-file.h" + int journal_access_blocked(sd_journal *j); int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_users); + +bool journal_shall_try_append_again(JournalFile *f, int r); From 82161b39734226e2ef9cf49232b2c2c856c83786 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 2/15] journal: Add compact mode This adds a new flag in preparation for incompatible journal changes which will be gated behind this flag. --- diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index d64c70c..5610d2f 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -153,19 +153,22 @@ enum { HEADER_INCOMPATIBLE_COMPRESSED_LZ4 = 1 << 1, HEADER_INCOMPATIBLE_KEYED_HASH = 1 << 2, HEADER_INCOMPATIBLE_COMPRESSED_ZSTD = 1 << 3, + HEADER_INCOMPATIBLE_COMPACT = 1 << 4, }; #define HEADER_INCOMPATIBLE_ANY \ (HEADER_INCOMPATIBLE_COMPRESSED_XZ | \ HEADER_INCOMPATIBLE_COMPRESSED_LZ4 | \ HEADER_INCOMPATIBLE_KEYED_HASH | \ - HEADER_INCOMPATIBLE_COMPRESSED_ZSTD) + HEADER_INCOMPATIBLE_COMPRESSED_ZSTD | \ + HEADER_INCOMPATIBLE_COMPACT) #define HEADER_INCOMPATIBLE_SUPPORTED \ ((HAVE_XZ ? HEADER_INCOMPATIBLE_COMPRESSED_XZ : 0) | \ (HAVE_LZ4 ? HEADER_INCOMPATIBLE_COMPRESSED_LZ4 : 0) | \ (HAVE_ZSTD ? HEADER_INCOMPATIBLE_COMPRESSED_ZSTD : 0) | \ - HEADER_INCOMPATIBLE_KEYED_HASH) + HEADER_INCOMPATIBLE_KEYED_HASH | \ + HEADER_INCOMPATIBLE_COMPACT) enum { HEADER_COMPATIBLE_SEALED = 1 << 0, diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index ac8ad14..02ab2d8 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -364,7 +364,7 @@ static bool warn_wrong_flags(const JournalFile *f, bool compatible) { f->path, type, flags & ~any); flags = (flags & any) & ~supported; if (flags) { - const char* strv[5]; + const char* strv[6]; size_t n = 0; _cleanup_free_ char *t = NULL; @@ -380,6 +380,8 @@ static bool warn_wrong_flags(const JournalFile *f, bool compatible) { strv[n++] = "zstd-compressed"; if (flags & HEADER_INCOMPATIBLE_KEYED_HASH) strv[n++] = "keyed-hash"; + if (flags & HEADER_INCOMPATIBLE_COMPACT) + strv[n++] = "compact"; } strv[n] = NULL; assert(n < ELEMENTSOF(strv)); @@ -3204,7 +3206,7 @@ void journal_file_print_header(JournalFile *f) { "Sequential number ID: %s\n" "State: %s\n" "Compatible flags:%s%s\n" - "Incompatible flags:%s%s%s%s%s\n" + "Incompatible flags:%s%s%s%s%s%s\n" "Header size: %"PRIu64"\n" "Arena size: %"PRIu64"\n" "Data hash table size: %"PRIu64"\n" @@ -3231,6 +3233,7 @@ void journal_file_print_header(JournalFile *f) { JOURNAL_HEADER_COMPRESSED_LZ4(f->header) ? " COMPRESSED-LZ4" : "", JOURNAL_HEADER_COMPRESSED_ZSTD(f->header) ? " COMPRESSED-ZSTD" : "", JOURNAL_HEADER_KEYED_HASH(f->header) ? " KEYED-HASH" : "", + JOURNAL_HEADER_COMPACT(f->header) ? " COMPACT" : "", (le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_ANY) ? " ???" : "", le64toh(f->header->header_size), le64toh(f->header->arena_size), diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 59509de..eab3418 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -184,6 +184,9 @@ static inline bool VALID_EPOCH(uint64_t u) { #define JOURNAL_HEADER_KEYED_HASH(h) \ FLAGS_SET(le32toh((h)->incompatible_flags), HEADER_INCOMPATIBLE_KEYED_HASH) +#define JOURNAL_HEADER_COMPACT(h) \ + FLAGS_SET(le32toh((h)->incompatible_flags), HEADER_INCOMPATIBLE_COMPACT) + int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset, Object **ret); int journal_file_read_object_header(JournalFile *f, ObjectType type, uint64_t offset, Object *ret); From 8872245b14148ba1b7d8b113c2393b4243bcd3f3 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 3/15] journal: Enable compact mode We also add an environment variable $SYSTEMD_JOURNAL_COMPACT that can be used to disable compact mode if needed (similar to $SYSTEMD_JOURNAL_KEYED_HASH). --- diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 02ab2d8..cfaf351 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -299,7 +299,8 @@ static int journal_file_init_header(JournalFile *f, JournalFile *template) { f->compress_xz * HEADER_INCOMPATIBLE_COMPRESSED_XZ | f->compress_lz4 * HEADER_INCOMPATIBLE_COMPRESSED_LZ4 | f->compress_zstd * HEADER_INCOMPATIBLE_COMPRESSED_ZSTD | - f->keyed_hash * HEADER_INCOMPATIBLE_KEYED_HASH); + f->keyed_hash * HEADER_INCOMPATIBLE_KEYED_HASH | + f->compact * HEADER_INCOMPATIBLE_COMPACT); h.compatible_flags = htole32( f->seal * HEADER_COMPATIBLE_SEALED); @@ -3378,6 +3379,14 @@ int journal_file_open( } else f->keyed_hash = r; + r = getenv_bool("SYSTEMD_JOURNAL_COMPACT"); + if (r < 0) { + if (r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_COMPACT environment variable, ignoring: %m"); + f->compact = true; + } else + f->compact = r; + if (DEBUG_LOGGING) { static int last_seal = -1, last_compress = -1, last_keyed_hash = -1; static uint64_t last_bytes = UINT64_MAX; diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index eab3418..0bb4774 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -71,6 +71,7 @@ typedef struct JournalFile { bool close_fd:1; bool archive:1; bool keyed_hash:1; + bool compact:1; direction_t last_direction; LocationType location_type; From 68c97d3d425d647239517c0cf4a12ba37597916b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 4/15] journal: Don't allocate objects above UINT32_MAX in compact mode To allow storing offsets as 32-bit, we should never allocate objects outside of the 32-bit range. --- diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index cfaf351..21c2f81 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -556,6 +556,10 @@ static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) if (f->metrics.max_size > 0 && new_size > f->metrics.max_size) return -E2BIG; + /* Refuse to go over 4G in compact mode so offsets can be stored in 32-bit. */ + if (JOURNAL_HEADER_COMPACT(f->header) && offset + size > UINT32_MAX) + return -E2BIG; + if (new_size > f->metrics.min_size && f->metrics.keep_free > 0) { struct statvfs svfs; From f707a143e0eb7439240a97310c43df8d2ed8490b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 5/15] ci: Use clang 11 for ASAN/UBSAN runs clang 10 UBSAN triggers false positives when using GCC zero sized arrays in unions. To avoid these false positives, let's use clang 11 in CI when running with sanitizers. Example stacktrace of false positive: ../src/libsystemd/sd-journal/journal-file.c:2270:60: runtime error: index 773 out of bounds for type 'le64_t [0]' \#0 0x7f7b53807463 in journal_file_entry_array_item /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/journal-file.c:2270:60 \#1 0x7f7b53812090 in generic_array_get /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/journal-file.c:2982:29 \#2 0x7f7b53813028 in generic_array_get_plus_one /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/journal-file.c:3037:16 \#3 0x7f7b53812a13 in journal_file_next_entry_for_data /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/journal-file.c:3713:21 \#4 0x7f7b5387d7a3 in find_location_for_match /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c \#5 0x7f7b5387d18e in find_location_for_match /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c:653:29 \#6 0x7f7b5387d3fe in find_location_for_match /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c:691:29 \#7 0x7f7b5387d18e in find_location_for_match /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c:653:29 \#8 0x7f7b5387d3fe in find_location_for_match /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c:691:29 \#9 0x7f7b5387a3b2 in find_location_with_matches /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c:736:24 \#10 0x7f7b5387947f in next_beyond_location /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c:798:21 \#11 0x7f7b53863005 in real_journal_next /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c:854:21 \#12 0x7f7b538634da in sd_journal_previous /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/sd-journal.c:897:16 \#13 0x4bc39c in main /home/runner/work/systemd/systemd/build/../src/libsystemd/sd-journal/test-journal-enum.c:23:9 \#14 0x7f7b529be0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) \#15 0x41b32d in _start (/home/runner/work/systemd/systemd/build/test-journal-enum+0x41b32d) --- diff --git a/.github/workflows/unit_tests.sh b/.github/workflows/unit_tests.sh index 9c7beb6..9ff1fb9 100755 --- a/.github/workflows/unit_tests.sh +++ b/.github/workflows/unit_tests.sh @@ -57,8 +57,10 @@ for phase in "${PHASES[@]}"; do MESON_ARGS=(--optimization=1) if [[ "$phase" = "RUN_CLANG_ASAN_UBSAN" ]]; then - export CC=clang - export CXX=clang++ + # Explicitly use clang-11, since with the default clang-10 + # we might trigger some UBSan false-positives. See https://github.com/systemd/systemd/pull/21183 + export CC=clang-11 + export CXX=clang++-11 # Build fuzzer regression tests only with clang (for now), # see: https://github.com/systemd/systemd/pull/15886#issuecomment-632689604 # -Db_lundef=false: See https://github.com/mesonbuild/meson/issues/764 From 70fd99107fc998883bb6717d3bd486c1a376447a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 6/15] journal: Use 32-bit entry array offsets in compact mode Before: OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 3610336 595.7M Field 5310 285.2K Entry 3498326 1.2G Data Hash Table 29 103.1M Field Hash Table 29 151.3K Entry Array 605991 1011.6M Tag 0 0B Total 7720021 2.9G After: OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 3562667 591.0M Field 3971 213.6K Entry 3498566 1.2G Data Hash Table 20 71.1M Field Hash Table 20 104.3K Entry Array 582647 505.0M Tag 0 0B Total 7647891 2.4G --- diff --git a/src/journal/managed-journal-file.c b/src/journal/managed-journal-file.c index 657cf5e..5056471 100644 --- a/src/journal/managed-journal-file.c +++ b/src/journal/managed-journal-file.c @@ -50,7 +50,7 @@ static int managed_journal_file_entry_array_punch_hole(JournalFile *f, uint64_t if (r < 0) return r; - n_items += journal_file_entry_array_n_items(&o); + n_items += journal_file_entry_array_n_items(f, &o); p = q; } @@ -66,8 +66,8 @@ static int managed_journal_file_entry_array_punch_hole(JournalFile *f, uint64_t if (n_unused == 0) return 0; - offset = p + offsetof(Object, entry_array.items) + - (journal_file_entry_array_n_items(&o) - n_unused) * sizeof(le64_t); + offset = p + journal_file_entry_array_items_offset(f) + + (journal_file_entry_array_n_items(f, &o) - n_unused) * journal_file_entry_array_item_size(f); sz = p + le64toh(o.object.size) - offset; if (sz < MINIMUM_HOLE_SIZE) @@ -78,10 +78,10 @@ static int managed_journal_file_entry_array_punch_hole(JournalFile *f, uint64_t o.object.size = htole64(offset - p); - n = pwrite(f->fd, &o, sizeof(EntryArrayObject), p); + n = pwrite(f->fd, &o, journal_file_entry_array_items_offset(f), p); if (n < 0) return log_debug_errno(errno, "Failed to modify entry array object size: %m"); - if ((size_t) n != sizeof(EntryArrayObject)) + if ((size_t) n != journal_file_entry_array_items_offset(f)) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short pwrite() while modifying entry array object size."); f->header->arena_size = htole64(ALIGN64(offset) - le64toh(f->header->header_size)); diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index 5610d2f..07df844 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -118,7 +118,10 @@ struct HashTableObject { struct EntryArrayObject { ObjectHeader object; le64_t next_entry_array_offset; - le64_t items[]; + union { + le64_t items[0]; + le32_t compact[0]; + }; } _packed_; #define TAG_LENGTH (256/8) diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 21c2f81..7414a88 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -763,9 +763,9 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) uint64_t sz; sz = le64toh(READ_NOW(o->object.size)); - if (sz < offsetof(Object, entry_array.items) || - (sz - offsetof(Object, entry_array.items)) % sizeof(le64_t) != 0 || - (sz - offsetof(Object, entry_array.items)) / sizeof(le64_t) <= 0) + if (sz < journal_file_entry_array_items_offset(f) || + (sz - journal_file_entry_array_items_offset(f)) % journal_file_entry_array_item_size(f) != 0 || + (sz - journal_file_entry_array_items_offset(f)) / journal_file_entry_array_item_size(f) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid object entry array size: %" PRIu64 ": %" PRIu64, sz, @@ -1654,7 +1654,7 @@ uint64_t journal_file_entry_n_items(Object *o) { return (sz - offsetof(Object, entry.items)) / sizeof(EntryItem); } -uint64_t journal_file_entry_array_n_items(Object *o) { +uint64_t journal_file_entry_array_n_items(JournalFile *f, Object *o) { uint64_t sz; assert(o); @@ -1666,7 +1666,15 @@ uint64_t journal_file_entry_array_n_items(Object *o) { if (sz < offsetof(Object, entry_array.items)) return 0; - return (sz - offsetof(Object, entry_array.items)) / sizeof(uint64_t); + return (sz - journal_file_entry_array_items_offset(f)) / journal_file_entry_array_item_size(f); +} + +uint64_t journal_file_entry_array_item(JournalFile *f, Object *o, size_t i) { + assert(o); + assert(o->object.type == OBJECT_ENTRY_ARRAY); + + return JOURNAL_HEADER_COMPACT(f->header) ? (uint64_t) le32toh(o->entry_array.compact[i]) : + le64toh(o->entry_array.items[i]); } uint64_t journal_file_hash_table_n_items(Object *o) { @@ -1684,6 +1692,16 @@ uint64_t journal_file_hash_table_n_items(Object *o) { return (sz - offsetof(Object, hash_table.items)) / sizeof(HashItem); } +static void write_entry_array_item(JournalFile *f, Object *o, uint64_t i, uint64_t p) { + assert(f); + assert(o); + + if (JOURNAL_HEADER_COMPACT(f->header)) + o->entry_array.compact[i] = htole32(p); + else + o->entry_array.items[i] = htole64(p); +} + static int link_entry_into_array(JournalFile *f, le64_t *first, le64_t *idx, @@ -1706,9 +1724,9 @@ static int link_entry_into_array(JournalFile *f, if (r < 0) return r; - n = journal_file_entry_array_n_items(o); + n = journal_file_entry_array_n_items(f, o); if (i < n) { - o->entry_array.items[i] = htole64(p); + write_entry_array_item(f, o, i, p); *idx = htole64(hidx + 1); return 0; } @@ -1727,7 +1745,7 @@ static int link_entry_into_array(JournalFile *f, n = 4; r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY, - offsetof(Object, entry_array.items) + n * sizeof(uint64_t), + journal_file_entry_array_items_offset(f) + n * journal_file_entry_array_item_size(f), &o, &q); if (r < 0) return r; @@ -1738,7 +1756,7 @@ static int link_entry_into_array(JournalFile *f, return r; #endif - o->entry_array.items[i] = htole64(p); + write_entry_array_item(f, o, i, p); if (ap == 0) *first = htole64(q); @@ -2252,7 +2270,7 @@ static int generic_array_get( if (r < 0) return r; - k = journal_file_entry_array_n_items(o); + k = journal_file_entry_array_n_items(f, o); if (i < k) break; @@ -2272,7 +2290,7 @@ static int generic_array_get( if (r < 0) return r; - k = journal_file_entry_array_n_items(o); + k = journal_file_entry_array_n_items(f, o); if (k == 0) break; @@ -2280,12 +2298,12 @@ static int generic_array_get( } do { - p = le64toh(o->entry_array.items[i]); + p = journal_file_entry_array_item(f, o, i); r = journal_file_move_to_object(f, OBJECT_ENTRY, p, ret); if (r >= 0) { /* Let's cache this item for the next invocation */ - chain_cache_put(f->chain_cache, ci, first, a, le64toh(o->entry_array.items[0]), t, i); + chain_cache_put(f->chain_cache, ci, first, a, journal_file_entry_array_item(f, o, 0), t, i); if (ret_offset) *ret_offset = p; @@ -2400,13 +2418,13 @@ static int generic_array_bisect( if (r < 0) return r; - k = journal_file_entry_array_n_items(array); + k = journal_file_entry_array_n_items(f, array); right = MIN(k, n); if (right <= 0) return 0; i = right - 1; - lp = p = le64toh(array->entry_array.items[i]); + lp = p = journal_file_entry_array_item(f, array, i); if (p <= 0) r = -EBADMSG; else @@ -2439,7 +2457,7 @@ static int generic_array_bisect( if (last_index > 0) { uint64_t x = last_index - 1; - p = le64toh(array->entry_array.items[x]); + p = journal_file_entry_array_item(f, array, x); if (p <= 0) return -EBADMSG; @@ -2459,7 +2477,7 @@ static int generic_array_bisect( if (last_index < right) { uint64_t y = last_index + 1; - p = le64toh(array->entry_array.items[y]); + p = journal_file_entry_array_item(f, array, y); if (p <= 0) return -EBADMSG; @@ -2489,7 +2507,7 @@ static int generic_array_bisect( assert(left < right); i = (left + right) / 2; - p = le64toh(array->entry_array.items[i]); + p = journal_file_entry_array_item(f, array, i); if (p <= 0) r = -EBADMSG; else @@ -2537,14 +2555,14 @@ found: return 0; /* Let's cache this item for the next invocation */ - chain_cache_put(f->chain_cache, ci, first, a, le64toh(array->entry_array.items[0]), t, subtract_one ? (i > 0 ? i-1 : UINT64_MAX) : i); + chain_cache_put(f->chain_cache, ci, first, a, journal_file_entry_array_item(f, array, 0), t, subtract_one ? (i > 0 ? i-1 : UINT64_MAX) : i); if (subtract_one && i == 0) p = last_p; else if (subtract_one) - p = le64toh(array->entry_array.items[i-1]); + p = journal_file_entry_array_item(f, array, i - 1); else - p = le64toh(array->entry_array.items[i]); + p = journal_file_entry_array_item(f, array, i); if (ret) { r = journal_file_move_to_object(f, OBJECT_ENTRY, p, ret); diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 0bb4774..31e06b5 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -195,7 +195,8 @@ int journal_file_tail_end_by_pread(JournalFile *f, uint64_t *ret_offset); int journal_file_tail_end_by_mmap(JournalFile *f, uint64_t *ret_offset); uint64_t journal_file_entry_n_items(Object *o) _pure_; -uint64_t journal_file_entry_array_n_items(Object *o) _pure_; +uint64_t journal_file_entry_array_n_items(JournalFile *f, Object *o) _pure_; +uint64_t journal_file_entry_array_item(JournalFile *f, Object *o, size_t i) _pure_; uint64_t journal_file_hash_table_n_items(Object *o) _pure_; int journal_file_append_object(JournalFile *f, ObjectType type, uint64_t size, Object **ret, uint64_t *ret_offset); @@ -208,6 +209,14 @@ int journal_file_append_entry( Object **ret, uint64_t *ret_offset); +static inline size_t journal_file_entry_array_items_offset(JournalFile *f) { + return JOURNAL_HEADER_COMPACT(f->header) ? offsetof(Object, entry_array.compact) : offsetof(Object, entry_array.items); +} + +static inline size_t journal_file_entry_array_item_size(JournalFile *f) { + return JOURNAL_HEADER_COMPACT(f->header) ? sizeof(le32_t) : sizeof(le64_t); +} + int journal_file_find_data_object(JournalFile *f, const void *data, uint64_t size, Object **ret, uint64_t *ret_offset); int journal_file_find_data_object_with_hash(JournalFile *f, const void *data, uint64_t size, uint64_t hash, Object **ret, uint64_t *ret_offset); diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index 56eaecb..f51fb15 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -332,8 +332,8 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o break; case OBJECT_ENTRY_ARRAY: - if ((le64toh(o->object.size) - offsetof(Object, entry_array.items)) % sizeof(le64_t) != 0 || - (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(le64_t) <= 0) { + if ((le64toh(o->object.size) - journal_file_entry_array_items_offset(f)) % journal_file_entry_array_item_size(f) != 0 || + (le64toh(o->object.size) - journal_file_entry_array_items_offset(f)) / journal_file_entry_array_item_size(f) <= 0) { error(offset, "Invalid object entry array size: %"PRIu64, le64toh(o->object.size)); @@ -347,15 +347,15 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o return -EBADMSG; } - for (uint64_t i = 0; i < journal_file_entry_array_n_items(o); i++) - if (le64toh(o->entry_array.items[i]) != 0 && - !VALID64(le64toh(o->entry_array.items[i]))) { + for (uint64_t i = 0; i < journal_file_entry_array_n_items(f, o); i++) { + uint64_t q = journal_file_entry_array_item(f, o, i); + if (q != 0 && !VALID64(q)) { error(offset, "Invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt, - i, journal_file_entry_array_n_items(o), - le64toh(o->entry_array.items[i])); + i, journal_file_entry_array_n_items(f, o), q); return -EBADMSG; } + } break; @@ -487,10 +487,10 @@ static int verify_data( return -EBADMSG; } - m = journal_file_entry_array_n_items(o); + m = journal_file_entry_array_n_items(f, o); for (j = 0; i < n && j < m; i++, j++) { - q = le64toh(o->entry_array.items[j]); + q = journal_file_entry_array_item(f, o, j); if (q <= last) { error(p, "Data object's entry array not sorted (%"PRIu64" <= %"PRIu64")", q, last); return -EBADMSG; @@ -729,11 +729,11 @@ static int verify_entry_array( return -EBADMSG; } - m = journal_file_entry_array_n_items(o); + m = journal_file_entry_array_n_items(f, o); for (j = 0; i < n && j < m; i++, j++) { uint64_t p; - p = le64toh(o->entry_array.items[j]); + p = journal_file_entry_array_item(f, o, j); if (p <= last) { error(a, "Entry array not sorted at %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; From 5f88df90ffb3ea524b6513ada4dd19344b52fa1e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 7/15] journal: Introduce EntryItemEx In later commits, we need to attach extra information to EntryItem. To allow doing so, introduce a new struct EntryItemEx that we can safely modify without modifying the journal format. We'll add more fields to EntryItemEx in later commits. --- diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index 07df844..75c7a5e 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -91,6 +91,12 @@ struct EntryItem { le64_t hash; } _packed_; +/* Extended version of EntryItem that stores extra information that we don't store in the journal file. */ +typedef struct { + uint64_t object_offset; + uint64_t hash; +} EntryItemEx; + #define EntryObject__contents { \ ObjectHeader object; \ le64_t seqnum; \ diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 7414a88..8ec09cd 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1809,15 +1809,13 @@ static int link_entry_into_array_plus_one(JournalFile *f, return 0; } -static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) { - uint64_t p; +static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t p) { int r; assert(f); assert(o); assert(offset > 0); - p = le64toh(o->entry.items[i].object_offset); r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); if (r < 0) return r; @@ -1829,8 +1827,8 @@ static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offs offset); } -static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) { - uint64_t n; +static int journal_file_link_entry( + JournalFile *f, Object *o, uint64_t offset, const EntryItemEx items[], size_t n_items) { int r; assert(f); @@ -1860,15 +1858,14 @@ static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) { f->header->tail_entry_monotonic = o->entry.monotonic; /* Link up the items */ - n = journal_file_entry_n_items(o); - for (uint64_t i = 0; i < n; i++) { + for (uint64_t i = 0; i < n_items; i++) { int k; /* If we fail to link an entry item because we can't allocate a new entry array, don't fail * immediately but try to link the other entry items since it might still be possible to link * those if they don't require a new entry array to be allocated. */ - k = journal_file_link_entry_item(f, o, offset, i); + k = journal_file_link_entry_item(f, o, offset, items[i].object_offset); if (k == -E2BIG) r = k; else if (k < 0) @@ -1883,7 +1880,7 @@ static int journal_file_append_entry_internal( const dual_timestamp *ts, const sd_id128_t *boot_id, uint64_t xor_hash, - const EntryItem items[], unsigned n_items, + const EntryItemEx items[], size_t n_items, uint64_t *seqnum, Object **ret, uint64_t *ret_offset) { uint64_t np; @@ -1903,7 +1900,9 @@ static int journal_file_append_entry_internal( return r; o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum)); - memcpy_safe(o->entry.items, items, n_items * sizeof(EntryItem)); + for (size_t i = 0; i < n_items; i++) + o->entry.items[i] = (EntryItem){ .object_offset = htole64(items[i].object_offset), + .hash = htole64(items[i].hash) }; o->entry.realtime = htole64(ts->realtime); o->entry.monotonic = htole64(ts->monotonic); o->entry.xor_hash = htole64(xor_hash); @@ -1917,7 +1916,7 @@ static int journal_file_append_entry_internal( return r; #endif - r = journal_file_link_entry(f, o, np); + r = journal_file_link_entry(f, o, np, items, n_items); if (r < 0) return r; @@ -2012,13 +2011,11 @@ int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t) return r; } -static int entry_item_cmp(const EntryItem *a, const EntryItem *b) { - return CMP(le64toh(a->object_offset), le64toh(b->object_offset)); +static int entry_item_cmp(const EntryItemEx *a, const EntryItemEx *b) { + return CMP(a->object_offset, b->object_offset); } -static size_t remove_duplicate_entry_items(EntryItem items[], size_t n) { - - /* This function relies on the items array being sorted. */ +static size_t remove_duplicate_entry_items(EntryItemEx items[], size_t n) { size_t j = 1; if (n <= 1) @@ -2039,7 +2036,7 @@ int journal_file_append_entry( uint64_t *seqnum, Object **ret, uint64_t *ret_offset) { - EntryItem *items; + EntryItemEx *items; int r; uint64_t xor_hash = 0; struct dual_timestamp _ts; @@ -2068,7 +2065,7 @@ int journal_file_append_entry( return r; #endif - items = newa(EntryItem, n_iovec); + items = newa(EntryItemEx, n_iovec); for (size_t i = 0; i < n_iovec; i++) { uint64_t p; @@ -2092,9 +2089,9 @@ int journal_file_append_entry( else xor_hash ^= le64toh(o->data.hash); - items[i] = (EntryItem) { - .object_offset = htole64(p), - .hash = o->data.hash, + items[i] = (EntryItemEx) { + .object_offset = p, + .hash = le64toh(o->data.hash), }; } @@ -3684,7 +3681,7 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 uint64_t q, n, xor_hash = 0; const sd_id128_t *boot_id; dual_timestamp ts; - EntryItem *items; + EntryItemEx *items; int r; assert(from); @@ -3702,7 +3699,7 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 boot_id = &o->entry.boot_id; n = journal_file_entry_n_items(o); - items = newa(EntryItem, n); + items = newa(EntryItemEx, n); for (uint64_t i = 0; i < n; i++) { uint64_t l, h; @@ -3759,9 +3756,9 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 else xor_hash ^= le64toh(u->data.hash); - items[i] = (EntryItem) { - .object_offset = htole64(h), - .hash = u->data.hash, + items[i] = (EntryItemEx) { + .object_offset = h, + .hash = le64toh(u->data.hash), }; r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o); From 8e04ede130c82a4d2cfdf24d2707f3fb38a1939e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 8/15] journal: Introduce journal_file_entry_item_next() and journal_file_data_payload() journal_file_entry_item_next() abstracts iterating over the entry items of an entry object. It will allow us to introduce trie storage for entry items and opt-in field indexing in later commits without having to make large changes across the sd-journal codebase. Instead, most of the read path changes will be localized to journal_file_entry_next(). journal_file_data_payload() retrieves the payload of a Data object, optionally decompressing it and checking to see if matches a given field. This function replaces all the decompression code in the sd-journal codebase with a single function. The trie commit will also make use of it and the opt-in field indexing commit will make use of the maybe_decompress_payload() helper function. This commit should not introduce any changes in sd-journal behavior. --- diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 8ec09cd..d7c2915 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1353,7 +1353,7 @@ int journal_file_find_data_object_with_hash( const void *data, uint64_t size, uint64_t hash, Object **ret, uint64_t *ret_offset) { - uint64_t p, osize, h, m, depth = 0; + uint64_t p, h, m, depth = 0; int r; assert(f); @@ -1369,8 +1369,6 @@ int journal_file_find_data_object_with_hash( if (r < 0) return r; - osize = offsetof(Object, data.payload) + size; - m = le64toh(READ_NOW(f->header->data_hash_table_size)) / sizeof(HashItem); if (m <= 0) return -EBADMSG; @@ -1380,6 +1378,8 @@ int journal_file_find_data_object_with_hash( while (p > 0) { Object *o; + void *d; + size_t rsize; r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); if (r < 0) @@ -1388,40 +1388,13 @@ int journal_file_find_data_object_with_hash( if (le64toh(o->data.hash) != hash) goto next; - if (o->object.flags & OBJECT_COMPRESSION_MASK) { -#if HAVE_COMPRESSION - uint64_t l; - size_t rsize = 0; - - l = le64toh(READ_NOW(o->object.size)); - if (l <= offsetof(Object, data.payload)) - return -EBADMSG; - - l -= offsetof(Object, data.payload); - - r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK, - o->data.payload, l, &f->compress_buffer, &rsize, 0); - if (r < 0) - return r; - - if (rsize == size && - memcmp(f->compress_buffer, data, size) == 0) { - - if (ret) - *ret = o; - - if (ret_offset) - *ret_offset = p; - - return 1; - } -#else - return -EPROTONOSUPPORT; -#endif - } else if (le64toh(o->object.size) == osize && - memcmp(o->data.payload, data, size) == 0) { + r = journal_file_data_payload(f, o, p, NULL, 0, 0, &d, &rsize); + if (r < 0) + return r; + assert(r > 0); /* journal_file_data_payload() always returns > 0 if no field is provided. */ - if (ret) + if (memcmp_nn(data, size, d, rsize) == 0) { + if (ret) *ret = o; if (ret_offset) @@ -1640,18 +1613,187 @@ static int journal_file_append_data( return 0; } -uint64_t journal_file_entry_n_items(Object *o) { - uint64_t sz; - assert(o); +static int maybe_decompress_payload( + JournalFile *f, + uint8_t *payload, + uint64_t size, + int compression, + const char *field, + size_t field_length, + size_t data_threshold, + void **ret_data, + size_t *ret_size) { - if (o->object.type != OBJECT_ENTRY) - return 0; + int r; - sz = le64toh(READ_NOW(o->object.size)); + /* We can't read objects larger than 4G on a 32bit machine */ + if ((uint64_t) (size_t) size != size) + return -E2BIG; + + if (compression != 0) { +#if HAVE_COMPRESSION + size_t rsize; + + if (field) { + r = decompress_startswith( + compression, payload, size, &f->compress_buffer, field, field_length, '='); + if (r < 0) + return log_debug_errno( + r, + "Cannot decompress %s object of length %" PRIu64 ": %m", + object_compressed_to_string(compression), + size); + if (r == 0) + return 0; + } + + r = decompress_blob(compression, payload, size, &f->compress_buffer, &rsize, 0); + if (r < 0) + return r; + + if (ret_data) + *ret_data = f->compress_buffer; + if (ret_size) + *ret_size = rsize; +#else + return -EPROTONOSUPPORT; +#endif + } else { + if (field && (size < field_length + 1 || memcmp(payload, field, field_length) != 0 || payload[field_length] != '=')) + return 0; + + if (ret_data) + *ret_data = payload; + if (ret_size) + *ret_size = (size_t) size; + } + + return 1; +} + +int journal_file_data_payload( + JournalFile *f, + Object *o, + uint64_t offset, + const char *field, + size_t field_length, + size_t data_threshold, + void **ret_data, + size_t *ret_size) { + + uint64_t size; + int r; + + assert(!field == (field_length == 0)); /* These must be specified together. */ + + /* If the caller doesn't provide a field or any of the output arguments, let's short-circuit the + * execution of this function. */ + if (!field && !ret_data && !ret_size) + return 1; + + if (!o) { + r = journal_file_move_to_object(f, OBJECT_DATA, offset, &o); + if (r < 0) + return r; + } + + size = le64toh(READ_NOW(o->object.size)); + if (size < offsetof(Object, data.payload)) + return -EBADMSG; + + size -= offsetof(Object, data.payload); + + return maybe_decompress_payload( + f, + o->data.payload, + size, + o->object.flags & OBJECT_COMPRESSION_MASK, + field, + field_length, + data_threshold, + ret_data, + ret_size); +} + +int journal_file_entry_item_next( + JournalFile *f, + Object *e, + uint64_t offset, + uint64_t *i, + const char *field, + size_t field_length, + size_t data_threshold, + uint64_t *ret_offset, + void **ret_data, + size_t *ret_size) { + + /* Iterates over the entry items of the given entry. The output parameters return data about the Data + * object pointed at by the next entry item if requested. + * + * - If `ret_offset` is not NULL, it is set to the offset of the Data object + * - If `ret_data` is not NULL, it is set to a pointer to the decompressed payload of the Data object + * - If `ret_size` is not NULL, it is set to the size of the decompressed payload of the Data object + * + * The iterator is stored in `i`. To start iterating from the start of the entry items, set `i` to + * zero. It is automatically updated by this function and should not be touched again unless you want + * to restart iterating over the entry items. + * + * If `field` and `field_length` are given, this function keeps iterating until it finds an entry + * item whose Data object payload starts with the given field, followed by the '=' character. + * + * If `data_threshold` is larger than zero, the decompressed payload is limited to `data_threshold` + * amount of bytes. + * + * This function returns a positive number if it succesfully managed to find the next entry item. If + * no more entry items were available, or none of the remaining entry items were of the given field, + * it returns zero. If an error occurred, it returns a negative errno value. + */ + + uint64_t p, sz; + int r; + + assert(!e || e->object.type == OBJECT_ENTRY); + assert(offset); + assert(i); + assert(!field == (field_length == 0)); /* These must be specified together. */ + + if (!e) { + r = journal_file_move_to_object(f, OBJECT_ENTRY, offset, &e); + if (r < 0) + return r; + } + + sz = le64toh(READ_NOW(e->object.size)); if (sz < offsetof(Object, entry.items)) - return 0; + return -EBADMSG; + + for (p = *i; p < (sz - offsetof(Object, entry.items)) / sizeof(EntryItem); p++) { + uint64_t q; + + q = le64toh(e->entry.items[p].object_offset); + + r = journal_file_data_payload( + f, NULL, q, field, field_length, data_threshold, ret_data, ret_size); + if (IN_SET(r, -EADDRNOTAVAIL, -EBADMSG)) { + log_debug_errno(r, "Entry item %"PRIu64" data object is bad, skipping over it: %m", q); + continue; + } + if (r < 0) + return r; + if (r == 0) + continue; + + if (ret_offset) + *ret_offset = q; - return (sz - offsetof(Object, entry.items)) / sizeof(EntryItem); + *i = ++p; + + return 1; + } + + *i = p; + + return 0; } uint64_t journal_file_entry_array_n_items(JournalFile *f, Object *o) { @@ -3678,7 +3820,7 @@ int journal_file_dispose(int dir_fd, const char *fname) { } int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p) { - uint64_t q, n, xor_hash = 0; + size_t n = 0, xor_hash = 0; const sd_id128_t *boot_id; dual_timestamp ts; EntryItemEx *items; @@ -3698,51 +3840,29 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 }; boot_id = &o->entry.boot_id; - n = journal_file_entry_n_items(o); + for (uint64_t i = 0;;) { + r = journal_file_entry_item_next(from, o, p, &i, NULL, 0, 0, NULL, NULL, NULL); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + items = newa(EntryItemEx, n); - for (uint64_t i = 0; i < n; i++) { - uint64_t l, h; - size_t t; + for (uint64_t i = 0, j = 0;; j++) { + uint64_t h; void *data; + size_t l; Object *u; - q = le64toh(o->entry.items[i].object_offset); - - r = journal_file_move_to_object(from, OBJECT_DATA, q, &o); + r = journal_file_entry_item_next(from, o, p, &i, NULL, 0, 0, NULL, &data, &l); if (r < 0) return r; - - l = le64toh(READ_NOW(o->object.size)); - if (l < offsetof(Object, data.payload)) - return -EBADMSG; - - l -= offsetof(Object, data.payload); - t = (size_t) l; - - /* We hit the limit on 32bit machines */ - if ((uint64_t) t != l) - return -E2BIG; - - if (o->object.flags & OBJECT_COMPRESSION_MASK) { -#if HAVE_COMPRESSION - size_t rsize = 0; - - r = decompress_blob( - o->object.flags & OBJECT_COMPRESSION_MASK, - o->data.payload, l, - &from->compress_buffer, &rsize, - 0); - if (r < 0) - return r; - - data = from->compress_buffer; - l = rsize; -#else - return -EPROTONOSUPPORT; -#endif - } else - data = o->data.payload; + if (r == 0) + break; if (l == 0) return -EBADMSG; @@ -3756,14 +3876,10 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 else xor_hash ^= le64toh(u->data.hash); - items[i] = (EntryItemEx) { + items[j] = (EntryItemEx) { .object_offset = h, .hash = le64toh(u->data.hash), }; - - r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o); - if (r < 0) - return r; } r = journal_file_append_entry_internal(to, &ts, boot_id, xor_hash, items, n, NULL, NULL, NULL); diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 31e06b5..43b4db4 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -194,7 +194,28 @@ int journal_file_read_object_header(JournalFile *f, ObjectType type, uint64_t of int journal_file_tail_end_by_pread(JournalFile *f, uint64_t *ret_offset); int journal_file_tail_end_by_mmap(JournalFile *f, uint64_t *ret_offset); -uint64_t journal_file_entry_n_items(Object *o) _pure_; +int journal_file_data_payload( + JournalFile *f, + Object *o, + uint64_t offset, + const char *field, + size_t field_length, + size_t data_threshold, + void **ret_data, + size_t *ret_size); + +int journal_file_entry_item_next( + JournalFile *f, + Object *e, + uint64_t offset, + uint64_t *i, + const char *field, + size_t field_length, + size_t data_threshold, + uint64_t *ret_offset, + void **ret_data, + size_t *ret_size); + uint64_t journal_file_entry_array_n_items(JournalFile *f, Object *o) _pure_; uint64_t journal_file_entry_array_item(JournalFile *f, Object *o, size_t i) _pure_; uint64_t journal_file_hash_table_n_items(Object *o) _pure_; diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index f51fb15..639b94e 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -273,13 +273,20 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o return -EBADMSG; } - for (uint64_t i = 0; i < journal_file_entry_n_items(o); i++) { - if (le64toh(o->entry.items[i].object_offset) == 0 || - !VALID64(le64toh(o->entry.items[i].object_offset))) { - error(offset, - "Invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt, - i, journal_file_entry_n_items(o), - le64toh(o->entry.items[i].object_offset)); + for (uint64_t i = 0;;) { + uint64_t p; + int r; + + r = journal_file_entry_item_next(f, o, offset, &i, NULL, 0, 0, &p, NULL, NULL); + if (r < 0) { + error_errno(offset, r, "Invalid entry item (%"PRIu64"): %m", i); + return r; + } + if (r == 0) + break; + + if (p == 0 || !VALID64(p)) { + error(offset, "Invalid entry item (%"PRIu64" offset: "OFSfmt, i, p); return -EBADMSG; } } @@ -636,19 +643,23 @@ static int verify_entry( MMapFileDescriptor *cache_data_fd, uint64_t n_data, bool last) { - uint64_t i, n; int r; assert(f); assert(o); assert(cache_data_fd); - n = journal_file_entry_n_items(o); - for (i = 0; i < n; i++) { + for (uint64_t i = 0;;) { uint64_t q; Object *u; - q = le64toh(o->entry.items[i].object_offset); + r = journal_file_entry_item_next(f, o, p, &i, NULL, 0, 0, &q, NULL, NULL); + if (r < 0) { + error_errno(p, r, "Invalid entry item of entry"); + return r; + } + if (r == 0) + break; if (!contains_uint64(cache_data_fd, n_data, q)) { error(p, "Invalid data object of entry"); diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index 644b995..535dfdf 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -2274,10 +2274,10 @@ static bool field_is_valid(const char *field) { _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) { JournalFile *f; - uint64_t i, n; - size_t field_length; + size_t l; + uint64_t i = 0; + void *d; int r; - Object *o; assert_return(j, -EINVAL); assert_return(!journal_pid_changed(j), -ECHILD); @@ -2293,136 +2293,23 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** if (f->current_offset <= 0) return -EADDRNOTAVAIL; - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + r = journal_file_entry_item_next( + f, NULL, f->current_offset, &i, field, strlen(field), j->data_threshold, NULL, &d, &l); if (r < 0) return r; + if (r == 0) + return -ENOENT; - field_length = strlen(field); - - n = journal_file_entry_n_items(o); - for (i = 0; i < n; i++) { - Object *d; - uint64_t p, l; - size_t t; - int compression; - - p = le64toh(o->entry.items[i].object_offset); - r = journal_file_move_to_object(f, OBJECT_DATA, p, &d); - if (IN_SET(r, -EADDRNOTAVAIL, -EBADMSG)) { - log_debug_errno(r, "Entry item %"PRIu64" data object is bad, skipping over it: %m", i); - continue; - } - if (r < 0) - return r; - - l = le64toh(d->object.size) - offsetof(Object, data.payload); - - compression = d->object.flags & OBJECT_COMPRESSION_MASK; - if (compression) { -#if HAVE_COMPRESSION - r = decompress_startswith(compression, - d->data.payload, l, - &f->compress_buffer, - field, field_length, '='); - if (r < 0) - log_debug_errno(r, "Cannot decompress %s object of length %"PRIu64" at offset "OFSfmt": %m", - object_compressed_to_string(compression), l, p); - else if (r > 0) { - - size_t rsize; - - r = decompress_blob(compression, - d->data.payload, l, - &f->compress_buffer, &rsize, - j->data_threshold); - if (r < 0) - return r; - - *data = f->compress_buffer; - *size = (size_t) rsize; - - return 0; - } -#else - return -EPROTONOSUPPORT; -#endif - } else if (l >= field_length+1 && - memcmp(d->data.payload, field, field_length) == 0 && - d->data.payload[field_length] == '=') { - - t = (size_t) l; - - if ((uint64_t) t != l) - return -E2BIG; - - *data = d->data.payload; - *size = t; - - return 0; - } - } - - return -ENOENT; -} - -static int return_data( - sd_journal *j, - JournalFile *f, - Object *o, - const void **ret_data, - size_t *ret_size) { - - size_t t; - uint64_t l; - int compression; - - assert(j); - assert(f); - - l = le64toh(READ_NOW(o->object.size)); - if (l < offsetof(Object, data.payload)) - return -EBADMSG; - l -= offsetof(Object, data.payload); - - /* We can't read objects larger than 4G on a 32bit machine */ - t = (size_t) l; - if ((uint64_t) t != l) - return -E2BIG; - - compression = o->object.flags & OBJECT_COMPRESSION_MASK; - if (compression) { -#if HAVE_COMPRESSION - size_t rsize; - int r; - - r = decompress_blob( - compression, - o->data.payload, l, - &f->compress_buffer, &rsize, - j->data_threshold); - if (r < 0) - return r; - - if (ret_data) - *ret_data = f->compress_buffer; - if (ret_size) - *ret_size = (size_t) rsize; -#else - return -EPROTONOSUPPORT; -#endif - } else { - if (ret_data) - *ret_data = o->data.payload; - if (ret_size) - *ret_size = t; - } + *data = d; + *size = l; return 0; } _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) { JournalFile *f; - Object *o; + void *d; + size_t l; int r; assert_return(j, -EINVAL); @@ -2437,36 +2324,15 @@ _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t if (f->current_offset <= 0) return -EADDRNOTAVAIL; - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - if (r < 0) + r = journal_file_entry_item_next( + f, NULL, f->current_offset, &j->current_field, NULL, 0, j->data_threshold, NULL, &d, &l); + if (r <= 0) return r; - for (uint64_t n = journal_file_entry_n_items(o); j->current_field < n; j->current_field++) { - uint64_t p; + *data = d; + *size = l; - p = le64toh(o->entry.items[j->current_field].object_offset); - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (IN_SET(r, -EADDRNOTAVAIL, -EBADMSG)) { - log_debug_errno(r, "Entry item %"PRIu64" data object is bad, skipping over it: %m", j->current_field); - continue; - } - if (r < 0) - return r; - - r = return_data(j, f, o, data, size); - if (r == -EBADMSG) { - log_debug("Entry item %"PRIu64" data payload is bad, skipping over it.", j->current_field); - continue; - } - if (r < 0) - return r; - - j->current_field++; - - return 1; - } - - return 0; + return 1; } _public_ int sd_journal_enumerate_available_data(sd_journal *j, const void **data, size_t *size) { @@ -2478,7 +2344,21 @@ _public_ int sd_journal_enumerate_available_data(sd_journal *j, const void **dat return r; if (!JOURNAL_ERRNO_IS_UNAVAILABLE_FIELD(r)) return r; - j->current_field++; /* Try with the next field */ + + /* Try with the next field */ + r = journal_file_entry_item_next( + j->current_file, + NULL, + j->current_file->current_offset, + &j->current_field, + NULL, + 0, + 0, + NULL, + NULL, + NULL); + if (r <= 0) + return r; } } @@ -2932,7 +2812,7 @@ _public_ int sd_journal_enumerate_unique( for (;;) { JournalFile *of; Object *o; - const void *odata; + void *odata; size_t ol; bool found; int r; @@ -2976,7 +2856,8 @@ _public_ int sd_journal_enumerate_unique( j->unique_offset, o->object.type, OBJECT_DATA); - r = return_data(j, j->unique_file, o, &odata, &ol); + r = journal_file_data_payload( + j->unique_file, o, j->unique_offset, NULL, 0, j->data_threshold, &odata, &ol); if (r < 0) return r; @@ -3023,9 +2904,8 @@ _public_ int sd_journal_enumerate_unique( if (found) continue; - r = return_data(j, j->unique_file, o, ret_data, ret_size); - if (r < 0) - return r; + *ret_data = odata; + *ret_size = ol; return 1; } From 16c1638e08fad40244e63910e635b1b41da1cd5a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 9/15] journal: Store entry item offsets in trie in compact mode In a journal file, data objects such as "_BOOT_ID=100b7ef4c5124bc6839e28db763b4627" and "PRIORITY=6" are referenced repeatedly; usually the only difference between two entries is the "MESSAGE=" data object. This results in a large amount of wasted space, as each entry contains an array of 16-byte EntryItem pointers to such data objects. Instead, compress that array of items into a Trie, allowing us to only record the difference between them: node: pointer to _BOOT_ID=100b7ef4c5124bc6839e28db763b4627 data `- node: pointer to PRIORITY=6 data `- node: pointer to MESSAGE=foo `- node: pointer to MESSAGE=bar The new Trie Node object stores an offset to the parent node, an offset to the corresponding Data object, the next object in the hash table chain and the hash itself. The hash we use is the XOR hash of all the Entry items starting from the current node. In the best case scenario, this allows us to find the correct node with just a single hash lookup. If we assume that for most messages, all fields except MESSAGE have already appeared in a previous message, we can compare the overhead before and after this change per message: (I'm not counting the EntryItem hash field as overhead here as we can remove those regardless of whether we use a trie or not) Before: - A new Data object for the MESSAGE field - A new Entry object with +- 20 64-bit offsets After: - A new Data object for the MESSAGE field - A new Trie Node object for the MESSAGE field (6 * 64-bit) - A new Entry object with a single 64-bit offset So the Entry overhead reduces from 20 * 64-bit to 7 * 64-bit. If we add opt-in field indexing in the future, we'll store the MESSAGE field inline which will reduce the Entry overhead to a single 64-bit offset for most messages. Before: OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 3562667 591.0M Field 3971 213.6K Entry 3498566 1.2G Data Hash Table 20 71.1M Field Hash Table 20 104.3K Entry Array 582647 505.0M Tag 0 0B Total 7647891 2.4G After: OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 3521895 587.0M Field 3140 169.4K Entry 3499118 240.2M Data Hash Table 14 49.7M Field Hash Table 14 73.0K Entry Array 577350 499.5M Tag 0 0B Trie Node 5767903 220.0M Trie Hash Table 14 74.6M Total 13369448 1.6G --- diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index 75c7a5e..7748998 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -23,6 +23,7 @@ typedef struct EntryObject EntryObject; typedef struct HashTableObject HashTableObject; typedef struct EntryArrayObject EntryArrayObject; typedef struct TagObject TagObject; +typedef struct TrieNodeObject TrieNodeObject; typedef struct EntryItem EntryItem; typedef struct HashItem HashItem; @@ -39,6 +40,8 @@ typedef enum ObjectType { OBJECT_FIELD_HASH_TABLE, OBJECT_ENTRY_ARRAY, OBJECT_TAG, + OBJECT_TRIE_NODE, + OBJECT_TRIE_HASH_TABLE, _OBJECT_TYPE_MAX } ObjectType; @@ -95,17 +98,22 @@ struct EntryItem { typedef struct { uint64_t object_offset; uint64_t hash; + /* The hash used to calculate the Entry object's XOR hash field. */ + uint64_t xor_hash; } EntryItemEx; -#define EntryObject__contents { \ - ObjectHeader object; \ - le64_t seqnum; \ - le64_t realtime; \ - le64_t monotonic; \ - sd_id128_t boot_id; \ - le64_t xor_hash; \ - EntryItem items[]; \ - } +#define EntryObject__contents { \ + ObjectHeader object; \ + le64_t seqnum; \ + le64_t realtime; \ + le64_t monotonic; \ + sd_id128_t boot_id; \ + le64_t xor_hash; \ + union { \ + EntryItem items[0]; \ + le64_t trie_offset; \ + }; \ +} struct EntryObject EntryObject__contents; struct EntryObject__packed EntryObject__contents _packed_; @@ -139,6 +147,18 @@ struct TagObject { uint8_t tag[TAG_LENGTH]; /* SHA-256 HMAC */ } _packed_; +#define TrieNodeObject__contents { \ + ObjectHeader object; \ + le64_t hash; \ + le32_t parent_offset; \ + le32_t object_offset; \ + le64_t next_hash_offset; \ +} + +struct TrieNodeObject TrieNodeObject__contents; +struct TrieNodeObject__packed TrieNodeObject__contents _packed_; +assert_cc(sizeof(struct TrieNodeObject) == sizeof(struct TrieNodeObject__packed)); + union Object { ObjectHeader object; DataObject data; @@ -147,6 +167,7 @@ union Object { HashTableObject hash_table; EntryArrayObject entry_array; TagObject tag; + TrieNodeObject trie_node; }; enum { @@ -227,12 +248,17 @@ enum { /* Added in 246 */ \ le64_t data_hash_chain_depth; \ le64_t field_hash_chain_depth; \ + /* Added in 251 */ \ + le64_t trie_hash_table_offset; \ + le64_t trie_hash_table_size; \ + le64_t n_trie_nodes; \ + le64_t trie_hash_chain_depth; \ } struct Header struct_Header__contents; struct Header__packed struct_Header__contents _packed_; assert_cc(sizeof(struct Header) == sizeof(struct Header__packed)); -assert_cc(sizeof(struct Header) == 256); +assert_cc(sizeof(struct Header) == 288); #define FSS_HEADER_SIGNATURE \ ((const char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }) diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index d7c2915..94d3a78 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -642,6 +642,8 @@ static uint64_t minimum_header_size(Object *o) { [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject), [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject), [OBJECT_TAG] = sizeof(TagObject), + [OBJECT_TRIE_NODE] = sizeof(TrieNodeObject), + [OBJECT_TRIE_HASH_TABLE] = sizeof(HashTableObject), }; if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0) @@ -707,19 +709,37 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) uint64_t sz; sz = le64toh(READ_NOW(o->object.size)); - if (sz < offsetof(Object, entry.items) || - (sz - offsetof(Object, entry.items)) % sizeof(EntryItem) != 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64, - offsetof(Object, entry.items), - sz, - offset); + if (JOURNAL_HEADER_COMPACT(f->header)) { + if (sz != sizeof(EntryObject)) + return log_debug_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64, + sizeof(EntryObject), + sz, + offset); - if ((sz - offsetof(Object, entry.items)) / sizeof(EntryItem) <= 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Invalid number items in entry: %" PRIu64 ": %" PRIu64, - (sz - offsetof(Object, entry.items)) / sizeof(EntryItem), - offset); + if (o->entry.trie_offset == 0) + return log_debug_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Bad entry trie offset (== 0): %" PRIu64, + offset); + } else { + if (sz < offsetof(Object, entry.items) || + (sz - offsetof(Object, entry.items)) % sizeof(EntryItem) != 0) + return log_debug_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64, + offsetof(Object, entry.items), + sz, + offset); + + if ((sz - offsetof(Object, entry.items)) / sizeof(EntryItem) <= 0) + return log_debug_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Invalid number items in entry: %" PRIu64 ": %" PRIu64, + (sz - offsetof(Object, entry.items)) / sizeof(EntryItem), + offset); + } if (le64toh(o->entry.seqnum) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), @@ -743,7 +763,8 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) } case OBJECT_DATA_HASH_TABLE: - case OBJECT_FIELD_HASH_TABLE: { + case OBJECT_FIELD_HASH_TABLE: + case OBJECT_TRIE_HASH_TABLE: { uint64_t sz; sz = le64toh(READ_NOW(o->object.size)); @@ -751,10 +772,9 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) (sz - offsetof(Object, hash_table.items)) % sizeof(HashItem) != 0 || (sz - offsetof(Object, hash_table.items)) / sizeof(HashItem) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Invalid %s hash table size: %" PRIu64 ": %" PRIu64, - o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", - sz, - offset); + "Invalid %s size: %" PRIu64 ": %" PRIu64, + journal_object_type_to_string(o->object.type), + sz, offset); break; } @@ -793,6 +813,26 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) le64toh(o->tag.epoch), offset); break; + + case OBJECT_TRIE_NODE: + if (le64toh(o->object.size) != sizeof(TrieNodeObject)) + return log_debug_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Invalid object trie node size: %" PRIu64, + le64toh(o->object.size)); + + if (!VALID64(le64toh(o->trie_node.next_hash_offset)) || + !VALID64(le32toh(o->trie_node.object_offset)) || + !VALID64(le32toh(o->trie_node.parent_offset))) + return log_debug_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Invalid offset (next_hash_offset=" OFSfmt ", object_offset=" OFSfmt32 + ", parent_offset=" OFSfmt32, + le64toh(o->trie_node.next_hash_offset), + le32toh(o->trie_node.object_offset), + le32toh(o->trie_node.parent_offset)); + + break; } return 0; @@ -1018,6 +1058,20 @@ int journal_file_append_object( return 0; } +static int default_data_hash_table_size(JournalFile *f) { + uint64_t s; + + /* We estimate that we need 1 hash table entry per 768 bytes of journal file and we want to make sure + * we never get beyond 75% fill level. Calculate the hash table size for the maximum file size based + * on these metrics. */ + + s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem); + if (s < DEFAULT_DATA_HASH_TABLE_SIZE) + s = DEFAULT_DATA_HASH_TABLE_SIZE; + + return s; +} + static int journal_file_setup_data_hash_table(JournalFile *f) { uint64_t s, p; Object *o; @@ -1026,14 +1080,7 @@ static int journal_file_setup_data_hash_table(JournalFile *f) { assert(f); assert(f->header); - /* We estimate that we need 1 hash table entry per 768 bytes - of journal file and we want to make sure we never get - beyond 75% fill level. Calculate the hash table size for - the maximum file size based on these metrics. */ - - s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem); - if (s < DEFAULT_DATA_HASH_TABLE_SIZE) - s = DEFAULT_DATA_HASH_TABLE_SIZE; + s = default_data_hash_table_size(f); log_debug("Reserving %"PRIu64" entries in data hash table.", s / sizeof(HashItem)); @@ -1081,6 +1128,48 @@ static int journal_file_setup_field_hash_table(JournalFile *f) { return 0; } +static int journal_file_setup_trie_hash_table(JournalFile *f) { + uint64_t s, p; + Object *o; + int r; + + assert(f); + assert(f->header); + + /* Based on the following results from converting a non-compact system journal to compact mode, we + * use "1.5 * default data hash table size" as the default trie hash table size. + * + * OBJECT TYPE ENTRIES SIZE + * Unused 0 0B + * Data 963284 89.8M + * Field 2544 137.2K + * Entry 3269815 574.1M + * Data Hash Table 11 39.1M + * Field Hash Table 11 57.4K + * Entry Array 458484 539.1M + * Tag 0 0B + * Trie Node 1660978 76.0M + * Trie Hash Table 11 39.1M + * Boot ID 58 1.8K + */ + s = default_data_hash_table_size(f); + s += ALIGN_TO(s / 2, sizeof(HashItem)); + + log_debug("Reserving %"PRIu64" entries in trie hash table.", s / sizeof(HashItem)); + + r = journal_file_append_object( + f, OBJECT_TRIE_HASH_TABLE, offsetof(Object, hash_table.items) + s, &o, &p); + if (r < 0) + return r; + + memzero(o->hash_table.items, s); + + f->header->trie_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); + f->header->trie_hash_table_size = htole64(s); + + return 0; +} + int journal_file_map_data_hash_table(JournalFile *f) { uint64_t s, p; void *t; @@ -1133,6 +1222,29 @@ int journal_file_map_field_hash_table(JournalFile *f) { return 0; } +static int journal_file_map_trie_hash_table(JournalFile *f) { + uint64_t s, p; + void *t; + int r; + + assert(f); + assert(f->header); + + if (f->trie_hash_table) + return 0; + + p = le64toh(f->header->trie_hash_table_offset); + s = le64toh(f->header->trie_hash_table_size); + + r = journal_file_move_to(f, OBJECT_TRIE_HASH_TABLE, true, p, s, &t); + if (r < 0) + return r; + + f->trie_hash_table = t; + return 0; +} + + static int journal_file_link_field( JournalFile *f, Object *o, @@ -1229,6 +1341,52 @@ static int journal_file_link_data( return 0; } +static int journal_file_link_trie_node( + JournalFile *f, + Object *o, + uint64_t offset, + uint64_t hash) { + + uint64_t p, h, m; + int r; + + assert(f); + assert(f->header); + assert(o); + assert(offset > 0); + + if (o->object.type != OBJECT_TRIE_NODE) + return -EINVAL; + + m = le64toh(READ_NOW(f->header->trie_hash_table_size)) / sizeof(HashItem); + if (m <= 0) + return -EBADMSG; + + /* This might alter the window we are looking at */ + o->trie_node.next_hash_offset = 0; + + h = hash % m; + p = le64toh(f->trie_hash_table[h].tail_hash_offset); + if (p == 0) + /* Only entry in the hash table is easy */ + f->trie_hash_table[h].head_hash_offset = htole64(offset); + else { + /* Move back to the previous data object, to patch in + * pointer */ + + r = journal_file_move_to_object(f, OBJECT_TRIE_NODE, p, &o); + if (r < 0) + return r; + + o->trie_node.next_hash_offset = htole64(offset); + } + + f->trie_hash_table[h].tail_hash_offset = htole64(offset); + f->header->n_trie_nodes = htole64(le64toh(f->header->n_trie_nodes) + 1); + + return 0; +} + static int next_hash_offset( JournalFile *f, uint64_t *p, @@ -1613,6 +1771,43 @@ static int journal_file_append_data( return 0; } +static int journal_file_append_trie_node( + JournalFile *f, + uint64_t hash, + uint64_t parent_offset, + uint64_t object_offset, + Object **ret, + uint64_t *ret_offset) { + + Object *o; + uint64_t p; + int r; + + /* Map the trie hash table, if it isn't mapped yet. */ + r = journal_file_map_trie_hash_table(f); + if (r < 0) + return r; + + r = journal_file_append_object(f, OBJECT_TRIE_NODE, sizeof(TrieNodeObject), &o, &p); + if (r < 0) + return r; + + o->trie_node.hash = htole64(hash); + o->trie_node.parent_offset = htole32(parent_offset); + o->trie_node.object_offset = htole32(object_offset); + + r = journal_file_link_trie_node(f, o, p, hash); + if (r < 0) + return r; + + if (ret) + *ret = o; + if (ret_offset) + *ret_offset = p; + + return 0; +} + static int maybe_decompress_payload( JournalFile *f, uint8_t *payload, @@ -1715,7 +1910,7 @@ int journal_file_data_payload( ret_size); } -int journal_file_entry_item_next( +static int journal_file_entry_item_next_compact( JournalFile *f, Object *e, uint64_t offset, @@ -1727,42 +1922,82 @@ int journal_file_entry_item_next( void **ret_data, size_t *ret_size) { - /* Iterates over the entry items of the given entry. The output parameters return data about the Data - * object pointed at by the next entry item if requested. - * - * - If `ret_offset` is not NULL, it is set to the offset of the Data object - * - If `ret_data` is not NULL, it is set to a pointer to the decompressed payload of the Data object - * - If `ret_size` is not NULL, it is set to the size of the decompressed payload of the Data object - * - * The iterator is stored in `i`. To start iterating from the start of the entry items, set `i` to - * zero. It is automatically updated by this function and should not be touched again unless you want - * to restart iterating over the entry items. - * - * If `field` and `field_length` are given, this function keeps iterating until it finds an entry - * item whose Data object payload starts with the given field, followed by the '=' character. - * - * If `data_threshold` is larger than zero, the decompressed payload is limited to `data_threshold` - * amount of bytes. - * - * This function returns a positive number if it succesfully managed to find the next entry item. If - * no more entry items were available, or none of the remaining entry items were of the given field, - * it returns zero. If an error occurred, it returns a negative errno value. - */ - - uint64_t p, sz; + uint64_t p; int r; - assert(!e || e->object.type == OBJECT_ENTRY); - assert(offset); - assert(i); - assert(!field == (field_length == 0)); /* These must be specified together. */ + if (*i == UINT64_MAX) + return 0; - if (!e) { - r = journal_file_move_to_object(f, OBJECT_ENTRY, offset, &e); + p = *i == 0 ? le64toh(e->entry.trie_offset) : *i; + if (p == 0) + return -EBADMSG; + + for (; p != 0;) { + Object *o; + uint64_t q; + + r = journal_file_move_to_object(f, OBJECT_TRIE_NODE, p, &o); + if (IN_SET(r, -EADDRNOTAVAIL, -EBADMSG)) { + log_debug_errno(r, "Bad trie node at %"PRIu64", skipping remaining entry items: %m", p); + break; + } + if (r < 0) + return r; + + p = le32toh(o->trie_node.parent_offset); + q = le32toh(o->trie_node.object_offset); + + r = journal_file_data_payload( + f, + NULL, + q, + field, + field_length, + data_threshold, + ret_data, + ret_size); + if (IN_SET(r, -EADDRNOTAVAIL, -EBADMSG)) { + log_debug_errno(r, "Entry item %"PRIu64" data object is bad, skipping over it: %m", q); + continue; + } if (r < 0) return r; + if (r == 0) + continue; + + if (ret_offset) + *ret_offset = q; + + /* If we've iterated all trie nodes, set the iterator to UINT64_MAX to indicate this. We + * can't use zero as zero is reserved for starting iteration from the beginning. */ + if (p == 0) + p = UINT64_MAX; + + *i = p; + + return 1; } + *i = UINT64_MAX; + + return 0; +} + +static int journal_file_entry_item_next_non_compact( + JournalFile *f, + Object *e, + uint64_t offset, + uint64_t *i, + const char *field, + size_t field_length, + size_t data_threshold, + uint64_t *ret_offset, + void **ret_data, + size_t *ret_size) { + + uint64_t p, sz; + int r; + sz = le64toh(READ_NOW(e->object.size)); if (sz < offsetof(Object, entry.items)) return -EBADMSG; @@ -1796,6 +2031,60 @@ int journal_file_entry_item_next( return 0; } +int journal_file_entry_item_next( + JournalFile *f, + Object *e, + uint64_t offset, + uint64_t *i, + const char *field, + size_t field_length, + size_t data_threshold, + uint64_t *ret_offset, + void **ret_data, + size_t *ret_size) { + + /* Iterates over the entry items of the given entry. The output parameters return data about the Data + * object pointed at by the next entry item if requested. + * + * - If `ret_offset` is not NULL, it is set to the offset of the Data object + * - If `ret_data` is not NULL, it is set to a pointer to the decompressed payload of the Data object + * - If `ret_size` is not NULL, it is set to the size of the decompressed payload of the Data object + * + * The iterator is stored in `i`. To start iterating from the start of the entry items, set `i` to + * zero. It is automatically updated by this function and should not be touched again unless you want + * to restart iterating over the entry items. + * + * If `field` and `field_length` are given, this function keeps iterating until it finds an entry + * item whose Data object payload starts with the given field, followed by the '=' character. + * + * If `data_threshold` is larger than zero, the decompressed payload is limited to `data_threshold` + * amount of bytes. + * + * This function returns a positive number if it succesfully managed to find the next entry item. If + * no more entry items were available, or none of the remaining entry items were of the given field, + * it returns zero. If an error occurred, it returns a negative errno value. + */ + + int r; + + assert(!e || e->object.type == OBJECT_ENTRY); + assert(offset); + assert(i); + assert(!field == (field_length == 0)); + + if (!e) { + r = journal_file_move_to_object(f, OBJECT_ENTRY, offset, &e); + if (r < 0) + return r; + } + + return JOURNAL_HEADER_COMPACT(f->header) ? + journal_file_entry_item_next_compact( + f, e, offset, i, field, field_length, data_threshold, ret_offset, ret_data, ret_size) : + journal_file_entry_item_next_non_compact( + f, e, offset, i, field, field_length, data_threshold, ret_offset, ret_data, ret_size); +} + uint64_t journal_file_entry_array_n_items(JournalFile *f, Object *o) { uint64_t sz; @@ -1834,6 +2123,81 @@ uint64_t journal_file_hash_table_n_items(Object *o) { return (sz - offsetof(Object, hash_table.items)) / sizeof(HashItem); } +static int journal_file_find_trie_object( + JournalFile *f, + uint64_t hash, + const EntryItemEx *items, + size_t n_items, + Object **ret, + uint64_t *ret_offset) { + + uint64_t p, h, m, depth = 0; + int r; + + assert(f); + assert(f->header); + assert(items); + assert(n_items > 0); + + /* If there's no trie hash table, then there's no entry. */ + if (le64toh(f->header->trie_hash_table_size) <= 0) + return 0; + + /* Map the trie hash table, if it isn't mapped yet. */ + r = journal_file_map_trie_hash_table(f); + if (r < 0) + return r; + + m = le64toh(READ_NOW(f->header->trie_hash_table_size)) / sizeof(HashItem); + if (m <= 0) + return -EBADMSG; + + h = hash % m; + p = le64toh(f->trie_hash_table[h].head_hash_offset); + + while (p > 0) { + Object *o; + + r = journal_file_move_to_object(f, OBJECT_TRIE_NODE, p, &o); + if (r < 0) + return r; + + if (le64toh(o->trie_node.hash) != hash) + goto next; + + uint64_t q = p; + Object *t = o; + size_t i = n_items - 1; + + for (; i != SIZE_MAX && q != 0; i--, q = le32toh(t->trie_node.parent_offset)) { + r = journal_file_move_to_object(f, OBJECT_TRIE_NODE, q, &t); + if (r < 0) + return r; + + if (le32toh(t->trie_node.object_offset) != items[i].object_offset) + break; + } + + if (i == SIZE_MAX && q == 0) { + if (ret) + *ret = o; + + if (ret_offset) + *ret_offset = p; + + return 1; + } + + next: + r = next_hash_offset( + f, &p, &o->trie_node.next_hash_offset, &depth, &f->header->trie_hash_chain_depth); + if (r < 0) + return r; + } + + return 0; +} + static void write_entry_array_item(JournalFile *f, Object *o, uint64_t i, uint64_t p) { assert(f); assert(o); @@ -2021,12 +2385,10 @@ static int journal_file_append_entry_internal( JournalFile *f, const dual_timestamp *ts, const sd_id128_t *boot_id, - uint64_t xor_hash, const EntryItemEx items[], size_t n_items, uint64_t *seqnum, Object **ret, uint64_t *ret_offset) { - uint64_t np; - uint64_t osize; + uint64_t np, osize, parent_offset = 0, xor_hash = 0; Object *o; int r; @@ -2035,16 +2397,53 @@ static int journal_file_append_entry_internal( assert(items || n_items == 0); assert(ts); - osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem)); + for (uint64_t i = 0; i < n_items; i++) + xor_hash ^= items[i].xor_hash; + + if (JOURNAL_HEADER_COMPACT(f->header)) { + size_t i; + + for (i = n_items - 1; i != SIZE_MAX; i--) { + r = journal_file_find_trie_object(f, xor_hash, items, i + 1, NULL, &parent_offset); + if (r < 0) + return r; + if (r > 0) + break; + + xor_hash ^= items[i].xor_hash; /* Remove hash from XOR hash. */ + } + + for (i += 1; i < n_items; i++) { + uint64_t p; + + xor_hash ^= items[i].xor_hash; /* Add hash back to XOR hash. */ + + r = journal_file_append_trie_node( + f, xor_hash, parent_offset, items[i].object_offset, NULL, &p); + if (r < 0) + return r; + + parent_offset = p; + } + } + + osize = JOURNAL_HEADER_COMPACT(f->header) ? + sizeof(EntryObject) : + offsetof(Object, entry.items) + (n_items * sizeof(EntryItem)); r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np); if (r < 0) return r; o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum)); - for (size_t i = 0; i < n_items; i++) - o->entry.items[i] = (EntryItem){ .object_offset = htole64(items[i].object_offset), - .hash = htole64(items[i].hash) }; + + if (JOURNAL_HEADER_COMPACT(f->header)) + o->entry.trie_offset = htole64(parent_offset); + else + for (size_t i = 0; i < n_items; i++) + o->entry.items[i] = (EntryItem){ .object_offset = htole64(items[i].object_offset), + .hash = htole64(items[i].hash) }; + o->entry.realtime = htole64(ts->realtime); o->entry.monotonic = htole64(ts->monotonic); o->entry.xor_hash = htole64(xor_hash); @@ -2179,9 +2578,8 @@ int journal_file_append_entry( Object **ret, uint64_t *ret_offset) { EntryItemEx *items; - int r; - uint64_t xor_hash = 0; struct dual_timestamp _ts; + int r; assert(f); assert(f->header); @@ -2226,14 +2624,12 @@ int journal_file_append_entry( * are completely identical (they include the XOR hash after all). For classic Jenkins-hash * files things are easier, we can just take the value from the stored record directly. */ - if (JOURNAL_HEADER_KEYED_HASH(f->header)) - xor_hash ^= jenkins_hash64(iovec[i].iov_base, iovec[i].iov_len); - else - xor_hash ^= le64toh(o->data.hash); - - items[i] = (EntryItemEx) { + items[i] = (EntryItemEx){ .object_offset = p, .hash = le64toh(o->data.hash), + .xor_hash = JOURNAL_HEADER_KEYED_HASH(f->header) ? + jenkins_hash64(iovec[i].iov_base, iovec[i].iov_len) : + le64toh(o->data.hash), }; } @@ -2242,7 +2638,7 @@ int journal_file_append_entry( typesafe_qsort(items, n_iovec, entry_item_cmp); n_iovec = remove_duplicate_entry_items(items, n_iovec); - r = journal_file_append_entry_internal(f, ts, boot_id, xor_hash, items, n_iovec, seqnum, ret, ret_offset); + r = journal_file_append_entry_internal(f, ts, boot_id, items, n_iovec, seqnum, ret, ret_offset); /* If the memory mapping triggered a SIGBUS then we return an * IO error and ignore the error code passed down to us, since @@ -3373,6 +3769,7 @@ void journal_file_print_header(JournalFile *f) { "Arena size: %"PRIu64"\n" "Data hash table size: %"PRIu64"\n" "Field hash table size: %"PRIu64"\n" + "Trie hash table size: %"PRIu64"\n" "Rotate suggested: %s\n" "Head sequential number: %"PRIu64" (%"PRIx64")\n" "Tail sequential number: %"PRIu64" (%"PRIx64")\n" @@ -3401,6 +3798,7 @@ void journal_file_print_header(JournalFile *f) { le64toh(f->header->arena_size), le64toh(f->header->data_hash_table_size) / sizeof(HashItem), le64toh(f->header->field_hash_table_size) / sizeof(HashItem), + le64toh(f->header->trie_hash_table_size) / sizeof(HashItem), yes_no(journal_file_rotate_suggested(f, 0, LOG_DEBUG)), le64toh(f->header->head_entry_seqnum), le64toh(f->header->head_entry_seqnum), le64toh(f->header->tail_entry_seqnum), le64toh(f->header->tail_entry_seqnum), @@ -3422,6 +3820,12 @@ void journal_file_print_header(JournalFile *f) { le64toh(f->header->n_fields), 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)))); + if (JOURNAL_HEADER_CONTAINS(f->header, n_trie_nodes)) + printf("Trie Node objects: %"PRIu64"\n" + "Trie Node hash table fill: %.1f%%\n", + le64toh(f->header->n_trie_nodes), + 100.0 * (double) le64toh(f->header->n_trie_nodes) / ((double) (le64toh(f->header->trie_hash_table_size) / sizeof(HashItem)))); + if (JOURNAL_HEADER_CONTAINS(f->header, n_tags)) printf("Tag objects: %"PRIu64"\n", le64toh(f->header->n_tags)); @@ -3437,6 +3841,10 @@ void journal_file_print_header(JournalFile *f) { printf("Deepest data hash chain: %" PRIu64"\n", f->header->data_hash_chain_depth); + if (JOURNAL_HEADER_CONTAINS(f->header, trie_hash_chain_depth)) + printf("Deepest trie hash chain: %" PRIu64"\n", + f->header->trie_hash_chain_depth); + if (fstat(f->fd, &st) >= 0) printf("Disk usage: %s\n", FORMAT_BYTES((uint64_t) st.st_blocks * 512ULL)); } @@ -3709,6 +4117,12 @@ int journal_file_open( if (r < 0) goto fail; + if (JOURNAL_HEADER_COMPACT(f->header)) { + r = journal_file_setup_trie_hash_table(f); + if (r < 0) + goto fail; + } + #if HAVE_GCRYPT r = journal_file_append_first_tag(f); if (r < 0) @@ -3820,7 +4234,7 @@ int journal_file_dispose(int dir_fd, const char *fname) { } int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p) { - size_t n = 0, xor_hash = 0; + size_t n = 0; const sd_id128_t *boot_id; dual_timestamp ts; EntryItemEx *items; @@ -3871,18 +4285,15 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 if (r < 0) return r; - if (JOURNAL_HEADER_KEYED_HASH(to->header)) - xor_hash ^= jenkins_hash64(data, l); - else - xor_hash ^= le64toh(u->data.hash); - - items[j] = (EntryItemEx) { + items[j] = (EntryItemEx){ .object_offset = h, .hash = le64toh(u->data.hash), + .xor_hash = JOURNAL_HEADER_KEYED_HASH(to->header) ? jenkins_hash64(data, l) : + le64toh(u->data.hash), }; } - r = journal_file_append_entry_internal(to, &ts, boot_id, xor_hash, items, n, NULL, NULL, NULL); + r = journal_file_append_entry_internal(to, &ts, boot_id, items, n, NULL, NULL, NULL); if (mmap_cache_fd_got_sigbus(to->cache_fd)) return -EIO; @@ -4105,6 +4516,14 @@ bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec, int log return true; } + if (JOURNAL_HEADER_CONTAINS(f->header, trie_hash_chain_depth) && + le64toh(f->header->trie_hash_chain_depth) > HASH_CHAIN_DEPTH_MAX) { + log_full(log_level, + "Trie hash table of %s has deepest hash chain of length at %" PRIu64 ", suggesting rotation.", + f->path, le64toh(f->header->trie_hash_chain_depth)); + return true; + } + /* Are the data objects properly indexed by field objects? */ if (JOURNAL_HEADER_CONTAINS(f->header, n_data) && JOURNAL_HEADER_CONTAINS(f->header, n_fields) && @@ -4142,6 +4561,8 @@ static const char * const journal_object_type_table[] = { [OBJECT_FIELD_HASH_TABLE] = "field hash table", [OBJECT_ENTRY_ARRAY] = "entry array", [OBJECT_TAG] = "tag", + [OBJECT_TRIE_NODE] = "trie node", + [OBJECT_TRIE_HASH_TABLE] = "trie hash table", }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(journal_object_type, ObjectType); diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 43b4db4..20187b8 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -84,6 +84,7 @@ typedef struct JournalFile { Header *header; HashItem *data_hash_table; HashItem *field_hash_table; + HashItem *trie_hash_table; uint64_t current_offset; uint64_t current_seqnum; @@ -151,6 +152,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(JournalFile*, journal_file_close); /* Use six characters to cover the offsets common in smallish journal * files without adding too many zeros. */ #define OFSfmt "%06"PRIx64 +#define OFSfmt32 "%06"PRIx32 static inline bool VALID_REALTIME(uint64_t u) { /* This considers timestamps until the year 3112 valid. That should be plenty room... */ diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index 639b94e..fe7e965 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -237,19 +237,35 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o } case OBJECT_ENTRY: - if ((le64toh(o->object.size) - offsetof(Object, entry.items)) % sizeof(EntryItem) != 0) { - error(offset, - "Bad entry size (<= %zu): %"PRIu64, - offsetof(Object, entry.items), - le64toh(o->object.size)); - return -EBADMSG; - } + if (JOURNAL_HEADER_COMPACT(f->header)) { + if (le64toh(o->object.size) != sizeof(EntryObject)) { + error(offset, + "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64, + sizeof(EntryObject), + le64toh(o->object.size), + offset); + return -EBADMSG; + } - if ((le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem) <= 0) { - error(offset, - "Invalid number items in entry: %"PRIu64, - (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem)); - return -EBADMSG; + if (o->entry.trie_offset == 0) { + error(offset, "Bad entry trie offset (== 0): %" PRIu64, offset); + return -EBADMSG; + } + } else { + if ((le64toh(o->object.size) - offsetof(Object, entry.items)) % sizeof(EntryItem) != 0) { + error(offset, + "Bad entry size (<= %zu): %" PRIu64, + offsetof(Object, entry.items), + le64toh(o->object.size)); + return -EBADMSG; + } + + if ((le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem) <= 0) { + error(offset, + "Invalid number items in entry: %" PRIu64, + (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem)); + return -EBADMSG; + } } if (le64toh(o->entry.seqnum) <= 0) { @@ -295,6 +311,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o case OBJECT_DATA_HASH_TABLE: case OBJECT_FIELD_HASH_TABLE: + case OBJECT_TRIE_HASH_TABLE: if ((le64toh(o->object.size) - offsetof(Object, hash_table.items)) % sizeof(HashItem) != 0 || (le64toh(o->object.size) - offsetof(Object, hash_table.items)) / sizeof(HashItem) <= 0) { error(offset, @@ -382,6 +399,25 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o } break; + + case OBJECT_TRIE_NODE: + if (le64toh(o->object.size) != sizeof(TrieNodeObject)) { + error(offset, "Invalid object trie node size: %"PRIu64, le64toh(o->object.size)); + return -EBADMSG; + } + + if (!VALID64(le64toh(o->trie_node.next_hash_offset)) || + !VALID64(le32toh(o->trie_node.object_offset)) || + !VALID64(le32toh(o->trie_node.parent_offset))) { + error(offset, + "Invalid offset (next_hash_offset="OFSfmt", object_offset="OFSfmt32", parent_offset="OFSfmt32, + le64toh(o->trie_node.next_hash_offset), + le32toh(o->trie_node.object_offset), + le32toh(o->trie_node.parent_offset)); + return -EBADMSG; + } + + break; } return 0; @@ -825,7 +861,7 @@ int journal_file_verify( uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0; sd_id128_t entry_boot_id; bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false; - uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0, n_tags = 0; + uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_trie_hash_tables = 0, n_entry_arrays = 0, n_tags = 0; usec_t last_usec = 0; _cleanup_close_ int data_fd = -1, entry_fd = -1, entry_array_fd = -1; _cleanup_fclose_ FILE *data_fp = NULL, *entry_fp = NULL, *entry_array_fp = NULL; @@ -1094,6 +1130,15 @@ int journal_file_verify( break; + case OBJECT_TRIE_HASH_TABLE: + r = verify_hash_table(o, p, &n_trie_hash_tables, + le64toh(f->header->trie_hash_table_offset), + le64toh(f->header->trie_hash_table_size)); + if (r < 0) + goto fail; + + break; + case OBJECT_ENTRY_ARRAY: r = write_uint64(entry_array_fp, p); if (r < 0) diff --git a/src/libsystemd/sd-journal/mmap-cache.h b/src/libsystemd/sd-journal/mmap-cache.h index 4769414..7afb7f0 100644 --- a/src/libsystemd/sd-journal/mmap-cache.h +++ b/src/libsystemd/sd-journal/mmap-cache.h @@ -5,7 +5,7 @@ #include /* One context per object type, plus one of the header, plus one "additional" one */ -#define MMAP_CACHE_MAX_CONTEXTS 9 +#define MMAP_CACHE_MAX_CONTEXTS 11 typedef struct MMapCache MMapCache; typedef struct MMapFileDescriptor MMapFileDescriptor; From 2aa2af1b133801391586a9808784a291168cb158 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 10/15] journal: Add support for not deduplicating specific fields in compact mode For fields that are almost always unique, allocating a separate Data object ends up adding a noticeable amount of overhead. By adding support for storing these fields inline in the entry object, we reduce the space required to store these unique fields. If a field is marked as unique, we don't allocate a Data object and store it inline in the Entry instead. The entry payload for storing a single unique field has the following format: - 1 byte for flags (compressed, etc) - 4 bytes for size - data (optionally compressed) Each unique field is serialized to this format and the concatenation of all the serialized unique fields becomes the entry payload. When iterating over entries, we first iterate over all the deduplicated fields via the trie. Once those are done, we continue with the inlined fields. journal_file_entry_next()'s implementation is extended to support this. One change in it's API is that ret_offset is set to zero for inline fields if it is provided. The list of fields to not deduplicate can be configured with the $SYSTEMD_JOURNAL_UNIQUE_FIELDS environment variable. If it's not set, we default to deduplicating all fields except MESSAGE. With this change, we need to have the field object available before we append the data object so we move field object allocation out of journal_file_append_data() and into journal_file_append_entry() and journal_file_copy_entry() instead. Before: OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 3521895 587.0M Field 3140 169.4K Entry 3499118 240.2M Data Hash Table 14 49.7M Field Hash Table 14 73.0K Entry Array 577350 499.5M Tag 0 0B Trie Node 5767903 220.0M Trie Hash Table 14 74.6M Total 13369448 1.6G After: OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 1022925 95.3M Field 2808 151.3K Entry 3499976 667.7M Data Hash Table 13 46.2M Field Hash Table 13 67.8K Entry Array 492907 576.7M Tag 0 0B Trie Node 1758648 67.0M Trie Hash Table 13 69.3M Total 6777303 1.4G --- diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index 7748998..d2bbb3c 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -47,11 +47,12 @@ typedef enum ObjectType { /* Object flags */ enum { - OBJECT_COMPRESSED_XZ = 1 << 0, - OBJECT_COMPRESSED_LZ4 = 1 << 1, - OBJECT_COMPRESSED_ZSTD = 1 << 2, + OBJECT_COMPRESSED_XZ = 1 << 0, + OBJECT_COMPRESSED_LZ4 = 1 << 1, + OBJECT_COMPRESSED_ZSTD = 1 << 2, OBJECT_COMPRESSION_MASK = (OBJECT_COMPRESSED_XZ | OBJECT_COMPRESSED_LZ4 | OBJECT_COMPRESSED_ZSTD), - _OBJECT_COMPRESSED_MAX = OBJECT_COMPRESSION_MASK, + FIELD_UNIQUE = 1 << 3, + _OBJECT_COMPRESSED_MAX = OBJECT_COMPRESSION_MASK, }; struct ObjectHeader { @@ -111,7 +112,10 @@ typedef struct { le64_t xor_hash; \ union { \ EntryItem items[0]; \ - le64_t trie_offset; \ + struct { \ + le64_t trie_offset; \ + uint8_t payload[]; \ + }; \ }; \ } diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 94d3a78..53c3d2b 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -34,6 +34,7 @@ #include "string-util.h" #include "strv.h" #include "sync-util.h" +#include "unaligned.h" #include "xattr-util.h" #define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem)) @@ -632,7 +633,7 @@ static int journal_file_move_to( return mmap_cache_fd_get(f->cache_fd, type_to_context(type), keep_always, offset, size, &f->last_stat, ret); } -static uint64_t minimum_header_size(Object *o) { +static uint64_t minimum_header_size(JournalFile *f, Object *o) { static const uint64_t table[] = { [OBJECT_DATA] = sizeof(DataObject), @@ -646,6 +647,10 @@ static uint64_t minimum_header_size(Object *o) { [OBJECT_TRIE_HASH_TABLE] = sizeof(HashTableObject), }; + if (o->object.type == OBJECT_ENTRY) + return JOURNAL_HEADER_COMPACT(f->header) ? offsetof(Object, entry.payload) : + offsetof(Object, entry.items); + if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0) return sizeof(ObjectHeader); @@ -710,19 +715,13 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) sz = le64toh(READ_NOW(o->object.size)); if (JOURNAL_HEADER_COMPACT(f->header)) { - if (sz != sizeof(EntryObject)) + if (sz < offsetof(Object, entry.payload)) return log_debug_errno( SYNTHETIC_ERRNO(EBADMSG), "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64, - sizeof(EntryObject), + offsetof(Object, entry.payload), sz, offset); - - if (o->entry.trie_offset == 0) - return log_debug_errno( - SYNTHETIC_ERRNO(EBADMSG), - "Bad entry trie offset (== 0): %" PRIu64, - offset); } else { if (sz < offsetof(Object, entry.items) || (sz - offsetof(Object, entry.items)) % sizeof(EntryItem) != 0) @@ -879,7 +878,7 @@ int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset "Attempt to move to object with invalid type: %" PRIu64, offset); - if (s < minimum_header_size(o)) + if (s < minimum_header_size(f, o)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Attempt to move to truncated object: %" PRIu64, offset); @@ -951,12 +950,12 @@ int journal_file_read_object_header(JournalFile *f, ObjectType type, uint64_t of "Attempt to read object with invalid type: %" PRIu64, offset); - if (s < minimum_header_size(&o)) + if (s < minimum_header_size(f, &o)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Attempt to read truncated object: %" PRIu64, offset); - if ((size_t) n < minimum_header_size(&o)) + if ((size_t) n < minimum_header_size(f, &o)) return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading object: %" PRIu64, offset); @@ -1683,15 +1682,32 @@ static int journal_file_append_field( return 0; } +static int maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, size_t size, size_t *rsize) { + int compression = 0; + +#if HAVE_COMPRESSION + if (JOURNAL_FILE_COMPRESS(f) && size >= f->compress_threshold_bytes) { + compression = compress_blob(src, size, dst, size - 1, rsize); + + if (compression > 0) + log_debug("Compressed data object %zu -> %zu using %s", size, *rsize, + object_compressed_to_string(compression)); + } +#endif + + return compression; +} + static int journal_file_append_data( JournalFile *f, const void *data, uint64_t size, Object **ret, uint64_t *ret_offset) { - uint64_t hash, p, fp, osize; - Object *o, *fo; - int r, compression = 0; - const void *eq; + uint64_t hash, p, osize; + Object *o; + size_t rsize = 0; + int compression = 0; + int r; assert(f); @@ -1706,10 +1722,6 @@ static int journal_file_append_data( if (r > 0) return 0; - eq = memchr(data, '=', size); - if (!eq) - return -EINVAL; - osize = offsetof(Object, data.payload) + size; r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p); if (r < 0) @@ -1717,58 +1729,37 @@ static int journal_file_append_data( o->data.hash = htole64(hash); -#if HAVE_COMPRESSION - if (JOURNAL_FILE_COMPRESS(f) && size >= f->compress_threshold_bytes) { - size_t rsize = 0; - - compression = compress_blob(data, size, o->data.payload, size - 1, &rsize); - - if (compression >= 0) { - o->object.size = htole64(offsetof(Object, data.payload) + rsize); - o->object.flags |= compression; - - log_debug("Compressed data object %"PRIu64" -> %zu using %s", - size, rsize, object_compressed_to_string(compression)); - } else - /* Compression didn't work, we don't really care why, let's continue without compression */ - compression = 0; - } -#endif + compression = maybe_compress_payload(f, o->data.payload, data, size, &rsize); - if (compression == 0) + if (compression > 0) { + o->object.size = htole64(offsetof(Object, data.payload) + rsize); + o->object.flags |= compression; + } else memcpy_safe(o->data.payload, data, size); r = journal_file_link_data(f, o, p, hash); if (r < 0) return r; - /* The linking might have altered the window, so let's refresh our pointer. */ - r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); - if (r < 0) - return r; + /* The linking might have altered the window, so let's only pass the offset to hmac which will + * move to the object again if needed. */ #if HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p); + r = journal_file_hmac_put_object(f, OBJECT_DATA, NULL, p); if (r < 0) return r; #endif - /* Create field object ... */ - r = journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, &fo, &fp); - if (r < 0) - return r; - - /* ... and link it in. */ - o->data.next_field_offset = fo->field.head_data_offset; - fo->field.head_data_offset = le64toh(p); - - if (ret) - *ret = o; + if (ret) { + r = journal_file_move_to_object(f, OBJECT_DATA, p, ret); + if (r < 0) + return r; + } if (ret_offset) *ret_offset = p; - return 0; + return 1; } static int journal_file_append_trie_node( @@ -1910,7 +1901,7 @@ int journal_file_data_payload( ret_size); } -static int journal_file_entry_item_next_compact( +static int journal_file_entry_item_next_trie( JournalFile *f, Object *e, uint64_t offset, @@ -1925,14 +1916,7 @@ static int journal_file_entry_item_next_compact( uint64_t p; int r; - if (*i == UINT64_MAX) - return 0; - - p = *i == 0 ? le64toh(e->entry.trie_offset) : *i; - if (p == 0) - return -EBADMSG; - - for (; p != 0;) { + for (p = *i; p != 0;) { Object *o; uint64_t q; @@ -1968,21 +1952,151 @@ static int journal_file_entry_item_next_compact( if (ret_offset) *ret_offset = q; - /* If we've iterated all trie nodes, set the iterator to UINT64_MAX to indicate this. We - * can't use zero as zero is reserved for starting iteration from the beginning. */ - if (p == 0) - p = UINT64_MAX; + *i = p; + + return 1; + } + + *i = p; + + return 0; +} + +static int journal_file_entry_item_next_inline( + JournalFile *f, + Object *e, + uint64_t offset, + uint64_t *i, + const char *field, + size_t field_length, + size_t data_threshold, + uint64_t *ret_offset, + void **ret_data, + size_t *ret_size) { + + uint64_t p, sz; + int r; + + sz = le64toh(READ_NOW(e->object.size)); + if (sz < offsetof(Object, entry.payload)) + return -EBADMSG; + + for (p = *i; p < offset + sz;) { + uint8_t *d; + uint8_t flags; + uint64_t isz; + + /* `i` stores the absolute offset of the current inline entry item. We convert it to an + * offset relative to the `payload` field of the entry object and add it to the `payload` + * field to get a pointer to the current inline entry item. */ + d = e->entry.payload + p - offset - offsetof(Object, entry.payload); + + p += sizeof(uint8_t) + sizeof(uint32_t); + if (p >= offset + sz) + return -EBADMSG; + + flags = *d++; + isz = unaligned_read_le32(d); + d += sizeof(uint32_t); + + p += isz; + if (p > offset + sz) + return -EBADMSG; + + r = maybe_decompress_payload( + f, + d, + isz, + flags & OBJECT_COMPRESSION_MASK, + field, + field_length, + data_threshold, + ret_data, + ret_size); + if (r == -EBADMSG) { + log_debug("Inline entry item has bad payload, skipping over it."); + continue; + } + if (r < 0) + return r; + if (r == 0) + continue; + + if (ret_offset) + *ret_offset = 0; *i = p; return 1; } - *i = UINT64_MAX; + *i = p; return 0; } +static int journal_file_entry_item_next_compact( + JournalFile *f, + Object *e, + uint64_t offset, + uint64_t *i, + const char *field, + size_t field_length, + size_t data_threshold, + uint64_t *ret_offset, + void **ret_data, + size_t *ret_size) { + + uint64_t p, sz; + int r; + + if (*i == UINT64_MAX) + return 0; + + sz = le64toh(READ_NOW(e->object.size)); + if (sz < offsetof(Object, entry.payload)) + return -EBADMSG; + + p = *i == 0 ? le64toh(READ_NOW(e->entry.trie_offset)) : *i; + + /* All of an entry's trie and data nodes are located before the entry object in the journal file. */ + if (p > offset + sz) + return -EBADMSG; + + /* If the iterator is located inside the entry object's payload, we're already iterating the inline + * entry items so we skip the trie node logic. */ + if (p < offset) { + r = journal_file_entry_item_next_trie( + f, e, offset, &p, field, field_length, data_threshold, ret_offset, ret_data, ret_size); + if (r < 0) + return r; + + /* If we've iterated all the trie nodes, set the iterator to the start of the inline entry + * items. */ + if (p == 0) + p = offset + offsetof(Object, entry.payload); + + if (r > 0) { + *i = p; + return r; + } + } + + r = journal_file_entry_item_next_inline( + f, e, offset, &p, field, field_length, data_threshold, ret_offset, ret_data, ret_size); + if (r < 0) + return r; + + /* If we finished with all the inline entry items, set the iterator to UINT64_MAX to indicate that + * we've finished iterating all the entry items. */ + if (p == offset + sz) + p = UINT64_MAX; + + *i = p; + + return r; +} + static int journal_file_entry_item_next_non_compact( JournalFile *f, Object *e, @@ -2046,7 +2160,8 @@ int journal_file_entry_item_next( /* Iterates over the entry items of the given entry. The output parameters return data about the Data * object pointed at by the next entry item if requested. * - * - If `ret_offset` is not NULL, it is set to the offset of the Data object + * - If `ret_offset` is not NULL, it is set to the offset of the Data object. If the data is stored + * inline in the entry object, `ret_offset` is set to 0. * - If `ret_data` is not NULL, it is set to a pointer to the decompressed payload of the Data object * - If `ret_size` is not NULL, it is set to the size of the decompressed payload of the Data object * @@ -2386,6 +2501,7 @@ static int journal_file_append_entry_internal( const dual_timestamp *ts, const sd_id128_t *boot_id, const EntryItemEx items[], size_t n_items, + const struct iovec inlined[], size_t n_inlined, uint64_t *seqnum, Object **ret, uint64_t *ret_offset) { uint64_t np, osize, parent_offset = 0, xor_hash = 0; @@ -2397,6 +2513,9 @@ static int journal_file_append_entry_internal( assert(items || n_items == 0); assert(ts); + if (!JOURNAL_HEADER_COMPACT(f->header)) + assert(n_inlined == 0); + for (uint64_t i = 0; i < n_items; i++) xor_hash ^= items[i].xor_hash; @@ -2428,18 +2547,49 @@ static int journal_file_append_entry_internal( } osize = JOURNAL_HEADER_COMPACT(f->header) ? - sizeof(EntryObject) : + offsetof(Object, entry.payload) : offsetof(Object, entry.items) + (n_items * sizeof(EntryItem)); + for (unsigned i = 0; i < n_inlined; i++) { + xor_hash ^= jenkins_hash64(inlined[i].iov_base, inlined[i].iov_len); + osize += sizeof(uint8_t) + sizeof(uint32_t) + inlined[i].iov_len; + } + r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np); if (r < 0) return r; o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum)); - if (JOURNAL_HEADER_COMPACT(f->header)) + if (JOURNAL_HEADER_COMPACT(f->header)) { + uint8_t *p = o->entry.payload; o->entry.trie_offset = htole64(parent_offset); - else + + for (unsigned i = 0; i < n_inlined; i++) { + int compression = 0; + size_t rsize; + + /* The format per inlined item is: flags (8-bit), size (32-bit), data (optionally + * compressed). */ + + compression = maybe_compress_payload(f, p + sizeof(uint8_t) + sizeof(uint32_t), + inlined[i].iov_base, inlined[i].iov_len, &rsize); + + if (compression > 0) { + *p++ = compression; + unaligned_write_le32(p, rsize); + p += sizeof(uint32_t) + rsize; + } else { + *p++ = 0; + unaligned_write_le32(p, inlined[i].iov_len); + p += sizeof(uint32_t); + memcpy_safe(p, inlined[i].iov_base, inlined[i].iov_len); + p += inlined[i].iov_len; + } + } + + o->object.size = htole64(offsetof(Object, entry.payload) + (p - o->entry.payload)); + } else for (size_t i = 0; i < n_items; i++) o->entry.items[i] = (EntryItem){ .object_offset = htole64(items[i].object_offset), .hash = htole64(items[i].hash) }; @@ -2569,6 +2719,21 @@ static size_t remove_duplicate_entry_items(EntryItemEx items[], size_t n) { return j; } +static int journal_file_append_field_from_data( + JournalFile *f, + const char *data, + size_t size, + Object **ret, + uint64_t *ret_offset) { + const void *eq; + + eq = memchr(data, '=', size); + if (!eq) + return -EINVAL; + + return journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, ret, ret_offset); +} + int journal_file_append_entry( JournalFile *f, const dual_timestamp *ts, @@ -2578,6 +2743,8 @@ int journal_file_append_entry( Object **ret, uint64_t *ret_offset) { EntryItemEx *items; + struct iovec *inlined; + size_t n_items = 0, n_inlined = 0; struct dual_timestamp _ts; int r; @@ -2606,15 +2773,31 @@ int journal_file_append_entry( #endif items = newa(EntryItemEx, n_iovec); + inlined = newa(struct iovec, n_iovec); for (size_t i = 0; i < n_iovec; i++) { uint64_t p; - Object *o; + Object *o, *fo; + + r = journal_file_append_field_from_data(f, iovec[i].iov_base, iovec[i].iov_len, &fo, NULL); + if (r < 0) + return r; + + if (FLAGS_SET(fo->object.flags, FIELD_UNIQUE)) { + inlined[n_inlined++] = iovec[i]; + continue; + } r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p); if (r < 0) return r; + if (r > 0) { + /* Link data object into the field object. */ + o->data.next_field_offset = fo->field.head_data_offset; + fo->field.head_data_offset = le64toh(p); + } + /* When calculating the XOR hash field, we need to take special care if the "keyed-hash" * journal file flag is on. We use the XOR hash field to quickly determine the identity of a * specific record, and give records with otherwise identical position (i.e. match in seqno, @@ -2624,7 +2807,7 @@ int journal_file_append_entry( * are completely identical (they include the XOR hash after all). For classic Jenkins-hash * files things are easier, we can just take the value from the stored record directly. */ - items[i] = (EntryItemEx){ + items[n_items++] = (EntryItemEx){ .object_offset = p, .hash = le64toh(o->data.hash), .xor_hash = JOURNAL_HEADER_KEYED_HASH(f->header) ? @@ -2635,10 +2818,11 @@ int journal_file_append_entry( /* Order by the position on disk, in order to improve seek * times for rotating media. */ - typesafe_qsort(items, n_iovec, entry_item_cmp); - n_iovec = remove_duplicate_entry_items(items, n_iovec); + typesafe_qsort(items, n_items, entry_item_cmp); + n_items = remove_duplicate_entry_items(items, n_items); - r = journal_file_append_entry_internal(f, ts, boot_id, items, n_iovec, seqnum, ret, ret_offset); + r = journal_file_append_entry_internal( + f, ts, boot_id, items, n_items, inlined, n_inlined, seqnum, ret, ret_offset); /* If the memory mapping triggered a SIGBUS then we return an * IO error and ignore the error code passed down to us, since @@ -3884,6 +4068,43 @@ static int journal_file_warn_btrfs(JournalFile *f) { return 1; } +static int add_unique_fields(JournalFile *f) { + const char *e; + int r; + + e = getenv("SYSTEMD_JOURNAL_UNIQUE_FIELDS"); + if (!e) + e = "MESSAGE"; + + for (const char *p = e;;) { + Object *o; + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + return 0; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNALD_UNIQUE_FIELDS environment variable, ignoring: %m"); + return 0; + } + + if (!journal_field_valid(word, strlen(word), true)) { + log_debug("Invalid field name in $SYSTEMD_JOURNALD_UNIQUE_FIELDS environment variable, ignoring: %s", word); + continue; + } + + r = journal_file_append_field(f, word, strlen(word), &o, NULL); + if (r < 0) + return r; + + o->object.flags |= FIELD_UNIQUE; + } + + return 0; +} + int journal_file_open( int fd, const char *fname, @@ -4121,6 +4342,10 @@ int journal_file_open( r = journal_file_setup_trie_hash_table(f); if (r < 0) goto fail; + + r = add_unique_fields(f); + if (r < 0) + goto fail; } #if HAVE_GCRYPT @@ -4238,6 +4463,8 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 const sd_id128_t *boot_id; dual_timestamp ts; EntryItemEx *items; + struct iovec *inlined; + size_t n_items = 0, n_inlined = 0; int r; assert(from); @@ -4265,27 +4492,53 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 } items = newa(EntryItemEx, n); + inlined = newa(struct iovec, n); for (uint64_t i = 0, j = 0;; j++) { uint64_t h; void *data; size_t l; - Object *u; + Object *u, *fo; r = journal_file_entry_item_next(from, o, p, &i, NULL, 0, 0, NULL, &data, &l); if (r < 0) - return r; + goto finish; if (r == 0) break; if (l == 0) return -EBADMSG; + r = journal_file_append_field_from_data(to, data, l, &fo, NULL); + if (r < 0) + goto finish; + + if (FLAGS_SET(fo->object.flags, FIELD_UNIQUE)) { + struct iovec iovec = { + .iov_base = memdup(data, l), + .iov_len = l, + }; + + if (!iovec.iov_base) { + r = -ENOMEM; + goto finish; + } + + inlined[n_inlined++] = iovec; + continue; + } + r = journal_file_append_data(to, data, l, &u, &h); if (r < 0) - return r; + goto finish; - items[j] = (EntryItemEx){ + if (r > 0) { + /* Link data object into the field object. */ + u->data.next_field_offset = fo->field.head_data_offset; + fo->field.head_data_offset = le64toh(h); + } + + items[n_items++] = (EntryItemEx){ .object_offset = h, .hash = le64toh(u->data.hash), .xor_hash = JOURNAL_HEADER_KEYED_HASH(to->header) ? jenkins_hash64(data, l) : @@ -4293,7 +4546,12 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 }; } - r = journal_file_append_entry_internal(to, &ts, boot_id, items, n, NULL, NULL, NULL); + r = journal_file_append_entry_internal( + to, &ts, boot_id, items, n_items, inlined, n_inlined, NULL, NULL, NULL); + +finish: + for (size_t i = 0; i < n_inlined; i++) + free(inlined[i].iov_base); if (mmap_cache_fd_got_sigbus(to->cache_fd)) return -EIO; diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index fe7e965..6bb7047 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -238,19 +238,14 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o case OBJECT_ENTRY: if (JOURNAL_HEADER_COMPACT(f->header)) { - if (le64toh(o->object.size) != sizeof(EntryObject)) { + if (le64toh(o->object.size) < offsetof(Object, entry.payload)) { error(offset, "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64, - sizeof(EntryObject), + offsetof(Object, entry.payload), le64toh(o->object.size), offset); return -EBADMSG; } - - if (o->entry.trie_offset == 0) { - error(offset, "Bad entry trie offset (== 0): %" PRIu64, offset); - return -EBADMSG; - } } else { if ((le64toh(o->object.size) - offsetof(Object, entry.items)) % sizeof(EntryItem) != 0) { error(offset, @@ -301,7 +296,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if (r == 0) break; - if (p == 0 || !VALID64(p)) { + if (!VALID64(p)) { error(offset, "Invalid entry item (%"PRIu64" offset: "OFSfmt, i, p); return -EBADMSG; } @@ -694,7 +689,7 @@ static int verify_entry( error_errno(p, r, "Invalid entry item of entry"); return r; } - if (r == 0) + if (r == 0 || q == 0) break; if (!contains_uint64(cache_data_fd, n_data, q)) { From ba6b85fb606f57e7cb300ade15e095412207606b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 11/15] journal: Add support for not indexing specific fields in compact mode Entry arrays remain one of the big contributors to journal disk usage (even after introducing 32-bit offsets). To improve the situation, let's add an environment variable $SYSTEMD_JOURNAL_NON_INDEXED_FIELDS that allows configuring which fields should not be indexed. Data object of fields that are not indexed simply don't have any entry offsets added to their entry arrays. This has the effect that when used as matches, these data objects don't return any entry matches at all. Before: OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 1022925 95.3M Field 2808 151.3K Entry 3499976 667.7M Data Hash Table 13 46.2M Field Hash Table 13 67.8K Entry Array 492907 576.7M Tag 0 0B Trie Node 1758648 67.0M Trie Hash Table 13 69.3M Total 6777303 1.4G After: SYSTEMD_JOURNAL_NON_INDEXED_FIELDS="_CAP_EFFECTIVE _PID _UID _GID _SELINUX_CONTEXT _SOURCE_REALTIME_TIMESTAMP _MACHINE_ID _HOSTNAME _STREAM_ID _LINE_BREAK SYSLOG_FACILITY SYSLOG_IDENTIFIER _COMM _CMDLINE _EXE _SYSTEMD_INVOCATION_ID" OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 987596 92.5M Field 2127 114.8K Entry 3500464 667.8M Data Hash Table 9 32.0M Field Hash Table 9 46.9K Entry Array 241932 200.3M Tag 0 0B Trie Node 1752229 66.8M Trie Hash Table 9 47.9M Total 6484375 1.0G --- diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index d2bbb3c..eef3f46 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -52,6 +52,7 @@ enum { OBJECT_COMPRESSED_ZSTD = 1 << 2, OBJECT_COMPRESSION_MASK = (OBJECT_COMPRESSED_XZ | OBJECT_COMPRESSED_LZ4 | OBJECT_COMPRESSED_ZSTD), FIELD_UNIQUE = 1 << 3, + FIELD_INDEXED = 1 << 4, _OBJECT_COMPRESSED_MAX = OBJECT_COMPRESSION_MASK, }; @@ -101,6 +102,7 @@ typedef struct { uint64_t hash; /* The hash used to calculate the Entry object's XOR hash field. */ uint64_t xor_hash; + bool indexed; } EntryItemEx; #define EntryObject__contents { \ diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 53c3d2b..2908b05 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1679,7 +1679,7 @@ static int journal_file_append_field( if (ret_offset) *ret_offset = p; - return 0; + return 1; } static int maybe_compress_payload(JournalFile *f, uint8_t *dst, const uint8_t *src, size_t size, size_t *rsize) { @@ -2482,6 +2482,9 @@ static int journal_file_link_entry( for (uint64_t i = 0; i < n_items; i++) { int k; + if (!items[i].indexed) + continue; + /* If we fail to link an entry item because we can't allocate a new entry array, don't fail * immediately but try to link the other entry items since it might still be possible to link * those if they don't require a new entry array to be allocated. */ @@ -2726,12 +2729,21 @@ static int journal_file_append_field_from_data( Object **ret, uint64_t *ret_offset) { const void *eq; + int r; eq = memchr(data, '=', size); if (!eq) return -EINVAL; - return journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, ret, ret_offset); + r = journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, ret, ret_offset); + if (r < 0) + return r; + + /* In compact mode, only index newly added fields. */ + if (JOURNAL_HEADER_COMPACT(f->header) && r > 0) + (*ret)->object.flags |= FIELD_INDEXED; + + return r; } int journal_file_append_entry( @@ -2813,6 +2825,8 @@ int journal_file_append_entry( .xor_hash = JOURNAL_HEADER_KEYED_HASH(f->header) ? jenkins_hash64(iovec[i].iov_base, iovec[i].iov_len) : le64toh(o->data.hash), + .indexed = !JOURNAL_HEADER_COMPACT(f->header) || + FLAGS_SET(fo->object.flags, FIELD_INDEXED), }; } @@ -4105,6 +4119,43 @@ static int add_unique_fields(JournalFile *f) { return 0; } +static int add_non_indexed_fields(JournalFile *f) { + const char *e; + int r; + + e = getenv("SYSTEMD_JOURNAL_NON_INDEXED_FIELDS"); + if (!e) + return 0; + + for (const char *p = e;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + return 0; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNALD_NON_INDEXED_FIELDS environment variable, ignoring: %m"); + return 0; + } + + if (!journal_field_valid(word, strlen(word), true)) { + log_debug("Invalid field name in $SYSTEMD_JOURNALD_NON_INDEXED_FIELDS environment variable, ignoring: %s", word); + continue; + } + + /* By default, all fields are created with the FIELD_INDEXED flag, indicating they should be + * indexed. By creating the fields here but not setting the FIELD_INDEXED flag, we make sure + * they aren't indexed. */ + r = journal_file_append_field(f, word, strlen(word), NULL, NULL); + if (r < 0) + return r; + } + + return 0; +} + int journal_file_open( int fd, const char *fname, @@ -4346,6 +4397,10 @@ int journal_file_open( r = add_unique_fields(f); if (r < 0) goto fail; + + r = add_non_indexed_fields(f); + if (r < 0) + goto fail; } #if HAVE_GCRYPT @@ -4543,6 +4598,8 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 .hash = le64toh(u->data.hash), .xor_hash = JOURNAL_HEADER_KEYED_HASH(to->header) ? jenkins_hash64(data, l) : le64toh(u->data.hash), + .indexed = !JOURNAL_HEADER_COMPACT(to->header) || + FLAGS_SET(fo->object.flags, FIELD_INDEXED), }; } diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index 6bb7047..b90f078 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -159,7 +159,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o uint64_t h1, h2; int r; - if (le64toh(o->data.entry_offset) == 0) + if (!JOURNAL_HEADER_COMPACT(f->header) && le64toh(o->data.entry_offset) == 0) warning(offset, "Unused data (entry_offset==0)"); if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) { From d5f18540b613f9b98a6d28aeb2627774b914f8ef Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 12/15] journal: Add boot ID deduplication in compact mode sd_id128_t objects take up 16 bytes per entry object. If we replace them with an offset to a boot id object, we reduce the overhead to 4 bytes per entry. On top of this, the change allows us to make the trie_offset field 32-bit as well. In total, this allows us to save 16 bytes per Entry object. Before: SYSTEMD_JOURNAL_NON_INDEXED_FIELDS="_CAP_EFFECTIVE _PID _UID _GID _SELINUX_CONTEXT _SOURCE_REALTIME_TIMESTAMP _MACHINE_ID _HOSTNAME _STREAM_ID _LINE_BREAK SYSLOG_FACILITY SYSLOG_IDENTIFIER _COMM _CMDLINE _EXE _SYSTEMD_INVOCATION_ID" OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 987596 92.5M Field 2127 114.8K Entry 3500464 667.8M Data Hash Table 9 32.0M Field Hash Table 9 46.9K Entry Array 241932 200.3M Tag 0 0B Trie Node 1752229 66.8M Trie Hash Table 9 47.9M Total 6484375 1.0G After: SYSTEMD_JOURNAL_NON_INDEXED_FIELDS="_CAP_EFFECTIVE _PID _UID _GID _SELINUX_CONTEXT _SOURCE_REALTIME_TIMESTAMP _MACHINE_ID _HOSTNAME _STREAM_ID _LINE_BREAK SYSLOG_FACILITY SYSLOG_IDENTIFIER _COMM _CMDLINE _EXE _SYSTEMD_INVOCATION_ID" OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 985433 92.3M Field 2142 115.7K Entry 3500812 614.4M Data Hash Table 9 32.0M Field Hash Table 9 46.9K Entry Array 240841 191.2M Tag 0 0B Trie Node 1752652 66.8M Trie Hash Table 9 47.9M Boot ID 55 1.7K Total 6481962 1.0G --- diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c index bfc1cf2..40c2a6b 100644 --- a/src/journal/test-journal.c +++ b/src/journal/test-journal.c @@ -31,7 +31,7 @@ static void test_non_empty(void) { static const char test[] = "TEST1=1", test2[] = "TEST2=2"; Object *o, *d; uint64_t p; - sd_id128_t fake_boot_id; + sd_id128_t fake_boot_id, boot_id; char t[] = "/var/tmp/journal-XXXXXX"; test_setup_logging(LOG_DEBUG); @@ -68,7 +68,8 @@ static void test_non_empty(void) { assert_se(journal_file_next_entry(f->file, p, DIRECTION_DOWN, &o, &p) == 1); assert_se(le64toh(o->entry.seqnum) == 3); - assert_se(sd_id128_equal(o->entry.boot_id, fake_boot_id)); + assert_se(journal_file_entry_boot_id(f->file, o, &boot_id) == 0); + assert_se(sd_id128_equal(boot_id, fake_boot_id)); assert_se(journal_file_next_entry(f->file, p, DIRECTION_DOWN, &o, &p) == 0); diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index eef3f46..7533e93 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -24,6 +24,7 @@ typedef struct HashTableObject HashTableObject; typedef struct EntryArrayObject EntryArrayObject; typedef struct TagObject TagObject; typedef struct TrieNodeObject TrieNodeObject; +typedef struct BootIdObject BootIdObject; typedef struct EntryItem EntryItem; typedef struct HashItem HashItem; @@ -42,6 +43,7 @@ typedef enum ObjectType { OBJECT_TAG, OBJECT_TRIE_NODE, OBJECT_TRIE_HASH_TABLE, + OBJECT_BOOT_ID, _OBJECT_TYPE_MAX } ObjectType; @@ -105,20 +107,24 @@ typedef struct { bool indexed; } EntryItemEx; -#define EntryObject__contents { \ - ObjectHeader object; \ - le64_t seqnum; \ - le64_t realtime; \ - le64_t monotonic; \ - sd_id128_t boot_id; \ - le64_t xor_hash; \ - union { \ - EntryItem items[0]; \ - struct { \ - le64_t trie_offset; \ - uint8_t payload[]; \ - }; \ - }; \ +#define EntryObject__contents { \ + ObjectHeader object; \ + le64_t seqnum; \ + le64_t realtime; \ + le64_t monotonic; \ + union { \ + struct { \ + sd_id128_t boot_id; \ + le64_t xor_hash; \ + EntryItem items[]; \ + }; \ + struct { \ + le64_t xor_hash_compact; \ + le32_t boot_id_offset; \ + le32_t trie_offset; \ + uint8_t payload[]; \ + }; \ + }; \ } struct EntryObject EntryObject__contents; @@ -165,6 +171,15 @@ struct TrieNodeObject TrieNodeObject__contents; struct TrieNodeObject__packed TrieNodeObject__contents _packed_; assert_cc(sizeof(struct TrieNodeObject) == sizeof(struct TrieNodeObject__packed)); +#define BootIdObject__contents { \ + ObjectHeader object; \ + sd_id128_t value; \ +} + +struct BootIdObject BootIdObject__contents; +struct BootIdObject__packed BootIdObject__contents _packed_; +assert_cc(sizeof(struct BootIdObject) == sizeof(struct BootIdObject__packed)); + union Object { ObjectHeader object; DataObject data; @@ -174,6 +189,7 @@ union Object { EntryArrayObject entry_array; TagObject tag; TrieNodeObject trie_node; + BootIdObject boot_id; }; enum { @@ -259,12 +275,13 @@ enum { le64_t trie_hash_table_size; \ le64_t n_trie_nodes; \ le64_t trie_hash_chain_depth; \ + le64_t boot_id_offset; \ } struct Header struct_Header__contents; struct Header__packed struct_Header__contents _packed_; assert_cc(sizeof(struct Header) == sizeof(struct Header__packed)); -assert_cc(sizeof(struct Header) == 288); +assert_cc(sizeof(struct Header) == 296); #define FSS_HEADER_SIGNATURE \ ((const char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }) diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 2908b05..52f86d1 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -326,7 +326,34 @@ static int journal_file_init_header(JournalFile *f, JournalFile *template) { return 0; } +static int journal_file_refresh_boot_id(JournalFile *f, const sd_id128_t *boot_id) { + Object *o; + uint64_t p; + int r; + + assert(boot_id); + + if (!JOURNAL_HEADER_COMPACT(f->header)) { + f->header->boot_id = *boot_id; + return 0; + } + + if (sd_id128_equal(*boot_id, f->header->boot_id) && le64toh(f->header->boot_id_offset) > 0) + return 0; + + r = journal_file_append_object(f, OBJECT_BOOT_ID, sizeof(BootIdObject), &o, &p); + if (r < 0) + return r; + + o->boot_id.value = *boot_id; + f->header->boot_id_offset = htole64(p); + f->header->boot_id = *boot_id; + + return 0; +} + static int journal_file_refresh_header(JournalFile *f) { + sd_id128_t boot_id; int r; assert(f); @@ -339,7 +366,11 @@ static int journal_file_refresh_header(JournalFile *f) { else if (r < 0) return r; - r = sd_id128_get_boot(&f->header->boot_id); + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + r = journal_file_refresh_boot_id(f, &boot_id); if (r < 0) return r; @@ -2057,7 +2088,7 @@ static int journal_file_entry_item_next_compact( if (sz < offsetof(Object, entry.payload)) return -EBADMSG; - p = *i == 0 ? le64toh(READ_NOW(e->entry.trie_offset)) : *i; + p = *i == 0 ? le32toh(READ_NOW(e->entry.trie_offset)) : *i; /* All of an entry's trie and data nodes are located before the entry object in the journal file. */ if (p > offset + sz) @@ -2200,6 +2231,26 @@ int journal_file_entry_item_next( f, e, offset, i, field, field_length, data_threshold, ret_offset, ret_data, ret_size); } +uint64_t journal_file_entry_xor_hash(JournalFile *f, Object *o) { + return JOURNAL_HEADER_COMPACT(f->header) ? le64toh(o->entry.xor_hash_compact) : + le64toh(o->entry.xor_hash); +} + +int journal_file_entry_boot_id(JournalFile *f, Object *o, sd_id128_t *ret_boot_id) { + int r; + + if (JOURNAL_HEADER_COMPACT(f->header)) { + r = journal_file_move_to_object(f, OBJECT_BOOT_ID, le32toh(o->entry.boot_id_offset), &o); + if (r < 0) + return r; + + *ret_boot_id = o->boot_id.value; + } else + *ret_boot_id = o->entry.boot_id; + + return 0; +} + uint64_t journal_file_entry_array_n_items(JournalFile *f, Object *o) { uint64_t sz; @@ -2566,7 +2617,7 @@ static int journal_file_append_entry_internal( if (JOURNAL_HEADER_COMPACT(f->header)) { uint8_t *p = o->entry.payload; - o->entry.trie_offset = htole64(parent_offset); + o->entry.trie_offset = htole32(parent_offset); for (unsigned i = 0; i < n_inlined; i++) { int compression = 0; @@ -2599,10 +2650,20 @@ static int journal_file_append_entry_internal( o->entry.realtime = htole64(ts->realtime); o->entry.monotonic = htole64(ts->monotonic); - o->entry.xor_hash = htole64(xor_hash); - if (boot_id) - f->header->boot_id = *boot_id; - o->entry.boot_id = f->header->boot_id; + + if (boot_id) { + r = journal_file_refresh_boot_id(f, boot_id); + if (r < 0) + return r; + } + + if (JOURNAL_HEADER_COMPACT(f->header)) { + o->entry.xor_hash_compact = htole64(xor_hash); + o->entry.boot_id_offset = htole32(le64toh(f->header->boot_id_offset)); + } else { + o->entry.xor_hash = htole64(xor_hash); + o->entry.boot_id = f->header->boot_id; + } #if HAVE_GCRYPT r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np); @@ -3568,14 +3629,21 @@ void journal_file_reset_location(JournalFile *f) { f->current_xor_hash = 0; } -void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset) { +int journal_file_save_location(JournalFile *f, Object *o, uint64_t offset) { + int r; + + r = journal_file_entry_boot_id(f, o, &f->current_boot_id); + if (r < 0) + return r; + f->location_type = LOCATION_SEEK; f->current_offset = offset; f->current_seqnum = le64toh(o->entry.seqnum); f->current_realtime = le64toh(o->entry.realtime); f->current_monotonic = le64toh(o->entry.monotonic); - f->current_boot_id = o->entry.boot_id; - f->current_xor_hash = le64toh(o->entry.xor_hash); + f->current_xor_hash = journal_file_entry_xor_hash(f, o); + + return 0; } int journal_file_compare_locations(JournalFile *af, JournalFile *bf) { @@ -4515,7 +4583,7 @@ int journal_file_dispose(int dir_fd, const char *fname) { int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p) { size_t n = 0; - const sd_id128_t *boot_id; + sd_id128_t boot_id; dual_timestamp ts; EntryItemEx *items; struct iovec *inlined; @@ -4534,7 +4602,10 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 .monotonic = le64toh(o->entry.monotonic), .realtime = le64toh(o->entry.realtime), }; - boot_id = &o->entry.boot_id; + + r = journal_file_entry_boot_id(from, o, &boot_id); + if (r < 0) + return r; for (uint64_t i = 0;;) { r = journal_file_entry_item_next(from, o, p, &i, NULL, 0, 0, NULL, NULL, NULL); @@ -4604,7 +4675,7 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 } r = journal_file_append_entry_internal( - to, &ts, boot_id, items, n_items, inlined, n_inlined, NULL, NULL, NULL); + to, &ts, &boot_id, items, n_items, inlined, n_inlined, NULL, NULL, NULL); finish: for (size_t i = 0; i < n_inlined; i++) @@ -4878,6 +4949,7 @@ static const char * const journal_object_type_table[] = { [OBJECT_TAG] = "tag", [OBJECT_TRIE_NODE] = "trie node", [OBJECT_TRIE_HASH_TABLE] = "trie hash table", + [OBJECT_BOOT_ID] = "boot id", }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(journal_object_type, ObjectType); diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 20187b8..6e431e9 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -218,6 +218,9 @@ int journal_file_entry_item_next( void **ret_data, size_t *ret_size); +uint64_t journal_file_entry_xor_hash(JournalFile *f, Object *o); +int journal_file_entry_boot_id(JournalFile *f, Object *o, sd_id128_t *ret_boot_id); + uint64_t journal_file_entry_array_n_items(JournalFile *f, Object *o) _pure_; uint64_t journal_file_entry_array_item(JournalFile *f, Object *o, size_t i) _pure_; uint64_t journal_file_hash_table_n_items(Object *o) _pure_; @@ -247,7 +250,7 @@ int journal_file_find_field_object(JournalFile *f, const void *field, uint64_t s int journal_file_find_field_object_with_hash(JournalFile *f, const void *field, uint64_t size, uint64_t hash, Object **ret, uint64_t *ret_offset); void journal_file_reset_location(JournalFile *f); -void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset); +int journal_file_save_location(JournalFile *f, Object *o, uint64_t offset); int journal_file_compare_locations(JournalFile *af, JournalFile *bf); int journal_file_next_entry(JournalFile *f, uint64_t p, direction_t direction, Object **ret, uint64_t *ret_offset); diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index b90f078..adb478d 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -1034,7 +1034,9 @@ int journal_file_verify( n_fields++; break; - case OBJECT_ENTRY: + case OBJECT_ENTRY: { + sd_id128_t boot_id; + if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) { error(p, "First entry before first tag"); r = -EBADMSG; @@ -1077,8 +1079,12 @@ int journal_file_verify( entry_seqnum = le64toh(o->entry.seqnum); entry_seqnum_set = true; + r = journal_file_entry_boot_id(f, o, &boot_id); + if (r < 0) + return r; + if (entry_monotonic_set && - sd_id128_equal(entry_boot_id, o->entry.boot_id) && + sd_id128_equal(entry_boot_id, boot_id) && entry_monotonic > le64toh(o->entry.monotonic)) { error(p, "Entry timestamp out of synchronization (%"PRIu64" > %"PRIu64")", @@ -1089,7 +1095,7 @@ int journal_file_verify( } entry_monotonic = le64toh(o->entry.monotonic); - entry_boot_id = o->entry.boot_id; + entry_boot_id = boot_id; entry_monotonic_set = true; if (!entry_realtime_set && @@ -1107,6 +1113,7 @@ int journal_file_verify( n_entries++; break; + } case OBJECT_DATA_HASH_TABLE: r = verify_hash_table(o, p, &n_data_hash_tables, diff --git a/src/libsystemd/sd-journal/mmap-cache.h b/src/libsystemd/sd-journal/mmap-cache.h index 7afb7f0..104b19d 100644 --- a/src/libsystemd/sd-journal/mmap-cache.h +++ b/src/libsystemd/sd-journal/mmap-cache.h @@ -5,7 +5,7 @@ #include /* One context per object type, plus one of the header, plus one "additional" one */ -#define MMAP_CACHE_MAX_CONTEXTS 11 +#define MMAP_CACHE_MAX_CONTEXTS 12 typedef struct MMapCache MMapCache; typedef struct MMapFileDescriptor MMapFileDescriptor; diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index 535dfdf..6b46903 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -111,32 +111,45 @@ static void detach_location(sd_journal *j) { journal_file_reset_location(f); } -static void init_location(Location *l, LocationType type, JournalFile *f, Object *o) { +static int init_location(Location *l, LocationType type, JournalFile *f, Object *o) { + sd_id128_t boot_id; + int r; + assert(l); assert(IN_SET(type, LOCATION_DISCRETE, LOCATION_SEEK)); assert(f); + r = journal_file_entry_boot_id(f, o, &boot_id); + if (r < 0) + return r; + *l = (Location) { .type = type, .seqnum = le64toh(o->entry.seqnum), .seqnum_id = f->header->seqnum_id, .realtime = le64toh(o->entry.realtime), .monotonic = le64toh(o->entry.monotonic), - .boot_id = o->entry.boot_id, - .xor_hash = le64toh(o->entry.xor_hash), + .boot_id = boot_id, + .xor_hash = journal_file_entry_xor_hash(f, o), .seqnum_set = true, .realtime_set = true, .monotonic_set = true, .xor_hash_set = true, }; + + return 0; } -static void set_location(sd_journal *j, JournalFile *f, Object *o) { +static int set_location(sd_journal *j, JournalFile *f, Object *o) { + int r; + assert(j); assert(f); assert(o); - init_location(&j->current_location, LOCATION_DISCRETE, f, o); + r = init_location(&j->current_location, LOCATION_DISCRETE, f, o); + if (r < 0) + return r; j->current_file = f; j->current_field = 0; @@ -144,6 +157,8 @@ static void set_location(sd_journal *j, JournalFile *f, Object *o) { /* Let f know its candidate entry was picked. */ assert(f->location_type == LOCATION_SEEK); f->location_type = LOCATION_DISCRETE; + + return 0; } static int match_is_valid(const void *data, size_t size) { @@ -781,7 +796,9 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc if (r <= 0) return r; - journal_file_save_location(f, c, cp); + r = journal_file_save_location(f, c, cp); + if (r < 0) + return r; } } else { f->last_direction = direction; @@ -790,7 +807,9 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc if (r <= 0) return r; - journal_file_save_location(f, c, cp); + r = journal_file_save_location(f, c, cp); + if (r < 0) + return r; } /* OK, we found the spot, now let's advance until an entry @@ -871,7 +890,9 @@ static int real_journal_next(sd_journal *j, direction_t direction) { if (r < 0) return r; - set_location(j, new_file, o); + r = set_location(j, new_file, o); + if (r < 0) + return r; return 1; } @@ -927,6 +948,7 @@ _public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) { } _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { + sd_id128_t boot_id; Object *o; int r; @@ -941,12 +963,16 @@ _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { if (r < 0) return r; + r = journal_file_entry_boot_id(j->current_file, o, &boot_id); + if (r < 0) + return r; + if (asprintf(cursor, "s=%s;i=%"PRIx64";b=%s;m=%"PRIx64";t=%"PRIx64";x=%"PRIx64, SD_ID128_TO_STRING(j->current_file->header->seqnum_id), le64toh(o->entry.seqnum), - SD_ID128_TO_STRING(o->entry.boot_id), le64toh(o->entry.monotonic), + SD_ID128_TO_STRING(boot_id), le64toh(o->entry.monotonic), le64toh(o->entry.realtime), - le64toh(o->entry.xor_hash)) < 0) + journal_file_entry_xor_hash(j->current_file, o)) < 0) return -ENOMEM; return 0; @@ -1054,8 +1080,9 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { } _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { - int r; + sd_id128_t boot_id; Object *o; + int r; assert_return(j, -EINVAL); assert_return(!journal_pid_changed(j), -ECHILD); @@ -1068,6 +1095,10 @@ _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { if (r < 0) return r; + r = journal_file_entry_boot_id(j->current_file, o, &boot_id); + if (r < 0) + return r; + for (;;) { _cleanup_free_ char *item = NULL; unsigned long long ll; @@ -1105,7 +1136,7 @@ _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { k = sd_id128_from_string(item+2, &id); if (k < 0) return k; - if (!sd_id128_equal(id, o->entry.boot_id)) + if (!sd_id128_equal(id, boot_id)) return 0; break; @@ -1126,7 +1157,7 @@ _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { case 'x': if (sscanf(item+2, "%llx", &ll) != 1) return -EINVAL; - if (ll != le64toh(o->entry.xor_hash)) + if (ll != journal_file_entry_xor_hash(j->current_file, o)) return 0; break; } @@ -2225,16 +2256,22 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12 if (r < 0) return r; - if (ret_boot_id) - *ret_boot_id = o->entry.boot_id; - else { - sd_id128_t id; + if (ret_boot_id) { + r = journal_file_entry_boot_id(f, o, ret_boot_id); + if (r < 0) + return r; + } else { + sd_id128_t id, boot_id; r = sd_id128_get_boot(&id); if (r < 0) return r; - if (!sd_id128_equal(id, o->entry.boot_id)) + r = journal_file_entry_boot_id(f, o, &boot_id); + if (r < 0) + return r; + + if (!sd_id128_equal(id, boot_id)) return -ESTALE; } From 129e2b80918133d1caf6839da57ff537ed155c32 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 13/15] journal: Reduce space allocated for data, trie hash tables in compact mode In compact mode, because we inline MESSAGE objects into the Entry object, we end up fewer data objects per file, about 2/3 of the amount of entries in a non-compact journal. Let's reduce the size of the data hash table and trie hash table accordingly. Number of data entries per file for system journal in compact mode: /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-0000000000000001-0005cdd5d27d2d94.journal: 113311 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-000000000004962b-0005ce4384695cd4.journal: 102223 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-0000000000298d1a-0005cfadbe650a0c.journal: 93671 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-00000000002053b6-0005cf6655ddb579.journal: 90780 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-00000000001bb51b-0005cf3a84e7a3bd.journal: 89281 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-00000000000927c6-0005ce9eb78f1b15.journal: 85873 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-00000000000dd673-0005cece4242847b.journal: 84350 /home/daandemeyer/projects/systemd/tmp/copy.journal: 79482 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-0000000000127578-0005cef1afc242ec.journal: 77679 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-000000000024f770-0005cf8941801623.journal: 75602 /home/daandemeyer/projects/systemd/tmp/copy@d5f0b421e7264c3e9e4b10780029fcd0-0000000000171c33-0005cf15e3b8d49c.journal: 71032 Number of data entries per file for system journal in non-compact mode: /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000001750b-0005ce128e01000b.journal: 155649 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-00000000001dddfb-0005cf3dd5768f32.journal: 154270 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000024e2ea-0005cf78e9c5ae9a.journal: 154196 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000012095c-0005cee439c98426.journal: 153614 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000030e309-0005cfdf9cbf0ee7.journal: 153251 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-00000000002e9274-0005cfc7147577e8.journal: 152944 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-00000000000fa98f-0005ced350dafd2c.journal: 152311 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-0000000000062908-0005ce4caed15dcf.journal: 150689 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-0000000000203056-0005cf573dedbe4d.journal: 150480 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-0000000000333ab9-0005cfef9c957367.journal: 150430 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-0000000000146366-0005cef557e2d20d.journal: 150216 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000022805d-0005cf683e77bc0a.journal: 149996 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-00000000002c2ebd-0005cfb62ab245c5.journal: 149568 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000016c06e-0005cf0785762604.journal: 149369 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-0000000000274ab8-0005cf8a01f4b69a.journal: 148248 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000029b8b2-0005cf9b05613234.journal: 147902 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-00000000000d564d-0005cec1af9c5432.journal: 147346 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000003d58e-0005ce3afd8524e2.journal: 147255 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-00000000000af51a-0005ceb0d3b90d23.journal: 146798 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-0000000000191537-0005cf196a6c4a8a.journal: 146645 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-00000000001b844e-0005cf2bbc072bd5.journal: 146572 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000009bc02-0005ce9c95ee4584.journal: 83220 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system.journal: 69055 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-000000000008cbdc-0005ce5ecffdd340.journal: 66396 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-0000000000009c46-0005ce0e3abbf007.journal: 56187 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-0000000000000001-0005cdd5d27d2d94.journal: 54650 /var/log/journal/9bc4fc1fd8c442b9817cd6fd77e03de6/system@2d0a2848c14f4df79fd48b9649fb2256-00000000002c2686-0005cfb628639e9f.journal: 5636 Before: SYSTEMD_JOURNAL_NON_INDEXED_FIELDS="_CAP_EFFECTIVE _PID _UID _GID _SELINUX_CONTEXT _SOURCE_REALTIME_TIMESTAMP _MACHINE_ID _HOSTNAME _STREAM_ID _LINE_BREAK SYSLOG_FACILITY SYSLOG_IDENTIFIER _COMM _CMDLINE _EXE _SYSTEMD_INVOCATION_ID" OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 985433 92.3M Field 2142 115.7K Entry 3500812 614.4M Data Hash Table 9 32.0M Field Hash Table 9 46.9K Entry Array 240841 191.2M Tag 0 0B Trie Node 1752652 66.8M Trie Hash Table 9 47.9M Boot ID 55 1.7K Total 6481962 1.0G After: SYSTEMD_JOURNAL_NON_INDEXED_FIELDS="_CAP_EFFECTIVE _PID _UID _GID _SELINUX_CONTEXT _SOURCE_REALTIME_TIMESTAMP _MACHINE_ID _HOSTNAME _STREAM_ID _LINE_BREAK SYSLOG_FACILITY SYSLOG_IDENTIFIER _COMM _CMDLINE _EXE _SYSTEMD_INVOCATION_ID" OBJECT TYPE ENTRIES SIZE Unused 0 0B Data 980599 92.0M Field 1948 105.2K Entry 3501263 614.5M Data Hash Table 8 18.9M Field Hash Table 8 41.7K Entry Array 238474 188.0M Tag 0 0B Trie Node 1753996 66.9M Trie Hash Table 8 28.4M Boot ID 53 1.6K Total 6476357 1009.0M --- diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 52f86d1..e4edab6 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1089,13 +1089,15 @@ int journal_file_append_object( } static int default_data_hash_table_size(JournalFile *f) { - uint64_t s; + uint64_t s, d; /* We estimate that we need 1 hash table entry per 768 bytes of journal file and we want to make sure * we never get beyond 75% fill level. Calculate the hash table size for the maximum file size based - * on these metrics. */ + * on these metrics. In compact, mode, we estimate we need 1 hash table entry per 1152 bytes of + * journal file. */ - s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem); + d = JOURNAL_HEADER_COMPACT(f->header) ? 1152 : 768; + s = (f->metrics.max_size * 4 / d / 3) * sizeof(HashItem); if (s < DEFAULT_DATA_HASH_TABLE_SIZE) s = DEFAULT_DATA_HASH_TABLE_SIZE; From aa44a6f311d7fc7ec7d4e1299beaa6a49a23c277 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 14/15] journal: Run unit tests with and without compact mode enabled --- diff --git a/src/journal/test-journal-flush.c b/src/journal/test-journal-flush.c index 6af819f..504361d 100644 --- a/src/journal/test-journal-flush.c +++ b/src/journal/test-journal-flush.c @@ -13,7 +13,7 @@ #include "path-util.h" #include "string-util.h" -int main(int argc, char *argv[]) { +static void run_test(int argc, char *argv[]) { _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; _cleanup_free_ char *fn = NULL; char dn[] = "/var/tmp/test-journal-flush.XXXXXX"; @@ -70,6 +70,12 @@ int main(int argc, char *argv[]) { unlink(fn); assert_se(rmdir(dn) == 0); +} + +int main(int argc, char *argv[]) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + run_test(argc, argv); - return 0; + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + run_test(argc, argv); } diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c index 2d86e2a..b1d5074 100644 --- a/src/journal/test-journal-interleaving.c +++ b/src/journal/test-journal-interleaving.c @@ -299,6 +299,10 @@ int main(int argc, char *argv[]) { test_skip(setup_sequential); test_skip(setup_interleaved); + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + test_sequence_numbers(); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); test_sequence_numbers(); return 0; diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c index 486a2f4..76ab8fb 100644 --- a/src/journal/test-journal-stream.c +++ b/src/journal/test-journal-stream.c @@ -184,11 +184,18 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - /* Run this test twice. Once with old hashing and once with new hashing */ + /* Run this test multiple times with different configurations of features. */ + + assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "0", 1) >= 0); + run_test(); + assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "1", 1) >= 0); run_test(); - assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "0", 1) >= 0); + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + run_test(); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); run_test(); return 0; diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c index 323e495..4e64ebf 100644 --- a/src/journal/test-journal-verify.c +++ b/src/journal/test-journal-verify.c @@ -56,7 +56,7 @@ static int raw_verify(const char *fn, const char *verification_key) { return r; } -int main(int argc, char *argv[]) { +static int run_test(int argc, char *argv[]) { _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; char t[] = "/var/tmp/journal-XXXXXX"; unsigned n; @@ -141,3 +141,11 @@ int main(int argc, char *argv[]) { return 0; } + +int main(int argc, char *argv[]) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + run_test(argc, argv); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + run_test(argc, argv); +} diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c index 40c2a6b..1310e00 100644 --- a/src/journal/test-journal.c +++ b/src/journal/test-journal.c @@ -259,6 +259,16 @@ int main(int argc, char *argv[]) { if (access("/etc/machine-id", F_OK) != 0) return log_tests_skipped("/etc/machine-id not found"); + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + + test_non_empty(); + test_empty(); +#if HAVE_COMPRESSION + test_min_compress_size(); +#endif + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + test_non_empty(); test_empty(); #if HAVE_COMPRESSION From abf3f6ecbf785439730d04307ccaf9c550697ef0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Feb 22 2022 10:43:08 +0000 Subject: [PATCH 15/15] journal: Turn adding entry array items into a O(1) operation from O(n) Previously, we'd iterate an entry array from start to end every time we added an entry offset to it. To speed up this operation, we cache the last entry array in the chain and how many items it contains. This allows the addition of an entry to the chain to be done in constant time instead of linear time as we don't have to iterate the entire chain anymore every time we add an entry. This reduces the time to copy my laptop's 4G journal from 70s before this commit to 43s after this commit. --- diff --git a/src/libsystemd/sd-journal/journal-authenticate.c b/src/libsystemd/sd-journal/journal-authenticate.c index 83cbf41..df8f1a0 100644 --- a/src/libsystemd/sd-journal/journal-authenticate.c +++ b/src/libsystemd/sd-journal/journal-authenticate.c @@ -248,7 +248,7 @@ int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uin case OBJECT_DATA: /* All but hash and payload are mutable */ gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); - gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload)); + gcry_md_write(f->hmac, journal_file_data_payload_field(f, o), le64toh(o->object.size) - journal_file_data_payload_offset(f)); break; case OBJECT_FIELD: diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index 7533e93..02950ee 100644 --- a/src/libsystemd/sd-journal/journal-def.h +++ b/src/libsystemd/sd-journal/journal-def.h @@ -74,8 +74,15 @@ struct ObjectHeader { le64_t entry_offset; /* the first array entry we store inline */ \ le64_t entry_array_offset; \ le64_t n_entries; \ - uint8_t payload[]; \ - } + union { \ + uint8_t payload[0]; \ + struct { \ + le32_t tail_entry_array_offset; \ + le32_t tail_entry_array_n_entries; \ + uint8_t compact[0]; \ + }; \ + }; \ +} struct DataObject DataObject__contents; struct DataObject__packed DataObject__contents _packed_; @@ -276,12 +283,14 @@ enum { le64_t n_trie_nodes; \ le64_t trie_hash_chain_depth; \ le64_t boot_id_offset; \ + le32_t tail_entry_array_offset; \ + le32_t tail_entry_array_n_entries; \ } struct Header struct_Header__contents; struct Header__packed struct_Header__contents _packed_; assert_cc(sizeof(struct Header) == sizeof(struct Header__packed)); -assert_cc(sizeof(struct Header) == 296); +assert_cc(sizeof(struct Header) == 304); #define FSS_HEADER_SIGNATURE \ ((const char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }) diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index e4edab6..379f30a 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -682,6 +682,9 @@ static uint64_t minimum_header_size(JournalFile *f, Object *o) { return JOURNAL_HEADER_COMPACT(f->header) ? offsetof(Object, entry.payload) : offsetof(Object, entry.items); + if (o->object.type == OBJECT_DATA) + return journal_file_data_payload_offset(f); + if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0) return sizeof(ObjectHeader); @@ -703,10 +706,10 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) le64toh(o->data.n_entries), offset); - if (le64toh(o->object.size) <= offsetof(Object, data.payload)) + if (le64toh(o->object.size) <= journal_file_data_payload_offset(f)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Bad object size (<= %zu): %" PRIu64 ": %" PRIu64, - offsetof(Object, data.payload), + journal_file_data_payload_offset(f), le64toh(o->object.size), offset); @@ -1755,20 +1758,20 @@ static int journal_file_append_data( if (r > 0) return 0; - osize = offsetof(Object, data.payload) + size; + osize = journal_file_data_payload_offset(f) + size; r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p); if (r < 0) return r; o->data.hash = htole64(hash); - compression = maybe_compress_payload(f, o->data.payload, data, size, &rsize); + compression = maybe_compress_payload(f, journal_file_data_payload_field(f, o), data, size, &rsize); if (compression > 0) { - o->object.size = htole64(offsetof(Object, data.payload) + rsize); + o->object.size = htole64(journal_file_data_payload_offset(f) + rsize); o->object.flags |= compression; } else - memcpy_safe(o->data.payload, data, size); + memcpy_safe(journal_file_data_payload_field(f, o), data, size); r = journal_file_link_data(f, o, p, hash); if (r < 0) @@ -1917,14 +1920,14 @@ int journal_file_data_payload( } size = le64toh(READ_NOW(o->object.size)); - if (size < offsetof(Object, data.payload)) + if (size < journal_file_data_payload_offset(f)) return -EBADMSG; - size -= offsetof(Object, data.payload); + size -= journal_file_data_payload_offset(f); return maybe_decompress_payload( f, - o->data.payload, + journal_file_data_payload_field(f, o), size, o->object.flags & OBJECT_COMPRESSION_MASK, field, @@ -2379,6 +2382,8 @@ static void write_entry_array_item(JournalFile *f, Object *o, uint64_t i, uint64 static int link_entry_into_array(JournalFile *f, le64_t *first, le64_t *idx, + le32_t *tail, + le32_t *tidx, uint64_t p) { int r; uint64_t n = 0, ap = 0, q, i, a, hidx; @@ -2390,8 +2395,9 @@ static int link_entry_into_array(JournalFile *f, assert(idx); assert(p > 0); - a = le64toh(*first); - i = hidx = le64toh(READ_NOW(*idx)); + a = tail ? le32toh(*tail) : le64toh(*first); + hidx = le64toh(READ_NOW(*idx)); + i = tidx ? le32toh(READ_NOW(*tidx)) : hidx; while (a > 0) { r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o); @@ -2402,6 +2408,8 @@ static int link_entry_into_array(JournalFile *f, if (i < n) { write_entry_array_item(f, o, i, p); *idx = htole64(hidx + 1); + if (tidx) + *tidx = htole32(le32toh(*tidx) + 1); return 0; } @@ -2442,10 +2450,15 @@ static int link_entry_into_array(JournalFile *f, o->entry_array.next_entry_array_offset = htole64(q); } + if (tail) + *tail = htole32(q); + if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays)) f->header->n_entry_arrays = htole64(le64toh(f->header->n_entry_arrays) + 1); *idx = htole64(hidx + 1); + if (tidx) + *tidx = htole32(1); return 0; } @@ -2454,6 +2467,8 @@ static int link_entry_into_array_plus_one(JournalFile *f, le64_t *extra, le64_t *first, le64_t *idx, + le32_t *tail, + le32_t *tidx, uint64_t p) { uint64_t hidx; @@ -2474,7 +2489,7 @@ static int link_entry_into_array_plus_one(JournalFile *f, le64_t i; i = htole64(hidx - 1); - r = link_entry_into_array(f, first, &i, p); + r = link_entry_into_array(f, first, &i, tail, tidx, p); if (r < 0) return r; } @@ -2498,6 +2513,8 @@ static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offs &o->data.entry_offset, &o->data.entry_array_offset, &o->data.n_entries, + JOURNAL_HEADER_COMPACT(f->header) ? &o->data.tail_entry_array_offset : NULL, + JOURNAL_HEADER_COMPACT(f->header) ? &o->data.tail_entry_array_n_entries : NULL, offset); } @@ -2519,6 +2536,8 @@ static int journal_file_link_entry( r = link_entry_into_array(f, &f->header->entry_array_offset, &f->header->n_entries, + JOURNAL_HEADER_CONTAINS(f->header, tail_entry_array_offset) ? &f->header->tail_entry_array_offset : NULL, + JOURNAL_HEADER_CONTAINS(f->header, tail_entry_array_n_entries) ? &f->header->tail_entry_array_n_entries : NULL, offset); if (r < 0) return r; diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 6e431e9..1ccd685 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -206,6 +206,14 @@ int journal_file_data_payload( void **ret_data, size_t *ret_size); +static inline size_t journal_file_data_payload_offset(JournalFile *f) { + return JOURNAL_HEADER_COMPACT(f->header) ? offsetof(Object, data.compact) : offsetof(Object, data.payload); +} + +static inline uint8_t* journal_file_data_payload_field(JournalFile *f, Object *o) { + return JOURNAL_HEADER_COMPACT(f->header) ? o->data.compact : o->data.payload; +} + int journal_file_entry_item_next( JournalFile *f, Object *e, diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index adb478d..3ab28bc 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -167,16 +167,16 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o return -EBADMSG; } - if (le64toh(o->object.size) - offsetof(Object, data.payload) <= 0) { + if (le64toh(o->object.size) - journal_file_data_payload_offset(f) <= 0) { error(offset, "Bad object size (<= %zu): %"PRIu64, - offsetof(Object, data.payload), + journal_file_data_payload_offset(f), le64toh(o->object.size)); return -EBADMSG; } h1 = le64toh(o->data.hash); - r = hash_payload(f, o, offset, o->data.payload, - le64toh(o->object.size) - offsetof(Object, data.payload), + r = hash_payload(f, o, offset, journal_file_data_payload_field(f, o), + le64toh(o->object.size) - journal_file_data_payload_offset(f), &h2); if (r < 0) return r;