From ac67c27ed349664e2185e296a4cd1f270da11455 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:28 +0000 Subject: [PATCH 1/15] journal: Log error when keyed hash env variable cannot be parsed --- diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index ef4c261..dc212dc 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -3274,7 +3274,7 @@ int journal_file_open( r = getenv_bool("SYSTEMD_JOURNAL_KEYED_HASH"); if (r < 0) { if (r != -ENXIO) - log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_KEYED_HASH environment variable, ignoring."); + log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_KEYED_HASH environment variable, ignoring: %m"); f->keyed_hash = true; } else f->keyed_hash = r; From 1937fe54cbec7d17d04359e2af5ebc3a4ae7ef67 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:28 +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 dc212dc..50a23a6 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -324,7 +324,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; @@ -340,6 +340,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)); @@ -3108,7 +3110,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" @@ -3135,6 +3137,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 39e91d7..50638e1 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(JournalFile *f, ObjectType type, uint64_t offset, Object *ret); From 44c502cbd08322b2582324d22c4e19f72a9266a0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +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 50a23a6..129aa8c 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -259,7 +259,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); @@ -3282,6 +3283,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 50638e1..cdbd579 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 6987dbf76ca668cfc8e69a240dceca529a476a7c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +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 129aa8c..9669fe9 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -516,6 +516,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 63c13f41d007c0ef333fe565b56ca27ca4cc6903 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +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 4baf7da19d3cfc991504ec8cfae3135229173e71 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +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/journald-file.c b/src/journal/journald-file.c index 35ca305..ea6f766 100644 --- a/src/journal/journald-file.c +++ b/src/journal/journald-file.c @@ -49,7 +49,7 @@ static int journald_file_entry_array_punch_hole(JournalFile *f, uint64_t p, uint 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,7 +66,7 @@ static int journald_file_entry_array_punch_hole(JournalFile *f, uint64_t p, uint return 0; offset = p + offsetof(Object, entry_array.items) + - (journal_file_entry_array_n_items(&o) - n_unused) * sizeof(le64_t); + (journal_file_entry_array_n_items(f, &o) - n_unused) * sizeof(le64_t); sz = p + le64toh(o.object.size) - offset; if (sz < MINIMUM_HOLE_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 9669fe9..785a30e 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -720,12 +720,13 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) } case OBJECT_ENTRY_ARRAY: { - uint64_t sz; + uint64_t sz, item_sz; sz = le64toh(READ_NOW(o->object.size)); + item_sz = JOURNAL_HEADER_COMPACT(f->header) ? sizeof(uint32_t) : sizeof(uint64_t); if (sz < offsetof(EntryArrayObject, items) || - (sz - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 || - (sz - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) + (sz - offsetof(EntryArrayObject, items)) % item_sz != 0 || + (sz - offsetof(EntryArrayObject, items)) / item_sz <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid object entry array size: %" PRIu64 ": %" PRIu64, sz, @@ -1618,7 +1619,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); @@ -1630,7 +1631,17 @@ 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 JOURNAL_HEADER_COMPACT(f->header) ? + (sz - offsetof(Object, entry_array.compact)) / sizeof(uint32_t) : + (sz - offsetof(Object, entry_array.items)) / sizeof(uint64_t); +} + +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) { @@ -1648,12 +1659,22 @@ 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, uint64_t p) { int r; - uint64_t n = 0, ap = 0, q, i, a, hidx; + uint64_t n = 0, ap = 0, q, i, a, hidx, sz; Object *o; assert(f); @@ -1670,9 +1691,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; } @@ -1690,9 +1711,11 @@ static int link_entry_into_array(JournalFile *f, if (n < 4) n = 4; - r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY, - offsetof(Object, entry_array.items) + n * sizeof(uint64_t), - &o, &q); + sz = JOURNAL_HEADER_COMPACT(f->header) ? + offsetof(Object, entry_array.compact) + n * sizeof(uint32_t) : + offsetof(Object, entry_array.items) + n * sizeof(uint64_t); + + r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY, sz, &o, &q); if (r < 0) return r; @@ -1702,7 +1725,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); @@ -2155,7 +2178,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; @@ -2175,7 +2198,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; @@ -2183,7 +2206,7 @@ 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, &e); if (r >= 0) @@ -2205,7 +2228,7 @@ static int generic_array_get( found: /* 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) *ret = e; @@ -2309,13 +2332,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 @@ -2348,7 +2371,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; @@ -2368,7 +2391,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; @@ -2398,7 +2421,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 @@ -2446,14 +2469,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); r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o); if (r < 0) diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index cdbd579..447e752 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -194,7 +194,8 @@ int journal_file_read_object(JournalFile *f, ObjectType type, uint64_t offset, O int journal_file_tail_end(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 *offset); diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index 8288ebc..1b6d4c5 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -333,9 +333,12 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o break; - case OBJECT_ENTRY_ARRAY: - if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 || - (le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) { + case OBJECT_ENTRY_ARRAY: { + uint64_t item_sz; + + item_sz = JOURNAL_HEADER_COMPACT(f->header) ? sizeof(uint32_t) : sizeof(uint64_t); + if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % item_sz != 0 || + (le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / item_sz <= 0) { error(offset, "Invalid object entry array size: %"PRIu64, le64toh(o->object.size)); @@ -349,17 +352,18 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o return -EBADMSG; } - for (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 (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; + } case OBJECT_TAG: if (le64toh(o->object.size) != sizeof(TagObject)) { @@ -473,10 +477,10 @@ static int entry_points_to_data( if (r < 0) return r; - m = journal_file_entry_array_n_items(o); + m = journal_file_entry_array_n_items(f, o); u = MIN(n - i, m); - if (entry_p <= le64toh(o->entry_array.items[u-1])) { + if (entry_p <= journal_file_entry_array_item(f, o, u - 1)) { uint64_t x, y, z; x = 0; @@ -485,13 +489,13 @@ static int entry_points_to_data( while (x < y) { z = (x + y) / 2; - if (le64toh(o->entry_array.items[z]) == entry_p) + if (journal_file_entry_array_item(f, o, z) == entry_p) return 0; if (x + 1 >= y) break; - if (entry_p < le64toh(o->entry_array.items[z])) + if (entry_p < journal_file_entry_array_item(f, o, z)) y = z; else x = z; @@ -566,10 +570,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; @@ -792,11 +796,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 b7ffe89f1ee2a1909a5ad624001616a39e1406e4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 7/15] journal: Stop comparing hash values from entry items against data objects These checks don't achieve anything of value. Assuming they were added to check for corruption, they don't actually achieve this goal since other parts of the data object can still get corrupted and we wouldn't notice unless we'd recalculate the hash every time. In theory, we could use the entry item hash to avoid a random access lookup for the data object hash in the journal file in the future to speed up searching, but for finding all entry objects containing a specific data objects, we already have entry arrays per data object to get fast access to this information. This means that duplicating the hashes in the entry item doesn't result in any added value. In this commit, we remove the checks so that in future commits we can remove the hashes from the journal file format in the new compact mode. --- diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 785a30e..3cacac3 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -3615,21 +3615,16 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 for (uint64_t i = 0; i < n; i++) { uint64_t l, h; - le64_t le_hash; size_t t; void *data; Object *u; q = le64toh(o->entry.items[i].object_offset); - le_hash = o->entry.items[i].hash; r = journal_file_move_to_object(from, OBJECT_DATA, q, &o); if (r < 0) return r; - if (le_hash != o->data.hash) - return -EBADMSG; - l = le64toh(READ_NOW(o->object.size)); if (l < offsetof(Object, data.payload)) return -EBADMSG; diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index 1b6d4c5..3cbd82f 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -718,11 +718,10 @@ static int verify_entry( n = journal_file_entry_n_items(o); for (i = 0; i < n; i++) { - uint64_t q, h; + uint64_t q; Object *u; q = le64toh(o->entry.items[i].object_offset); - h = le64toh(o->entry.items[i].hash); if (!contains_uint64(cache_data_fd, n_data, q)) { error(p, "Invalid data object of entry"); @@ -733,12 +732,7 @@ static int verify_entry( if (r < 0) return r; - if (le64toh(u->data.hash) != h) { - error(p, "Hash mismatch for data object of entry"); - return -EBADMSG; - } - - r = data_object_in_hash_table(f, h, q); + r = data_object_in_hash_table(f, le64toh(u->data.hash), q); if (r < 0) return r; if (r == 0) { diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index 7a6cc4a..5c8f2de 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -2296,12 +2296,10 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** for (i = 0; i < n; i++) { Object *d; uint64_t p, l; - le64_t le_hash; size_t t; int compression; p = le64toh(o->entry.items[i].object_offset); - le_hash = o->entry.items[i].hash; 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); @@ -2310,11 +2308,6 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** if (r < 0) return r; - if (le_hash != d->data.hash) { - log_debug("Entry item %"PRIu64" hash is bad, skipping over it.", i); - continue; - } - l = le64toh(d->object.size) - offsetof(Object, data.payload); compression = d->object.flags & OBJECT_COMPRESSION_MASK; @@ -2443,10 +2436,8 @@ _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t for (uint64_t n = journal_file_entry_n_items(o); j->current_field < n; j->current_field++) { uint64_t p; - le64_t le_hash; p = le64toh(o->entry.items[j->current_field].object_offset); - le_hash = o->entry.items[j->current_field].hash; 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); @@ -2455,11 +2446,6 @@ _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t if (r < 0) return r; - if (le_hash != o->data.hash) { - log_debug("Entry item %"PRIu64" hash is bad, skipping over it.", j->current_field); - continue; - } - 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); From 82a3ae1cb14dd0661e05894934a39b6d403e7650 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 8/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 3cacac3..691402f 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1778,15 +1778,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; @@ -1798,8 +1796,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); @@ -1829,9 +1827,8 @@ 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++) { - r = journal_file_link_entry_item(f, o, offset, i); + for (uint64_t i = 0; i < n_items; i++) { + r = journal_file_link_entry_item(f, o, offset, items[i].object_offset); if (r < 0) return r; } @@ -1844,7 +1841,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; @@ -1864,7 +1861,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); @@ -1878,7 +1877,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; @@ -1973,13 +1972,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) @@ -2000,7 +1997,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; @@ -2029,7 +2026,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; @@ -2053,9 +2050,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), }; } @@ -3593,7 +3590,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); @@ -3611,7 +3608,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; @@ -3668,9 +3665,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 a8f2e1ab4d025c10cedb68d9d18bf1a1ae3b5484 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 9/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 691402f..b51bbf5 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1301,7 +1301,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); @@ -1317,8 +1317,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; @@ -1328,6 +1326,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) @@ -1336,40 +1336,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) @@ -1605,18 +1578,180 @@ 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; + + 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; + + *i = ++p; + + return 1; + } - return (sz - offsetof(Object, entry.items)) / sizeof(EntryItem); + *i = p; + + return 0; } uint64_t journal_file_entry_array_n_items(JournalFile *f, Object *o) { @@ -3587,7 +3722,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; @@ -3607,51 +3742,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; @@ -3665,14 +3778,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 447e752..8f5c907 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -193,7 +193,28 @@ int journal_file_read_object(JournalFile *f, ObjectType type, uint64_t offset, O int journal_file_tail_end(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 3cbd82f..fd645ff 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -275,13 +275,20 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o return -EBADMSG; } - for (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 (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; } } @@ -450,12 +457,22 @@ static int entry_points_to_data( if (r < 0) return r; - n = journal_file_entry_n_items(o); - for (i = 0; i < n; i++) - if (le64toh(o->entry.items[i].object_offset) == data_p) { + for (i = 0;;) { + uint64_t p; + + r = journal_file_entry_item_next(f, o, entry_p, &i, NULL, 0, 0, &p, NULL, NULL); + if (r < 0) { + error_errno(entry_p, r, "Invalid entry item (%"PRIu64"): %m", i); + return r; + } + if (r == 0) + break; + + if (p == data_p) { found = true; break; } + } if (!found) { error(entry_p, "Data object at "OFSfmt" not referenced by linked entry", data_p); @@ -709,19 +726,23 @@ static int verify_entry( Object *o, uint64_t p, MMapFileDescriptor *cache_data_fd, uint64_t n_data) { - 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 5c8f2de..14dbe58 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -2267,10 +2267,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); @@ -2286,136 +2286,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); @@ -2430,36 +2317,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) { @@ -2471,7 +2337,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; } } @@ -2925,7 +2805,7 @@ _public_ int sd_journal_enumerate_unique( for (;;) { JournalFile *of; Object *o; - const void *odata; + void *odata; size_t ol; bool found; int r; @@ -2969,7 +2849,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; @@ -3016,9 +2897,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 f7399c9caf9d3386f722beec495106efabc3a1a9 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 10/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 b51bbf5..87c6e50 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -602,6 +602,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) @@ -667,19 +669,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(EntryObject, items) || - (sz - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64, - offsetof(EntryObject, 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(EntryObject, items)) / sizeof(EntryItem) <= 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Invalid number items in entry: %" PRIu64 ": %" PRIu64, - (sz - offsetof(EntryObject, 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(EntryObject, items) || + (sz - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) + return log_debug_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64, + offsetof(EntryObject, items), + sz, + offset); + + if ((sz - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) + return log_debug_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Invalid number items in entry: %" PRIu64 ": %" PRIu64, + (sz - offsetof(EntryObject, items)) / sizeof(EntryItem), + offset); + } if (le64toh(o->entry.seqnum) <= 0) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), @@ -703,7 +723,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)); @@ -711,10 +732,9 @@ static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) (sz - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 || (sz - offsetof(HashTableObject, 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; } @@ -754,6 +774,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; @@ -966,6 +1006,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; @@ -974,14 +1028,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)); @@ -1029,6 +1076,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; @@ -1081,6 +1170,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, @@ -1177,6 +1289,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, @@ -1578,6 +1736,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, @@ -1673,7 +1868,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, @@ -1685,42 +1880,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; @@ -1754,6 +1989,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; @@ -1794,6 +2083,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); @@ -1975,12 +2339,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; @@ -1989,16 +2351,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); @@ -2133,9 +2532,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); @@ -2180,14 +2578,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), }; } @@ -2196,7 +2592,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 @@ -3275,6 +3671,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" @@ -3303,6 +3700,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), @@ -3324,6 +3722,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)); @@ -3339,6 +3743,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)); } @@ -3611,6 +4019,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) @@ -3722,7 +4136,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; @@ -3773,18 +4187,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; @@ -4007,6 +4418,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) && @@ -4044,6 +4463,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 8f5c907..6fc3632 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 fd645ff..5028be9 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -239,19 +239,35 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o } case OBJECT_ENTRY: - if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) { - error(offset, - "Bad entry size (<= %zu): %"PRIu64, - offsetof(EntryObject, 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(EntryObject, items)) / sizeof(EntryItem) <= 0) { - error(offset, - "Invalid number items in entry: %"PRIu64, - (le64toh(o->object.size) - offsetof(EntryObject, 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(EntryObject, items)) % sizeof(EntryItem) != 0) { + error(offset, + "Bad entry size (<= %zu): %" PRIu64, + offsetof(EntryObject, items), + le64toh(o->object.size)); + return -EBADMSG; + } + + if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) { + error(offset, + "Invalid number items in entry: %" PRIu64, + (le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem)); + return -EBADMSG; + } } if (le64toh(o->entry.seqnum) <= 0) { @@ -297,6 +313,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(HashTableObject, items)) % sizeof(HashItem) != 0 || (le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) { error(offset, @@ -388,6 +405,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; @@ -896,7 +932,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; @@ -1165,6 +1201,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 6417bbc4409f9947a1a4f4fb3ac5479b195d2e25 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 11/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 87c6e50..bb7e942 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)) @@ -592,7 +593,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), @@ -606,6 +607,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); @@ -670,19 +675,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(EntryObject, items) || (sz - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) @@ -841,7 +840,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); @@ -906,7 +905,7 @@ int journal_file_read_object(JournalFile *f, ObjectType type, uint64_t offset, O "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); @@ -1639,15 +1638,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); @@ -1670,10 +1686,6 @@ static int journal_file_append_data( 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) @@ -1681,25 +1693,12 @@ 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; + compression = maybe_compress_payload(f, o->data.payload, data, size, &rsize); - 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 - - 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); @@ -1718,15 +1717,6 @@ static int journal_file_append_data( if (r < 0) return r; - /* 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; @@ -1868,7 +1858,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, @@ -1883,14 +1873,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; @@ -1926,21 +1909,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, @@ -2004,7 +2117,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 * @@ -2340,6 +2454,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; @@ -2351,6 +2466,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; @@ -2382,18 +2500,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) }; @@ -2523,6 +2672,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, @@ -2532,6 +2696,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; @@ -2560,15 +2726,29 @@ 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; + /* 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, @@ -2578,7 +2758,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) ? @@ -2589,10 +2769,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 @@ -3786,6 +3967,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, @@ -4023,6 +4241,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 @@ -4140,6 +4362,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); @@ -4167,27 +4391,51 @@ 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; + + /* 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[j] = (EntryItemEx){ + items[n_items++] = (EntryItemEx){ .object_offset = h, .hash = le64toh(u->data.hash), .xor_hash = JOURNAL_HEADER_KEYED_HASH(to->header) ? jenkins_hash64(data, l) : @@ -4195,7 +4443,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 5028be9..c790345 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -240,19 +240,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(EntryObject, items)) % sizeof(EntryItem) != 0) { error(offset, @@ -303,7 +298,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; } @@ -777,7 +772,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 9330ef9cb42d3adf02edfb3aad8e2af562bd421b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 12/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 bb7e942..3a8d541 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1635,7 +1635,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) { @@ -2441,6 +2441,9 @@ static int journal_file_link_entry( /* Link up the items */ for (uint64_t i = 0; i < n_items; i++) { + if (!items[i].indexed) + continue; + r = journal_file_link_entry_item(f, o, offset, items[i].object_offset); if (r < 0) return r; @@ -2679,12 +2682,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( @@ -2764,6 +2776,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), }; } @@ -4004,6 +4018,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, @@ -4245,6 +4296,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 @@ -4440,6 +4495,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 c790345..d286f98 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -161,7 +161,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 ead3ed70fd9f0a43f841c43845fa385f55929213 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 13/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 3afe66d..549889b 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; 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 3a8d541..6333709 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -286,7 +286,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); @@ -299,7 +326,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; @@ -2014,7 +2045,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) @@ -2157,6 +2188,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; @@ -2519,7 +2570,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; @@ -2552,10 +2603,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); @@ -3455,14 +3516,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) { @@ -4414,7 +4482,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; @@ -4433,7 +4501,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); @@ -4501,7 +4572,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++) @@ -4775,6 +4846,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 6fc3632..9b9b72d 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -217,6 +217,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_; @@ -238,7 +241,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 *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 *offset); diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index d286f98..6ff25fb 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -1105,7 +1105,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; @@ -1148,8 +1150,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")", @@ -1160,7 +1166,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 && @@ -1178,6 +1184,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 14dbe58..0037da2 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) { @@ -774,7 +789,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; @@ -783,7 +800,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 @@ -864,7 +883,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; } @@ -920,6 +941,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; @@ -934,12 +956,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; @@ -1047,8 +1073,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); @@ -1061,6 +1088,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; @@ -1098,7 +1129,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; @@ -1119,7 +1150,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; } @@ -2218,16 +2249,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 3a3804d022db3c883f0613327520140d5be726fb Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 14/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 6333709..9cfe706 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1037,13 +1037,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 358f5badef60356e99f697f7569f13c02ac35780 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Jan 20 2022 16:52:29 +0000 Subject: [PATCH 15/15] journal: Inline loop variable --- diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index 6ff25fb..080a43d 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -137,8 +137,6 @@ static int hash_payload(JournalFile *f, Object *o, uint64_t offset, const uint8_ } static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) { - uint64_t i; - assert(f); assert(offset); assert(o); @@ -286,7 +284,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o return -EBADMSG; } - for (i = 0;;) { + for (uint64_t i = 0;;) { uint64_t p; int r; @@ -318,7 +316,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o return -EBADMSG; } - for (i = 0; i < journal_file_hash_table_n_items(o); i++) { + for (uint64_t i = 0; i < journal_file_hash_table_n_items(o); i++) { if (o->hash_table.items[i].head_hash_offset != 0 && !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) { error(offset, @@ -371,7 +369,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o return -EBADMSG; } - for (i = 0; i < journal_file_entry_array_n_items(f, o); 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,