From 1d3ef11dfe9d59d0d8a5eeeee401b372124923e9 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 30 Sep 2023 19:18:34 +0900 Subject: journal: move several tests to libsystemd/sd-journal This also renames test-journal-xyz for journald (rather than sd-journal) to test-journald-xyz. --- src/journal/meson.build | 24 +- src/journal/test-journal-append.c | 269 ---------- src/journal/test-journal-config.c | 50 -- src/journal/test-journal-flush.c | 82 --- src/journal/test-journal-interleaving.c | 559 --------------------- src/journal/test-journal-stream.c | 201 -------- src/journal/test-journal-syslog.c | 70 --- src/journal/test-journal-verify.c | 210 -------- src/journal/test-journal.c | 280 ----------- src/journal/test-journald-config.c | 50 ++ src/journal/test-journald-syslog.c | 70 +++ src/libsystemd/meson.build | 12 + src/libsystemd/sd-journal/test-journal-append.c | 269 ++++++++++ src/libsystemd/sd-journal/test-journal-flush.c | 82 +++ .../sd-journal/test-journal-interleaving.c | 559 +++++++++++++++++++++ src/libsystemd/sd-journal/test-journal-stream.c | 201 ++++++++ src/libsystemd/sd-journal/test-journal-verify.c | 210 ++++++++ src/libsystemd/sd-journal/test-journal.c | 280 +++++++++++ 18 files changed, 1735 insertions(+), 1743 deletions(-) delete mode 100644 src/journal/test-journal-append.c delete mode 100644 src/journal/test-journal-config.c delete mode 100644 src/journal/test-journal-flush.c delete mode 100644 src/journal/test-journal-interleaving.c delete mode 100644 src/journal/test-journal-stream.c delete mode 100644 src/journal/test-journal-syslog.c delete mode 100644 src/journal/test-journal-verify.c delete mode 100644 src/journal/test-journal.c create mode 100644 src/journal/test-journald-config.c create mode 100644 src/journal/test-journald-syslog.c create mode 100644 src/libsystemd/sd-journal/test-journal-append.c create mode 100644 src/libsystemd/sd-journal/test-journal-flush.c create mode 100644 src/libsystemd/sd-journal/test-journal-interleaving.c create mode 100644 src/libsystemd/sd-journal/test-journal-stream.c create mode 100644 src/libsystemd/sd-journal/test-journal-verify.c create mode 100644 src/libsystemd/sd-journal/test-journal.c diff --git a/src/journal/meson.build b/src/journal/meson.build index 6ae5cfbf9d..a3aa8ad5ee 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -103,11 +103,7 @@ executables += [ ], }, journal_test_template + { - 'sources' : files('test-journal-append.c'), - 'type' : 'manual', - }, - journal_test_template + { - 'sources' : files('test-journal-config.c'), + 'sources' : files('test-journald-config.c'), 'dependencies' : [ liblz4, libselinux, @@ -115,16 +111,7 @@ executables += [ ], }, journal_test_template + { - 'sources' : files('test-journal-flush.c'), - }, - journal_test_template + { - 'sources' : files('test-journal-interleaving.c'), - }, - journal_test_template + { - 'sources' : files('test-journal-stream.c'), - }, - journal_test_template + { - 'sources' : files('test-journal-syslog.c'), + 'sources' : files('test-journald-syslog.c'), 'dependencies' : [ liblz4, libselinux, @@ -132,13 +119,6 @@ executables += [ threads, ], }, - journal_test_template + { - 'sources' : files('test-journal-verify.c'), - 'timeout' : 90, - }, - journal_test_template + { - 'sources' : files('test-journal.c'), - }, journal_fuzz_template + { 'sources' : files( 'fuzz-journald-audit.c', diff --git a/src/journal/test-journal-append.c b/src/journal/test-journal-append.c deleted file mode 100644 index fbbf44add4..0000000000 --- a/src/journal/test-journal-append.c +++ /dev/null @@ -1,269 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include -#include - -#include "chattr-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "io-util.h" -#include "journal-file-util.h" -#include "log.h" -#include "mmap-cache.h" -#include "parse-util.h" -#include "random-util.h" -#include "rm-rf.h" -#include "strv.h" -#include "terminal-util.h" -#include "tests.h" -#include "tmpfile-util.h" - -static int journal_append_message(JournalFile *mj, const char *message) { - struct iovec iovec; - struct dual_timestamp ts; - - assert(mj); - assert(message); - - dual_timestamp_get(&ts); - iovec = IOVEC_MAKE_STRING(message); - return journal_file_append_entry( - mj, - &ts, - /* boot_id= */ NULL, - &iovec, - /* n_iovec= */ 1, - /* seqnum= */ NULL, - /* seqnum_id= */ NULL, - /* ret_object= */ NULL, - /* ret_offset= */ NULL); -} - -static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { - _cleanup_(mmap_cache_unrefp) MMapCache *mmap_cache = NULL; - _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL; - _cleanup_(journal_file_offline_closep) JournalFile *mj = NULL; - uint64_t start, end; - int r; - - mmap_cache = mmap_cache_new(); - assert_se(mmap_cache); - - /* journal_file_open() requires a valid machine id */ - if (sd_id128_get_machine(NULL) < 0) - return log_tests_skipped("No valid machine ID found"); - - assert_se(mkdtemp_malloc("/tmp/journal-append-XXXXXX", &tempdir) >= 0); - assert_se(chdir(tempdir) >= 0); - (void) chattr_path(tempdir, FS_NOCOW_FL, FS_NOCOW_FL, NULL); - - log_debug("Opening journal %s/system.journal", tempdir); - - r = journal_file_open( - /* fd= */ -1, - "system.journal", - O_RDWR|O_CREAT, - JOURNAL_COMPRESS, - 0644, - /* compress_threshold_bytes= */ UINT64_MAX, - /* metrics= */ NULL, - mmap_cache, - /* template= */ NULL, - &mj); - if (r < 0) - return log_error_errno(r, "Failed to open the journal: %m"); - - assert_se(mj); - - /* Add a couple of initial messages */ - for (int i = 0; i < 10; i++) { - _cleanup_free_ char *message = NULL; - - assert_se(asprintf(&message, "MESSAGE=Initial message %d", i) >= 0); - r = journal_append_message(mj, message); - if (r < 0) - return log_error_errno(r, "Failed to write to the journal: %m"); - } - - start = start_offset == UINT64_MAX ? random_u64() % mj->last_stat.st_size : start_offset; - end = (uint64_t) mj->last_stat.st_size; - - /* Print the initial offset at which we start flipping bits, which can be - * later used to reproduce a potential fail */ - log_info("Start offset: %" PRIu64 ", corrupt-step: %" PRIu64, start, step); - fflush(stdout); - - if (start >= end) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Start offset >= journal size, can't continue"); - - for (uint64_t offset = start; offset < end; offset += step) { - _cleanup_free_ char *message = NULL; - uint8_t b; - - /* Flip a bit in the journal file */ - r = pread(mj->fd, &b, 1, offset); - assert_se(r == 1); - b |= 0x1; - r = pwrite(mj->fd, &b, 1, offset); - assert_se(r == 1); - - /* Close and reopen the journal to flush all caches and remap - * the corrupted journal */ - mj = journal_file_offline_close(mj); - r = journal_file_open( - /* fd= */ -1, - "system.journal", - O_RDWR|O_CREAT, - JOURNAL_COMPRESS, - 0644, - /* compress_threshold_bytes= */ UINT64_MAX, - /* metrics= */ NULL, - mmap_cache, - /* template= */ NULL, - &mj); - if (r < 0) { - /* The corrupted journal might get rejected during reopening - * if it's corrupted enough (especially its header), so - * treat this as a success if it doesn't crash */ - log_info_errno(r, "Failed to reopen the journal: %m"); - break; - } - - /* Try to write something to the (possibly corrupted) journal */ - assert_se(asprintf(&message, "MESSAGE=Hello world %" PRIu64, offset) >= 0); - r = journal_append_message(mj, message); - if (r < 0) { - /* We care only about crashes or sanitizer errors, - * failed write without any crash is a success */ - log_info_errno(r, "Failed to write to the journal: %m"); - break; - } - } - - return 0; -} - -int main(int argc, char *argv[]) { - uint64_t start_offset = UINT64_MAX; - uint64_t iterations = 100; - uint64_t iteration_step = 1; - uint64_t corrupt_step = 31; - bool sequential = false, run_one = false; - int c, r; - - test_setup_logging(LOG_DEBUG); - - enum { - ARG_START_OFFSET = 0x1000, - ARG_ITERATIONS, - ARG_ITERATION_STEP, - ARG_CORRUPT_STEP, - ARG_SEQUENTIAL, - ARG_RUN_ONE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "start-offset", required_argument, NULL, ARG_START_OFFSET }, - { "iterations", required_argument, NULL, ARG_ITERATIONS }, - { "iteration-step", required_argument, NULL, ARG_ITERATION_STEP }, - { "corrupt-step", required_argument, NULL, ARG_CORRUPT_STEP }, - { "sequential", no_argument, NULL, ARG_SEQUENTIAL }, - { "run-one", required_argument, NULL, ARG_RUN_ONE }, - {} - }; - - assert_se(argc >= 0); - assert_se(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { - - case 'h': - printf("Syntax:\n" - " %s [OPTION...]\n" - "Options:\n" - " --start-offset=OFFSET Offset at which to start corrupting the journal\n" - " (default: random offset is picked, unless\n" - " --sequential is used - in that case we use 0 + iteration)\n" - " --iterations=ITER Number of iterations to perform before exiting\n" - " (default: 100)\n" - " --iteration-step=STEP Iteration step (default: 1)\n" - " --corrupt-step=STEP Corrupt every n-th byte starting from OFFSET (default: 31)\n" - " --sequential Go through offsets sequentially instead of picking\n" - " a random one on each iteration. If set, we go through\n" - " offsets <0; ITER), or 0) - /* Reached the end of the journal file */ - break; - } - - return EXIT_SUCCESS; -} diff --git a/src/journal/test-journal-config.c b/src/journal/test-journal-config.c deleted file mode 100644 index 1a6c531f4e..0000000000 --- a/src/journal/test-journal-config.c +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "journald-server.h" -#include "tests.h" - -#define _COMPRESS_PARSE_CHECK(str, enab, thresh, varname) \ - do { \ - JournalCompressOptions varname = {true, 111}; \ - config_parse_compress("", "", 0, "", 0, "", 0, str, \ - &varname, NULL); \ - assert_se((enab) == varname.enabled); \ - if (varname.enabled) \ - assert_se((thresh) == varname.threshold_bytes); \ - } while (0) - -#define COMPRESS_PARSE_CHECK(str, enabled, threshold) \ - _COMPRESS_PARSE_CHECK(str, enabled, threshold, conf##__COUNTER__) - -TEST(config_compress) { - COMPRESS_PARSE_CHECK("yes", true, 111); - COMPRESS_PARSE_CHECK("no", false, 111); - COMPRESS_PARSE_CHECK("y", true, 111); - COMPRESS_PARSE_CHECK("n", false, 111); - COMPRESS_PARSE_CHECK("true", true, 111); - COMPRESS_PARSE_CHECK("false", false, 111); - COMPRESS_PARSE_CHECK("t", true, 111); - COMPRESS_PARSE_CHECK("f", false, 111); - COMPRESS_PARSE_CHECK("on", true, 111); - COMPRESS_PARSE_CHECK("off", false, 111); - - /* Weird size/bool overlapping case. We preserve backward compatibility instead of assuming these are byte - * counts. */ - COMPRESS_PARSE_CHECK("1", true, 111); - COMPRESS_PARSE_CHECK("0", false, 111); - - /* IEC sizing */ - COMPRESS_PARSE_CHECK("1B", true, 1); - COMPRESS_PARSE_CHECK("1K", true, 1024); - COMPRESS_PARSE_CHECK("1M", true, 1024 * 1024); - COMPRESS_PARSE_CHECK("1G", true, 1024 * 1024 * 1024); - - /* Invalid Case */ - COMPRESS_PARSE_CHECK("-1", true, 111); - COMPRESS_PARSE_CHECK("blah blah", true, 111); - COMPRESS_PARSE_CHECK("", true, UINT64_MAX); -} - -DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/journal/test-journal-flush.c b/src/journal/test-journal-flush.c deleted file mode 100644 index b5956d24fc..0000000000 --- a/src/journal/test-journal-flush.c +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "chattr-util.h" -#include "journal-file-util.h" -#include "journal-internal.h" -#include "macro.h" -#include "path-util.h" -#include "rm-rf.h" -#include "string-util.h" -#include "tmpfile-util.h" - -static void test_journal_flush(int argc, char *argv[]) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - _cleanup_free_ char *fn = NULL; - _cleanup_(rm_rf_physical_and_freep) char *dn = NULL; - JournalFile *new_journal = NULL; - sd_journal *j = NULL; - unsigned n = 0; - int r; - - assert_se(m = mmap_cache_new()); - assert_se(mkdtemp_malloc("/var/tmp/test-journal-flush.XXXXXX", &dn) >= 0); - (void) chattr_path(dn, FS_NOCOW_FL, FS_NOCOW_FL, NULL); - - assert_se(fn = path_join(dn, "test.journal")); - - r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal); - assert_se(r >= 0); - - if (argc > 1) - r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), 0); - else - r = sd_journal_open(&j, 0); - assert_se(r == 0); - - sd_journal_set_data_threshold(j, 0); - - SD_JOURNAL_FOREACH(j) { - Object *o; - JournalFile *f; - - f = j->current_file; - assert_se(f && f->current_offset > 0); - - r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); - if (r < 0) - log_error_errno(r, "journal_file_move_to_object failed: %m"); - assert_se(r >= 0); - - r = journal_file_copy_entry(f, new_journal, o, f->current_offset, NULL, NULL); - if (r < 0) - log_warning_errno(r, "journal_file_copy_entry failed: %m"); - assert_se(r >= 0 || - IN_SET(r, -EBADMSG, /* corrupted file */ - -EPROTONOSUPPORT, /* unsupported compression */ - -EIO, /* file rotated */ - -EREMCHG)); /* clock rollback */ - - if (++n >= 10000) - break; - } - - sd_journal_close(j); - - (void) journal_file_offline_close(new_journal); -} - -int main(int argc, char *argv[]) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); - test_journal_flush(argc, argv); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); - test_journal_flush(argc, argv); - - return 0; -} diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c deleted file mode 100644 index d8588759d1..0000000000 --- a/src/journal/test-journal-interleaving.c +++ /dev/null @@ -1,559 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "sd-id128.h" -#include "sd-journal.h" - -#include "alloc-util.h" -#include "chattr-util.h" -#include "io-util.h" -#include "journal-file-util.h" -#include "journal-vacuum.h" -#include "log.h" -#include "logs-show.h" -#include "parse-util.h" -#include "rm-rf.h" -#include "tests.h" - -/* This program tests skipping around in a multi-file journal. */ - -static bool arg_keep = false; -static dual_timestamp previous_ts = {}; - -_noreturn_ static void log_assert_errno(const char *text, int error, const char *file, unsigned line, const char *func) { - log_internal(LOG_CRIT, error, file, line, func, - "'%s' failed at %s:%u (%s): %m", text, file, line, func); - abort(); -} - -#define assert_ret(expr) \ - do { \ - int _r_ = (expr); \ - if (_unlikely_(_r_ < 0)) \ - log_assert_errno(#expr, -_r_, PROJECT_FILE, __LINE__, __func__); \ - } while (false) - -static JournalFile *test_open_internal(const char *name, JournalFileFlags flags) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - JournalFile *f; - - m = mmap_cache_new(); - assert_se(m != NULL); - - assert_ret(journal_file_open(-1, name, O_RDWR|O_CREAT, flags, 0644, UINT64_MAX, NULL, m, NULL, &f)); - return f; -} - -static JournalFile *test_open(const char *name) { - return test_open_internal(name, JOURNAL_COMPRESS); -} - -static JournalFile *test_open_strict(const char *name) { - return test_open_internal(name, JOURNAL_COMPRESS | JOURNAL_STRICT_ORDER); -} - -static void test_close(JournalFile *f) { - (void) journal_file_offline_close(f); -} - -static void append_number(JournalFile *f, int n, const sd_id128_t *boot_id, uint64_t *seqnum) { - _cleanup_free_ char *p = NULL, *q = NULL; - dual_timestamp ts; - struct iovec iovec[2]; - size_t n_iov = 0; - - dual_timestamp_get(&ts); - - if (ts.monotonic <= previous_ts.monotonic) - ts.monotonic = previous_ts.monotonic + 1; - - if (ts.realtime <= previous_ts.realtime) - ts.realtime = previous_ts.realtime + 1; - - previous_ts = ts; - - assert_se(asprintf(&p, "NUMBER=%d", n) >= 0); - iovec[n_iov++] = IOVEC_MAKE_STRING(p); - - if (boot_id) { - assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id))); - iovec[n_iov++] = IOVEC_MAKE_STRING(q); - } - - assert_ret(journal_file_append_entry(f, &ts, boot_id, iovec, n_iov, seqnum, NULL, NULL, NULL)); -} - -static void append_unreferenced_data(JournalFile *f, const sd_id128_t *boot_id) { - _cleanup_free_ char *q = NULL; - dual_timestamp ts; - struct iovec iovec; - - assert(boot_id); - - ts.monotonic = usec_sub_unsigned(previous_ts.monotonic, 10); - ts.realtime = usec_sub_unsigned(previous_ts.realtime, 10); - - assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id))); - iovec = IOVEC_MAKE_STRING(q); - - assert_se(journal_file_append_entry(f, &ts, boot_id, &iovec, 1, NULL, NULL, NULL, NULL) == -EREMCHG); -} - -static void test_check_number(sd_journal *j, int n) { - sd_id128_t boot_id; - const void *d; - _cleanup_free_ char *k = NULL; - size_t l; - int x; - - assert_se(sd_journal_get_monotonic_usec(j, NULL, &boot_id) >= 0); - assert_ret(sd_journal_get_data(j, "NUMBER", &d, &l)); - assert_se(k = strndup(d, l)); - printf("%s %s (expected=%i)\n", SD_ID128_TO_STRING(boot_id), k, n); - - assert_se(safe_atoi(k + STRLEN("NUMBER="), &x) >= 0); - assert_se(n == x); -} - -static void test_check_numbers_down(sd_journal *j, int count) { - int i; - - for (i = 1; i <= count; i++) { - int r; - test_check_number(j, i); - assert_ret(r = sd_journal_next(j)); - if (i == count) - assert_se(r == 0); - else - assert_se(r == 1); - } - -} - -static void test_check_numbers_up(sd_journal *j, int count) { - for (int i = count; i >= 1; i--) { - int r; - test_check_number(j, i); - assert_ret(r = sd_journal_previous(j)); - if (i == 1) - assert_se(r == 0); - else - assert_se(r == 1); - } - -} - -static void setup_sequential(void) { - JournalFile *f1, *f2, *f3; - sd_id128_t id; - - f1 = test_open("one.journal"); - f2 = test_open("two.journal"); - f3 = test_open("three.journal"); - assert_se(sd_id128_randomize(&id) >= 0); - log_info("boot_id: %s", SD_ID128_TO_STRING(id)); - append_number(f1, 1, &id, NULL); - append_number(f1, 2, &id, NULL); - append_number(f1, 3, &id, NULL); - append_number(f2, 4, &id, NULL); - assert_se(sd_id128_randomize(&id) >= 0); - log_info("boot_id: %s", SD_ID128_TO_STRING(id)); - append_number(f2, 5, &id, NULL); - append_number(f2, 6, &id, NULL); - append_number(f3, 7, &id, NULL); - append_number(f3, 8, &id, NULL); - assert_se(sd_id128_randomize(&id) >= 0); - log_info("boot_id: %s", SD_ID128_TO_STRING(id)); - append_number(f3, 9, &id, NULL); - test_close(f1); - test_close(f2); - test_close(f3); -} - -static void setup_interleaved(void) { - JournalFile *f1, *f2, *f3; - sd_id128_t id; - - f1 = test_open("one.journal"); - f2 = test_open("two.journal"); - f3 = test_open("three.journal"); - assert_se(sd_id128_randomize(&id) >= 0); - log_info("boot_id: %s", SD_ID128_TO_STRING(id)); - append_number(f1, 1, &id, NULL); - append_number(f2, 2, &id, NULL); - append_number(f3, 3, &id, NULL); - append_number(f1, 4, &id, NULL); - append_number(f2, 5, &id, NULL); - append_number(f3, 6, &id, NULL); - append_number(f1, 7, &id, NULL); - append_number(f2, 8, &id, NULL); - append_number(f3, 9, &id, NULL); - test_close(f1); - test_close(f2); - test_close(f3); -} - -static void setup_unreferenced_data(void) { - JournalFile *f1, *f2, *f3; - sd_id128_t id; - - /* For issue #29275. */ - - f1 = test_open_strict("one.journal"); - f2 = test_open_strict("two.journal"); - f3 = test_open_strict("three.journal"); - assert_se(sd_id128_randomize(&id) >= 0); - log_info("boot_id: %s", SD_ID128_TO_STRING(id)); - append_number(f1, 1, &id, NULL); - append_number(f1, 2, &id, NULL); - append_number(f1, 3, &id, NULL); - assert_se(sd_id128_randomize(&id) >= 0); - log_info("boot_id: %s", SD_ID128_TO_STRING(id)); - append_unreferenced_data(f1, &id); - append_number(f2, 4, &id, NULL); - append_number(f2, 5, &id, NULL); - append_number(f2, 6, &id, NULL); - assert_se(sd_id128_randomize(&id) >= 0); - log_info("boot_id: %s", SD_ID128_TO_STRING(id)); - append_unreferenced_data(f2, &id); - append_number(f3, 7, &id, NULL); - append_number(f3, 8, &id, NULL); - append_number(f3, 9, &id, NULL); - test_close(f1); - test_close(f2); - test_close(f3); -} - -static void mkdtemp_chdir_chattr(char *path) { - assert_se(mkdtemp(path)); - assert_se(chdir(path) >= 0); - - /* Speed up things a bit on btrfs, ensuring that CoW is turned off for all files created in our - * directory during the test run */ - (void) chattr_path(path, FS_NOCOW_FL, FS_NOCOW_FL, NULL); -} - -static void test_skip_one(void (*setup)(void)) { - char t[] = "/var/tmp/journal-skip-XXXXXX"; - sd_journal *j; - int r; - - mkdtemp_chdir_chattr(t); - - setup(); - - /* Seek to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ - test_check_numbers_down(j, 9); - sd_journal_close(j); - - /* Seek to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ - assert_se(sd_journal_previous(j) == 0); /* no-op */ - test_check_numbers_down(j, 9); - sd_journal_close(j); - - /* Seek to head twice, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ - assert_ret(sd_journal_seek_head(j)); - assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ - test_check_numbers_down(j, 9); - sd_journal_close(j); - - /* Seek to head, move to previous, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_se(sd_journal_previous(j) == 0); /* no-op */ - assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ - test_check_numbers_down(j, 9); - sd_journal_close(j); - - /* Seek to head, walk several steps, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_se(sd_journal_previous(j) == 0); /* no-op */ - assert_se(sd_journal_previous(j) == 0); /* no-op */ - assert_se(sd_journal_previous(j) == 0); /* no-op */ - assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ - assert_se(sd_journal_previous(j) == 0); /* no-op */ - assert_se(sd_journal_previous(j) == 0); /* no-op */ - test_check_numbers_down(j, 9); - sd_journal_close(j); - - /* Seek to tail, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_tail(j)); - assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ - test_check_numbers_up(j, 9); - sd_journal_close(j); - - /* Seek to tail twice, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_tail(j)); - assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ - assert_ret(sd_journal_seek_tail(j)); - assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ - test_check_numbers_up(j, 9); - sd_journal_close(j); - - /* Seek to tail, move to next, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_tail(j)); - assert_se(sd_journal_next(j) == 0); /* no-op */ - assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ - test_check_numbers_up(j, 9); - sd_journal_close(j); - - /* Seek to tail, walk several steps, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_tail(j)); - assert_se(sd_journal_next(j) == 0); /* no-op */ - assert_se(sd_journal_next(j) == 0); /* no-op */ - assert_se(sd_journal_next(j) == 0); /* no-op */ - assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry. */ - assert_se(sd_journal_next(j) == 0); /* no-op */ - assert_se(sd_journal_next(j) == 0); /* no-op */ - test_check_numbers_up(j, 9); - sd_journal_close(j); - - /* Seek to tail, skip to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_tail(j)); - assert_se(sd_journal_previous_skip(j, 9) == 9); /* pointing to the first entry. */ - test_check_numbers_down(j, 9); - sd_journal_close(j); - - /* Seek to tail, skip to head in a more complex way, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_tail(j)); - assert_se(sd_journal_next(j) == 0); - assert_se(sd_journal_previous_skip(j, 4) == 4); - assert_se(sd_journal_previous_skip(j, 5) == 5); - assert_se(sd_journal_previous(j) == 0); - assert_se(sd_journal_previous_skip(j, 5) == 0); - assert_se(sd_journal_next(j) == 1); - assert_se(sd_journal_previous_skip(j, 5) == 1); - assert_se(sd_journal_next(j) == 1); - assert_se(sd_journal_next(j) == 1); - assert_se(sd_journal_previous(j) == 1); - assert_se(sd_journal_next(j) == 1); - assert_se(sd_journal_next(j) == 1); - assert_se(sd_journal_previous_skip(j, 5) == 3); - test_check_numbers_down(j, 9); - sd_journal_close(j); - - /* Seek to head, skip to tail, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_se(sd_journal_next_skip(j, 9) == 9); - test_check_numbers_up(j, 9); - sd_journal_close(j); - - /* Seek to head, skip to tail in a more complex way, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_ret(sd_journal_seek_head(j)); - assert_se(sd_journal_previous(j) == 0); - assert_se(sd_journal_next_skip(j, 4) == 4); - assert_se(sd_journal_next_skip(j, 5) == 5); - assert_se(sd_journal_next(j) == 0); - assert_se(sd_journal_next_skip(j, 5) == 0); - assert_se(sd_journal_previous(j) == 1); - assert_se(sd_journal_next_skip(j, 5) == 1); - assert_se(sd_journal_previous(j) == 1); - assert_se(sd_journal_previous(j) == 1); - assert_se(sd_journal_next(j) == 1); - assert_se(sd_journal_previous(j) == 1); - assert_se(sd_journal_previous(j) == 1); - assert_se(r = sd_journal_next_skip(j, 5) == 3); - test_check_numbers_up(j, 9); - sd_journal_close(j); - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } - - puts("------------------------------------------------------------"); -} - -TEST(skip) { - test_skip_one(setup_sequential); - test_skip_one(setup_interleaved); -} - -static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) { - char t[] = "/var/tmp/journal-boot-id-XXXXXX"; - sd_journal *j; - _cleanup_free_ BootId *boots = NULL; - size_t n_boots; - - mkdtemp_chdir_chattr(t); - - setup(); - - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_get_boots(j, &boots, &n_boots) >= 0); - assert_se(boots); - assert_se(n_boots == n_boots_expected); - sd_journal_close(j); - - FOREACH_ARRAY(b, boots, n_boots) { - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_find_boot_by_id(j, b->id) == 1); - sd_journal_close(j); - } - - for (int i = - (int) n_boots + 1; i <= (int) n_boots; i++) { - sd_id128_t id; - - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_find_boot_by_offset(j, i, &id) == 1); - if (i <= 0) - assert_se(sd_id128_equal(id, boots[n_boots + i - 1].id)); - else - assert_se(sd_id128_equal(id, boots[i - 1].id)); - sd_journal_close(j); - } - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } - - puts("------------------------------------------------------------"); -} - -TEST(boot_id) { - test_boot_id_one(setup_sequential, 3); - test_boot_id_one(setup_unreferenced_data, 3); -} - -static void test_sequence_numbers_one(void) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - char t[] = "/var/tmp/journal-seq-XXXXXX"; - JournalFile *one, *two; - uint64_t seqnum = 0; - sd_id128_t seqnum_id; - - m = mmap_cache_new(); - assert_se(m != NULL); - - mkdtemp_chdir_chattr(t); - - assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, - UINT64_MAX, NULL, m, NULL, &one) == 0); - - append_number(one, 1, NULL, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 1); - append_number(one, 2, NULL, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 2); - - assert_se(one->header->state == STATE_ONLINE); - assert_se(!sd_id128_equal(one->header->file_id, one->header->machine_id)); - assert_se(!sd_id128_equal(one->header->file_id, one->header->tail_entry_boot_id)); - assert_se(sd_id128_equal(one->header->file_id, one->header->seqnum_id)); - - memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t)); - - assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, - UINT64_MAX, NULL, m, one, &two) == 0); - - assert_se(two->header->state == STATE_ONLINE); - assert_se(!sd_id128_equal(two->header->file_id, one->header->file_id)); - assert_se(sd_id128_equal(two->header->machine_id, one->header->machine_id)); - assert_se(sd_id128_is_null(two->header->tail_entry_boot_id)); /* Not written yet. */ - assert_se(sd_id128_equal(two->header->seqnum_id, one->header->seqnum_id)); - - append_number(two, 3, NULL, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 3); - append_number(two, 4, NULL, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 4); - - /* Verify tail_entry_boot_id. */ - assert_se(sd_id128_equal(two->header->tail_entry_boot_id, one->header->tail_entry_boot_id)); - - test_close(two); - - append_number(one, 5, NULL, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 5); - - append_number(one, 6, NULL, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 6); - - test_close(one); - - /* If the machine-id is not initialized, the header file verification - * (which happens when re-opening a journal file) will fail. */ - if (sd_id128_get_machine(NULL) >= 0) { - /* restart server */ - seqnum = 0; - - assert_se(journal_file_open(-1, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0, - UINT64_MAX, NULL, m, NULL, &two) == 0); - - assert_se(sd_id128_equal(two->header->seqnum_id, seqnum_id)); - - append_number(two, 7, NULL, &seqnum); - printf("seqnum=%"PRIu64"\n", seqnum); - assert_se(seqnum == 5); - - /* So..., here we have the same seqnum in two files with the - * same seqnum_id. */ - - test_close(two); - } - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } -} - -TEST(sequence_numbers) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); - test_sequence_numbers_one(); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); - test_sequence_numbers_one(); -} - -static int intro(void) { - /* journal_file_open() requires a valid machine id */ - if (access("/etc/machine-id", F_OK) != 0) - return log_tests_skipped("/etc/machine-id not found"); - - arg_keep = saved_argc > 1; - - return EXIT_SUCCESS; -} - -DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c deleted file mode 100644 index 2897de4905..0000000000 --- a/src/journal/test-journal-stream.c +++ /dev/null @@ -1,201 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "chattr-util.h" -#include "io-util.h" -#include "journal-file-util.h" -#include "journal-internal.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "rm-rf.h" -#include "tests.h" - -#define N_ENTRIES 200 - -static void verify_contents(sd_journal *j, unsigned skip) { - unsigned i; - - assert_se(j); - - i = 0; - SD_JOURNAL_FOREACH(j) { - const void *d; - char *k, *c; - size_t l; - unsigned u = 0; - - assert_se(sd_journal_get_cursor(j, &k) >= 0); - printf("cursor: %s\n", k); - free(k); - - assert_se(sd_journal_get_data(j, "MAGIC", &d, &l) >= 0); - printf("\t%.*s\n", (int) l, (const char*) d); - - assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0); - assert_se(k = strndup(d, l)); - printf("\t%s\n", k); - - if (skip > 0) { - assert_se(safe_atou(k + 7, &u) >= 0); - assert_se(i == u); - i += skip; - } - - free(k); - - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); - free(c); - } - - if (skip > 0) - assert_se(i == N_ENTRIES); -} - -static void run_test(void) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - JournalFile *one, *two, *three; - char t[] = "/var/tmp/journal-stream-XXXXXX"; - unsigned i; - _cleanup_(sd_journal_closep) sd_journal *j = NULL; - char *z; - const void *data; - size_t l; - dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; - - m = mmap_cache_new(); - assert_se(m != NULL); - - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); - (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL); - - assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one) == 0); - assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two) == 0); - assert_se(journal_file_open(-1, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three) == 0); - - for (i = 0; i < N_ENTRIES; i++) { - char *p, *q; - dual_timestamp ts; - struct iovec iovec[2]; - - dual_timestamp_get(&ts); - - if (ts.monotonic <= previous_ts.monotonic) - ts.monotonic = previous_ts.monotonic + 1; - - if (ts.realtime <= previous_ts.realtime) - ts.realtime = previous_ts.realtime + 1; - - previous_ts = ts; - - assert_se(asprintf(&p, "NUMBER=%u", i) >= 0); - iovec[0] = IOVEC_MAKE(p, strlen(p)); - - assert_se(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo") >= 0); - - iovec[1] = IOVEC_MAKE(q, strlen(q)); - - if (i % 10 == 0) - assert_se(journal_file_append_entry(three, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); - else { - if (i % 3 == 0) - assert_se(journal_file_append_entry(two, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); - - assert_se(journal_file_append_entry(one, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); - } - - free(p); - free(q); - } - - (void) journal_file_offline_close(one); - (void) journal_file_offline_close(two); - (void) journal_file_offline_close(three); - - assert_se(sd_journal_open_directory(&j, t, 0) >= 0); - - assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); - SD_JOURNAL_FOREACH_BACKWARDS(j) { - _cleanup_free_ char *c; - - assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); - printf("\t%.*s\n", (int) l, (const char*) data); - - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); - } - - SD_JOURNAL_FOREACH(j) { - _cleanup_free_ char *c; - - assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); - printf("\t%.*s\n", (int) l, (const char*) data); - - assert_se(sd_journal_get_cursor(j, &c) >= 0); - assert_se(sd_journal_test_cursor(j, c) > 0); - } - - sd_journal_flush_matches(j); - - verify_contents(j, 1); - - printf("NEXT TEST\n"); - assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); - - assert_se(z = journal_make_match_string(j)); - printf("resulting match expression is: %s\n", z); - free(z); - - verify_contents(j, 5); - - printf("NEXT TEST\n"); - sd_journal_flush_matches(j); - assert_se(sd_journal_add_match(j, "MAGIC=waldo", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=10", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=11", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=12", 0) >= 0); - - assert_se(z = journal_make_match_string(j)); - printf("resulting match expression is: %s\n", z); - free(z); - - verify_contents(j, 0); - - assert_se(sd_journal_query_unique(j, "NUMBER") >= 0); - SD_JOURNAL_FOREACH_UNIQUE(j, data, l) - printf("%.*s\n", (int) l, (const char*) data); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); -} - -int main(int argc, char *argv[]) { - - /* journal_file_open() requires a valid machine id */ - if (access("/etc/machine-id", F_OK) != 0) - return log_tests_skipped("/etc/machine-id not found"); - - test_setup_logging(LOG_DEBUG); - - /* Run this test multiple times with different configurations of features. */ - - assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "0", 1) >= 0); - run_test(); - - assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "1", 1) >= 0); - run_test(); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); - run_test(); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); - run_test(); - - return 0; -} diff --git a/src/journal/test-journal-syslog.c b/src/journal/test-journal-syslog.c deleted file mode 100644 index 84cfcefc3a..0000000000 --- a/src/journal/test-journal-syslog.c +++ /dev/null @@ -1,70 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "alloc-util.h" -#include "journald-syslog.h" -#include "macro.h" -#include "string-util.h" -#include "syslog-util.h" -#include "tests.h" - -static void test_syslog_parse_identifier_one(const char *str, - const char *ident, const char *pid, const char *rest, int ret) { - const char *buf = str; - _cleanup_free_ char *ident2 = NULL, *pid2 = NULL; - int ret2; - - ret2 = syslog_parse_identifier(&buf, &ident2, &pid2); - - assert_se(ret == ret2); - assert_se(ident == ident2 || streq_ptr(ident, ident2)); - assert_se(pid == pid2 || streq_ptr(pid, pid2)); - assert_se(streq(buf, rest)); -} - -static void test_syslog_parse_priority_one(const char *str, bool with_facility, int priority, int ret) { - int priority2 = 0, ret2; - - ret2 = syslog_parse_priority(&str, &priority2, with_facility); - - assert_se(ret == ret2); - if (ret2 == 1) - assert_se(priority == priority2); -} - -TEST(syslog_parse_identifier) { - test_syslog_parse_identifier_one("pidu[111]: xxx", "pidu", "111", "xxx", 11); - test_syslog_parse_identifier_one("pidu: xxx", "pidu", NULL, "xxx", 6); - test_syslog_parse_identifier_one("pidu: xxx", "pidu", NULL, " xxx", 6); - test_syslog_parse_identifier_one("pidu xxx", NULL, NULL, "pidu xxx", 0); - test_syslog_parse_identifier_one(" pidu xxx", NULL, NULL, " pidu xxx", 0); - test_syslog_parse_identifier_one("", NULL, NULL, "", 0); - test_syslog_parse_identifier_one(" ", NULL, NULL, " ", 0); - test_syslog_parse_identifier_one(":", "", NULL, "", 1); - test_syslog_parse_identifier_one(": ", "", NULL, " ", 2); - test_syslog_parse_identifier_one(" :", "", NULL, "", 2); - test_syslog_parse_identifier_one(" pidu:", "pidu", NULL, "", 8); - test_syslog_parse_identifier_one("pidu:", "pidu", NULL, "", 5); - test_syslog_parse_identifier_one("pidu: ", "pidu", NULL, "", 6); - test_syslog_parse_identifier_one("pidu : ", NULL, NULL, "pidu : ", 0); -} - -TEST(syslog_parse_priority) { - test_syslog_parse_priority_one("", false, 0, 0); - test_syslog_parse_priority_one("<>", false, 0, 0); - test_syslog_parse_priority_one("<>aaa", false, 0, 0); - test_syslog_parse_priority_one("", false, 0, 0); - test_syslog_parse_priority_one("aaa", false, 0, 0); - test_syslog_parse_priority_one(" ", false, 0, 0); - test_syslog_parse_priority_one(" aaa", false, 0, 0); - test_syslog_parse_priority_one(" aaa", false, 0, 0); - test_syslog_parse_priority_one(" <1>", false, 0, 0); - test_syslog_parse_priority_one("<1>", false, 1, 1); - test_syslog_parse_priority_one("<7>", false, 7, 1); - test_syslog_parse_priority_one("<8>", false, 0, 0); - test_syslog_parse_priority_one("<9>", true, 9, 1); - test_syslog_parse_priority_one("<22>", true, 22, 1); - test_syslog_parse_priority_one("<111>", false, 0, 0); - test_syslog_parse_priority_one("<111>", true, 111, 1); -} - -DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c deleted file mode 100644 index 7853b36495..0000000000 --- a/src/journal/test-journal-verify.c +++ /dev/null @@ -1,210 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include - -#include "chattr-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "journal-file-util.h" -#include "journal-verify.h" -#include "log.h" -#include "mmap-cache.h" -#include "rm-rf.h" -#include "strv.h" -#include "terminal-util.h" -#include "tests.h" - -#define N_ENTRIES 6000 -#define RANDOM_RANGE 77 - -static void bit_toggle(const char *fn, uint64_t p) { - uint8_t b; - ssize_t r; - int fd; - - fd = open(fn, O_RDWR|O_CLOEXEC); - assert_se(fd >= 0); - - r = pread(fd, &b, 1, p/8); - assert_se(r == 1); - - b ^= 1 << (p % 8); - - r = pwrite(fd, &b, 1, p/8); - assert_se(r == 1); - - safe_close(fd); -} - -static int raw_verify(const char *fn, const char *verification_key) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - JournalFile *f; - int r; - - m = mmap_cache_new(); - assert_se(m != NULL); - - r = journal_file_open( - /* fd= */ -1, - fn, - O_RDONLY, - JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), - 0666, - /* compress_threshold_bytes= */ UINT64_MAX, - /* metrics= */ NULL, - m, - /* template= */ NULL, - &f); - if (r < 0) - return r; - - r = journal_file_verify(f, verification_key, NULL, NULL, NULL, false); - (void) journal_file_close(f); - - return r; -} - -static int run_test(const char *verification_key, ssize_t max_iterations) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - char t[] = "/var/tmp/journal-XXXXXX"; - struct stat st; - JournalFile *f; - JournalFile *df; - usec_t from = 0, to = 0, total = 0; - uint64_t start, end; - int r; - - m = mmap_cache_new(); - assert_se(m != NULL); - - /* journal_file_open() requires a valid machine id */ - if (sd_id128_get_machine(NULL) < 0) - return log_tests_skipped("No valid machine ID found"); - - test_setup_logging(LOG_DEBUG); - - assert_se(mkdtemp(t)); - assert_se(chdir(t) >= 0); - (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL); - - log_info("Generating a test journal"); - - assert_se(journal_file_open( - /* fd= */ -1, - "test.journal", - O_RDWR|O_CREAT, - JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), - 0666, - /* compress_threshold_bytes= */ UINT64_MAX, - /* metrics= */ NULL, - m, - /* template= */ NULL, - &df) == 0); - - for (size_t n = 0; n < N_ENTRIES; n++) { - _cleanup_free_ char *test = NULL; - struct iovec iovec; - struct dual_timestamp ts; - - dual_timestamp_get(&ts); - assert_se(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE)); - iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry( - df, - &ts, - /* boot_id= */ NULL, - &iovec, - /* n_iovec= */ 1, - /* seqnum= */ NULL, - /* seqnum_id= */ NULL, - /* ret_object= */ NULL, - /* ret_offset= */ NULL) == 0); - } - - (void) journal_file_offline_close(df); - - log_info("Verifying with key: %s", strna(verification_key)); - - assert_se(journal_file_open( - /* fd= */ -1, - "test.journal", - O_RDONLY, - JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), - 0666, - /* compress_threshold_bytes= */ UINT64_MAX, - /* metrics= */ NULL, - m, - /* template= */ NULL, - &f) == 0); - journal_file_print_header(f); - journal_file_dump(f); - - assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0); - - if (verification_key && JOURNAL_HEADER_SEALED(f->header)) - log_info("=> Validated from %s to %s, %s missing", - FORMAT_TIMESTAMP(from), - FORMAT_TIMESTAMP(to), - FORMAT_TIMESPAN(total > to ? total - to : 0, 0)); - - (void) journal_file_close(f); - assert_se(stat("test.journal", &st) >= 0); - - start = 38448 * 8 + 0; - end = max_iterations < 0 ? (uint64_t)st.st_size * 8 : start + max_iterations; - log_info("Toggling bits %"PRIu64 " to %"PRIu64, start, end); - - for (uint64_t p = start; p < end; p++) { - bit_toggle("test.journal", p); - - if (max_iterations < 0) - log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8); - - r = raw_verify("test.journal", verification_key); - /* Suppress the notice when running in the limited (CI) mode */ - if (verification_key && max_iterations < 0 && r >= 0) - log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8); - - bit_toggle("test.journal", p); - } - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - - return 0; -} - -int main(int argc, char *argv[]) { - const char *verification_key = NULL; - int max_iterations = 512; - - if (argc > 1) { - /* Don't limit the number of iterations when the verification key - * is provided on the command line, we want to do that only in CIs */ - verification_key = argv[1]; - max_iterations = -1; - } - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); - run_test(verification_key, max_iterations); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); - run_test(verification_key, max_iterations); - -#if HAVE_GCRYPT - /* If we're running without any arguments and we're compiled with gcrypt - * check the journal verification stuff with a valid key as well */ - if (argc <= 1) { - verification_key = "c262bd-85187f-0b1b04-877cc5/1c7af8-35a4e900"; - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); - run_test(verification_key, max_iterations); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); - run_test(verification_key, max_iterations); - } -#endif - - return 0; -} diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c deleted file mode 100644 index a0916a0a24..0000000000 --- a/src/journal/test-journal.c +++ /dev/null @@ -1,280 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "chattr-util.h" -#include "io-util.h" -#include "journal-authenticate.h" -#include "journal-file-util.h" -#include "journal-vacuum.h" -#include "log.h" -#include "rm-rf.h" -#include "tests.h" - -static bool arg_keep = false; - -static void mkdtemp_chdir_chattr(char *path) { - assert_se(mkdtemp(path)); - assert_se(chdir(path) >= 0); - - /* Speed up things a bit on btrfs, ensuring that CoW is turned off for all files created in our - * directory during the test run */ - (void) chattr_path(path, FS_NOCOW_FL, FS_NOCOW_FL, NULL); -} - -static void test_non_empty_one(void) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - dual_timestamp ts; - JournalFile *f; - struct iovec iovec; - static const char test[] = "TEST1=1", test2[] = "TEST2=2"; - Object *o, *d; - uint64_t p; - sd_id128_t fake_boot_id; - char t[] = "/var/tmp/journal-XXXXXX"; - - m = mmap_cache_new(); - assert_se(m != NULL); - - mkdtemp_chdir_chattr(t); - - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f) == 0); - - assert_se(dual_timestamp_get(&ts)); - assert_se(sd_id128_randomize(&fake_boot_id) == 0); - - iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); - - iovec = IOVEC_MAKE_STRING(test2); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); - - iovec = IOVEC_MAKE_STRING(test); - assert_se(journal_file_append_entry(f, &ts, &fake_boot_id, &iovec, 1, NULL, NULL, NULL, NULL) == 0); - -#if HAVE_GCRYPT - journal_file_append_tag(f); -#endif - journal_file_dump(f); - - assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); - - assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); - - assert_se(journal_file_next_entry(f, 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_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 0); - - assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); - - assert_se(journal_file_find_data_object(f, test, strlen(test), &d, NULL) == 1); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); - - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); - - assert_se(journal_file_find_data_object(f, test2, strlen(test2), &d, NULL) == 1); - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); - - assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); - - assert_se(journal_file_find_data_object(f, "quux", 4, &d, NULL) == 0); - - assert_se(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 1); - - assert_se(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 3); - - assert_se(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1); - assert_se(le64toh(o->entry.seqnum) == 2); - - assert_se(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0); - - journal_file_rotate(&f, m, JOURNAL_SEAL|JOURNAL_COMPRESS, UINT64_MAX, NULL); - journal_file_rotate(&f, m, JOURNAL_SEAL|JOURNAL_COMPRESS, UINT64_MAX, NULL); - - (void) journal_file_offline_close(f); - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } - - puts("------------------------------------------------------------"); -} - -TEST(non_empty) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); - test_non_empty_one(); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); - test_non_empty_one(); -} - -static void test_empty_one(void) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - JournalFile *f1, *f2, *f3, *f4; - char t[] = "/var/tmp/journal-XXXXXX"; - - m = mmap_cache_new(); - assert_se(m != NULL); - - mkdtemp_chdir_chattr(t); - - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1) == 0); - assert_se(journal_file_open(-1, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2) == 0); - assert_se(journal_file_open(-1, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3) == 0); - assert_se(journal_file_open(-1, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4) == 0); - - journal_file_print_header(f1); - puts(""); - journal_file_print_header(f2); - puts(""); - journal_file_print_header(f3); - puts(""); - journal_file_print_header(f4); - puts(""); - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } - - (void) journal_file_offline_close(f1); - (void) journal_file_offline_close(f2); - (void) journal_file_offline_close(f3); - (void) journal_file_offline_close(f4); -} - -TEST(empty) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); - test_empty_one(); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); - test_empty_one(); -} - -#if HAVE_COMPRESSION -static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { - _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; - dual_timestamp ts; - JournalFile *f; - struct iovec iovec; - Object *o; - uint64_t p; - char t[] = "/var/tmp/journal-XXXXXX"; - char data[2048] = "FIELD="; - bool is_compressed; - int r; - - assert_se(data_size <= sizeof(data)); - - m = mmap_cache_new(); - assert_se(m != NULL); - - mkdtemp_chdir_chattr(t); - - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f) == 0); - - dual_timestamp_get(&ts); - - iovec = IOVEC_MAKE(data, data_size); - assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); - -#if HAVE_GCRYPT - journal_file_append_tag(f); -#endif - journal_file_dump(f); - - /* We have to partially reimplement some of the dump logic, because the normal next_entry does the - * decompression for us. */ - p = le64toh(f->header->header_size); - for (;;) { - r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); - assert_se(r == 0); - if (o->object.type == OBJECT_DATA) - break; - - assert_se(p < le64toh(f->header->tail_object_offset)); - p = p + ALIGN64(le64toh(o->object.size)); - } - - is_compressed = COMPRESSION_FROM_OBJECT(o) != COMPRESSION_NONE; - - (void) journal_file_offline_close(f); - - log_info("Done..."); - - if (arg_keep) - log_info("Not removing %s", t); - else { - journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); - - assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); - } - - puts("------------------------------------------------------------"); - - return is_compressed; -} - -static void test_min_compress_size_one(void) { - /* Note that XZ will actually fail to compress anything under 80 bytes, so you have to choose the limits - * carefully */ - - /* DEFAULT_MIN_COMPRESS_SIZE is 512 */ - assert_se(!check_compressed(UINT64_MAX, 255)); - assert_se(check_compressed(UINT64_MAX, 513)); - - /* compress everything */ - assert_se(check_compressed(0, 96)); - assert_se(check_compressed(8, 96)); - - /* Ensure we don't try to compress less than 8 bytes */ - assert_se(!check_compressed(0, 7)); - - /* check boundary conditions */ - assert_se(check_compressed(256, 256)); - assert_se(!check_compressed(256, 255)); -} - -TEST(min_compress_size) { - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); - test_min_compress_size_one(); - - assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); - test_min_compress_size_one(); -} -#endif - -static int intro(void) { - arg_keep = saved_argc > 1; - - /* journal_file_open() requires a valid machine id */ - if (access("/etc/machine-id", F_OK) != 0) - return log_tests_skipped("/etc/machine-id not found"); - - return EXIT_SUCCESS; -} - -DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/journal/test-journald-config.c b/src/journal/test-journald-config.c new file mode 100644 index 0000000000..1a6c531f4e --- /dev/null +++ b/src/journal/test-journald-config.c @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "journald-server.h" +#include "tests.h" + +#define _COMPRESS_PARSE_CHECK(str, enab, thresh, varname) \ + do { \ + JournalCompressOptions varname = {true, 111}; \ + config_parse_compress("", "", 0, "", 0, "", 0, str, \ + &varname, NULL); \ + assert_se((enab) == varname.enabled); \ + if (varname.enabled) \ + assert_se((thresh) == varname.threshold_bytes); \ + } while (0) + +#define COMPRESS_PARSE_CHECK(str, enabled, threshold) \ + _COMPRESS_PARSE_CHECK(str, enabled, threshold, conf##__COUNTER__) + +TEST(config_compress) { + COMPRESS_PARSE_CHECK("yes", true, 111); + COMPRESS_PARSE_CHECK("no", false, 111); + COMPRESS_PARSE_CHECK("y", true, 111); + COMPRESS_PARSE_CHECK("n", false, 111); + COMPRESS_PARSE_CHECK("true", true, 111); + COMPRESS_PARSE_CHECK("false", false, 111); + COMPRESS_PARSE_CHECK("t", true, 111); + COMPRESS_PARSE_CHECK("f", false, 111); + COMPRESS_PARSE_CHECK("on", true, 111); + COMPRESS_PARSE_CHECK("off", false, 111); + + /* Weird size/bool overlapping case. We preserve backward compatibility instead of assuming these are byte + * counts. */ + COMPRESS_PARSE_CHECK("1", true, 111); + COMPRESS_PARSE_CHECK("0", false, 111); + + /* IEC sizing */ + COMPRESS_PARSE_CHECK("1B", true, 1); + COMPRESS_PARSE_CHECK("1K", true, 1024); + COMPRESS_PARSE_CHECK("1M", true, 1024 * 1024); + COMPRESS_PARSE_CHECK("1G", true, 1024 * 1024 * 1024); + + /* Invalid Case */ + COMPRESS_PARSE_CHECK("-1", true, 111); + COMPRESS_PARSE_CHECK("blah blah", true, 111); + COMPRESS_PARSE_CHECK("", true, UINT64_MAX); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/journal/test-journald-syslog.c b/src/journal/test-journald-syslog.c new file mode 100644 index 0000000000..84cfcefc3a --- /dev/null +++ b/src/journal/test-journald-syslog.c @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "journald-syslog.h" +#include "macro.h" +#include "string-util.h" +#include "syslog-util.h" +#include "tests.h" + +static void test_syslog_parse_identifier_one(const char *str, + const char *ident, const char *pid, const char *rest, int ret) { + const char *buf = str; + _cleanup_free_ char *ident2 = NULL, *pid2 = NULL; + int ret2; + + ret2 = syslog_parse_identifier(&buf, &ident2, &pid2); + + assert_se(ret == ret2); + assert_se(ident == ident2 || streq_ptr(ident, ident2)); + assert_se(pid == pid2 || streq_ptr(pid, pid2)); + assert_se(streq(buf, rest)); +} + +static void test_syslog_parse_priority_one(const char *str, bool with_facility, int priority, int ret) { + int priority2 = 0, ret2; + + ret2 = syslog_parse_priority(&str, &priority2, with_facility); + + assert_se(ret == ret2); + if (ret2 == 1) + assert_se(priority == priority2); +} + +TEST(syslog_parse_identifier) { + test_syslog_parse_identifier_one("pidu[111]: xxx", "pidu", "111", "xxx", 11); + test_syslog_parse_identifier_one("pidu: xxx", "pidu", NULL, "xxx", 6); + test_syslog_parse_identifier_one("pidu: xxx", "pidu", NULL, " xxx", 6); + test_syslog_parse_identifier_one("pidu xxx", NULL, NULL, "pidu xxx", 0); + test_syslog_parse_identifier_one(" pidu xxx", NULL, NULL, " pidu xxx", 0); + test_syslog_parse_identifier_one("", NULL, NULL, "", 0); + test_syslog_parse_identifier_one(" ", NULL, NULL, " ", 0); + test_syslog_parse_identifier_one(":", "", NULL, "", 1); + test_syslog_parse_identifier_one(": ", "", NULL, " ", 2); + test_syslog_parse_identifier_one(" :", "", NULL, "", 2); + test_syslog_parse_identifier_one(" pidu:", "pidu", NULL, "", 8); + test_syslog_parse_identifier_one("pidu:", "pidu", NULL, "", 5); + test_syslog_parse_identifier_one("pidu: ", "pidu", NULL, "", 6); + test_syslog_parse_identifier_one("pidu : ", NULL, NULL, "pidu : ", 0); +} + +TEST(syslog_parse_priority) { + test_syslog_parse_priority_one("", false, 0, 0); + test_syslog_parse_priority_one("<>", false, 0, 0); + test_syslog_parse_priority_one("<>aaa", false, 0, 0); + test_syslog_parse_priority_one("", false, 0, 0); + test_syslog_parse_priority_one("aaa", false, 0, 0); + test_syslog_parse_priority_one(" ", false, 0, 0); + test_syslog_parse_priority_one(" aaa", false, 0, 0); + test_syslog_parse_priority_one(" aaa", false, 0, 0); + test_syslog_parse_priority_one(" <1>", false, 0, 0); + test_syslog_parse_priority_one("<1>", false, 1, 1); + test_syslog_parse_priority_one("<7>", false, 7, 1); + test_syslog_parse_priority_one("<8>", false, 0, 0); + test_syslog_parse_priority_one("<9>", true, 9, 1); + test_syslog_parse_priority_one("<22>", true, 22, 1); + test_syslog_parse_priority_one("<111>", false, 0, 0); + test_syslog_parse_priority_one("<111>", true, 111, 1); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index bcf8e1faf8..5d18f974ba 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -172,6 +172,10 @@ simple_tests += files( 'sd-device/test-sd-device-monitor.c', 'sd-device/test-sd-device.c', 'sd-event/test-event.c', + 'sd-journal/test-journal-flush.c', + 'sd-journal/test-journal-interleaving.c', + 'sd-journal/test-journal-stream.c', + 'sd-journal/test-journal.c', 'sd-login/test-login.c', 'sd-netlink/test-netlink.c', ) @@ -234,6 +238,14 @@ libsystemd_tests += [ 'dependencies' : threads, 'timeout' : 120, }, + { + 'sources' : files('sd-journal/test-journal-append.c'), + 'type' : 'manual', + }, + { + 'sources' : files('sd-journal/test-journal-verify.c'), + 'timeout' : 90, + }, { 'sources' : files('sd-resolve/test-resolve.c'), 'dependencies' : threads, diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c new file mode 100644 index 0000000000..fbbf44add4 --- /dev/null +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "chattr-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "io-util.h" +#include "journal-file-util.h" +#include "log.h" +#include "mmap-cache.h" +#include "parse-util.h" +#include "random-util.h" +#include "rm-rf.h" +#include "strv.h" +#include "terminal-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static int journal_append_message(JournalFile *mj, const char *message) { + struct iovec iovec; + struct dual_timestamp ts; + + assert(mj); + assert(message); + + dual_timestamp_get(&ts); + iovec = IOVEC_MAKE_STRING(message); + return journal_file_append_entry( + mj, + &ts, + /* boot_id= */ NULL, + &iovec, + /* n_iovec= */ 1, + /* seqnum= */ NULL, + /* seqnum_id= */ NULL, + /* ret_object= */ NULL, + /* ret_offset= */ NULL); +} + +static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { + _cleanup_(mmap_cache_unrefp) MMapCache *mmap_cache = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL; + _cleanup_(journal_file_offline_closep) JournalFile *mj = NULL; + uint64_t start, end; + int r; + + mmap_cache = mmap_cache_new(); + assert_se(mmap_cache); + + /* journal_file_open() requires a valid machine id */ + if (sd_id128_get_machine(NULL) < 0) + return log_tests_skipped("No valid machine ID found"); + + assert_se(mkdtemp_malloc("/tmp/journal-append-XXXXXX", &tempdir) >= 0); + assert_se(chdir(tempdir) >= 0); + (void) chattr_path(tempdir, FS_NOCOW_FL, FS_NOCOW_FL, NULL); + + log_debug("Opening journal %s/system.journal", tempdir); + + r = journal_file_open( + /* fd= */ -1, + "system.journal", + O_RDWR|O_CREAT, + JOURNAL_COMPRESS, + 0644, + /* compress_threshold_bytes= */ UINT64_MAX, + /* metrics= */ NULL, + mmap_cache, + /* template= */ NULL, + &mj); + if (r < 0) + return log_error_errno(r, "Failed to open the journal: %m"); + + assert_se(mj); + + /* Add a couple of initial messages */ + for (int i = 0; i < 10; i++) { + _cleanup_free_ char *message = NULL; + + assert_se(asprintf(&message, "MESSAGE=Initial message %d", i) >= 0); + r = journal_append_message(mj, message); + if (r < 0) + return log_error_errno(r, "Failed to write to the journal: %m"); + } + + start = start_offset == UINT64_MAX ? random_u64() % mj->last_stat.st_size : start_offset; + end = (uint64_t) mj->last_stat.st_size; + + /* Print the initial offset at which we start flipping bits, which can be + * later used to reproduce a potential fail */ + log_info("Start offset: %" PRIu64 ", corrupt-step: %" PRIu64, start, step); + fflush(stdout); + + if (start >= end) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Start offset >= journal size, can't continue"); + + for (uint64_t offset = start; offset < end; offset += step) { + _cleanup_free_ char *message = NULL; + uint8_t b; + + /* Flip a bit in the journal file */ + r = pread(mj->fd, &b, 1, offset); + assert_se(r == 1); + b |= 0x1; + r = pwrite(mj->fd, &b, 1, offset); + assert_se(r == 1); + + /* Close and reopen the journal to flush all caches and remap + * the corrupted journal */ + mj = journal_file_offline_close(mj); + r = journal_file_open( + /* fd= */ -1, + "system.journal", + O_RDWR|O_CREAT, + JOURNAL_COMPRESS, + 0644, + /* compress_threshold_bytes= */ UINT64_MAX, + /* metrics= */ NULL, + mmap_cache, + /* template= */ NULL, + &mj); + if (r < 0) { + /* The corrupted journal might get rejected during reopening + * if it's corrupted enough (especially its header), so + * treat this as a success if it doesn't crash */ + log_info_errno(r, "Failed to reopen the journal: %m"); + break; + } + + /* Try to write something to the (possibly corrupted) journal */ + assert_se(asprintf(&message, "MESSAGE=Hello world %" PRIu64, offset) >= 0); + r = journal_append_message(mj, message); + if (r < 0) { + /* We care only about crashes or sanitizer errors, + * failed write without any crash is a success */ + log_info_errno(r, "Failed to write to the journal: %m"); + break; + } + } + + return 0; +} + +int main(int argc, char *argv[]) { + uint64_t start_offset = UINT64_MAX; + uint64_t iterations = 100; + uint64_t iteration_step = 1; + uint64_t corrupt_step = 31; + bool sequential = false, run_one = false; + int c, r; + + test_setup_logging(LOG_DEBUG); + + enum { + ARG_START_OFFSET = 0x1000, + ARG_ITERATIONS, + ARG_ITERATION_STEP, + ARG_CORRUPT_STEP, + ARG_SEQUENTIAL, + ARG_RUN_ONE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "start-offset", required_argument, NULL, ARG_START_OFFSET }, + { "iterations", required_argument, NULL, ARG_ITERATIONS }, + { "iteration-step", required_argument, NULL, ARG_ITERATION_STEP }, + { "corrupt-step", required_argument, NULL, ARG_CORRUPT_STEP }, + { "sequential", no_argument, NULL, ARG_SEQUENTIAL }, + { "run-one", required_argument, NULL, ARG_RUN_ONE }, + {} + }; + + assert_se(argc >= 0); + assert_se(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + printf("Syntax:\n" + " %s [OPTION...]\n" + "Options:\n" + " --start-offset=OFFSET Offset at which to start corrupting the journal\n" + " (default: random offset is picked, unless\n" + " --sequential is used - in that case we use 0 + iteration)\n" + " --iterations=ITER Number of iterations to perform before exiting\n" + " (default: 100)\n" + " --iteration-step=STEP Iteration step (default: 1)\n" + " --corrupt-step=STEP Corrupt every n-th byte starting from OFFSET (default: 31)\n" + " --sequential Go through offsets sequentially instead of picking\n" + " a random one on each iteration. If set, we go through\n" + " offsets <0; ITER), or 0) + /* Reached the end of the journal file */ + break; + } + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/sd-journal/test-journal-flush.c b/src/libsystemd/sd-journal/test-journal-flush.c new file mode 100644 index 0000000000..b5956d24fc --- /dev/null +++ b/src/libsystemd/sd-journal/test-journal-flush.c @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-journal.h" + +#include "alloc-util.h" +#include "chattr-util.h" +#include "journal-file-util.h" +#include "journal-internal.h" +#include "macro.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tmpfile-util.h" + +static void test_journal_flush(int argc, char *argv[]) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + _cleanup_free_ char *fn = NULL; + _cleanup_(rm_rf_physical_and_freep) char *dn = NULL; + JournalFile *new_journal = NULL; + sd_journal *j = NULL; + unsigned n = 0; + int r; + + assert_se(m = mmap_cache_new()); + assert_se(mkdtemp_malloc("/var/tmp/test-journal-flush.XXXXXX", &dn) >= 0); + (void) chattr_path(dn, FS_NOCOW_FL, FS_NOCOW_FL, NULL); + + assert_se(fn = path_join(dn, "test.journal")); + + r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal); + assert_se(r >= 0); + + if (argc > 1) + r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), 0); + else + r = sd_journal_open(&j, 0); + assert_se(r == 0); + + sd_journal_set_data_threshold(j, 0); + + SD_JOURNAL_FOREACH(j) { + Object *o; + JournalFile *f; + + f = j->current_file; + assert_se(f && f->current_offset > 0); + + r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); + if (r < 0) + log_error_errno(r, "journal_file_move_to_object failed: %m"); + assert_se(r >= 0); + + r = journal_file_copy_entry(f, new_journal, o, f->current_offset, NULL, NULL); + if (r < 0) + log_warning_errno(r, "journal_file_copy_entry failed: %m"); + assert_se(r >= 0 || + IN_SET(r, -EBADMSG, /* corrupted file */ + -EPROTONOSUPPORT, /* unsupported compression */ + -EIO, /* file rotated */ + -EREMCHG)); /* clock rollback */ + + if (++n >= 10000) + break; + } + + sd_journal_close(j); + + (void) journal_file_offline_close(new_journal); +} + +int main(int argc, char *argv[]) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + test_journal_flush(argc, argv); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + test_journal_flush(argc, argv); + + return 0; +} diff --git a/src/libsystemd/sd-journal/test-journal-interleaving.c b/src/libsystemd/sd-journal/test-journal-interleaving.c new file mode 100644 index 0000000000..d8588759d1 --- /dev/null +++ b/src/libsystemd/sd-journal/test-journal-interleaving.c @@ -0,0 +1,559 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-id128.h" +#include "sd-journal.h" + +#include "alloc-util.h" +#include "chattr-util.h" +#include "io-util.h" +#include "journal-file-util.h" +#include "journal-vacuum.h" +#include "log.h" +#include "logs-show.h" +#include "parse-util.h" +#include "rm-rf.h" +#include "tests.h" + +/* This program tests skipping around in a multi-file journal. */ + +static bool arg_keep = false; +static dual_timestamp previous_ts = {}; + +_noreturn_ static void log_assert_errno(const char *text, int error, const char *file, unsigned line, const char *func) { + log_internal(LOG_CRIT, error, file, line, func, + "'%s' failed at %s:%u (%s): %m", text, file, line, func); + abort(); +} + +#define assert_ret(expr) \ + do { \ + int _r_ = (expr); \ + if (_unlikely_(_r_ < 0)) \ + log_assert_errno(#expr, -_r_, PROJECT_FILE, __LINE__, __func__); \ + } while (false) + +static JournalFile *test_open_internal(const char *name, JournalFileFlags flags) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + JournalFile *f; + + m = mmap_cache_new(); + assert_se(m != NULL); + + assert_ret(journal_file_open(-1, name, O_RDWR|O_CREAT, flags, 0644, UINT64_MAX, NULL, m, NULL, &f)); + return f; +} + +static JournalFile *test_open(const char *name) { + return test_open_internal(name, JOURNAL_COMPRESS); +} + +static JournalFile *test_open_strict(const char *name) { + return test_open_internal(name, JOURNAL_COMPRESS | JOURNAL_STRICT_ORDER); +} + +static void test_close(JournalFile *f) { + (void) journal_file_offline_close(f); +} + +static void append_number(JournalFile *f, int n, const sd_id128_t *boot_id, uint64_t *seqnum) { + _cleanup_free_ char *p = NULL, *q = NULL; + dual_timestamp ts; + struct iovec iovec[2]; + size_t n_iov = 0; + + dual_timestamp_get(&ts); + + if (ts.monotonic <= previous_ts.monotonic) + ts.monotonic = previous_ts.monotonic + 1; + + if (ts.realtime <= previous_ts.realtime) + ts.realtime = previous_ts.realtime + 1; + + previous_ts = ts; + + assert_se(asprintf(&p, "NUMBER=%d", n) >= 0); + iovec[n_iov++] = IOVEC_MAKE_STRING(p); + + if (boot_id) { + assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id))); + iovec[n_iov++] = IOVEC_MAKE_STRING(q); + } + + assert_ret(journal_file_append_entry(f, &ts, boot_id, iovec, n_iov, seqnum, NULL, NULL, NULL)); +} + +static void append_unreferenced_data(JournalFile *f, const sd_id128_t *boot_id) { + _cleanup_free_ char *q = NULL; + dual_timestamp ts; + struct iovec iovec; + + assert(boot_id); + + ts.monotonic = usec_sub_unsigned(previous_ts.monotonic, 10); + ts.realtime = usec_sub_unsigned(previous_ts.realtime, 10); + + assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id))); + iovec = IOVEC_MAKE_STRING(q); + + assert_se(journal_file_append_entry(f, &ts, boot_id, &iovec, 1, NULL, NULL, NULL, NULL) == -EREMCHG); +} + +static void test_check_number(sd_journal *j, int n) { + sd_id128_t boot_id; + const void *d; + _cleanup_free_ char *k = NULL; + size_t l; + int x; + + assert_se(sd_journal_get_monotonic_usec(j, NULL, &boot_id) >= 0); + assert_ret(sd_journal_get_data(j, "NUMBER", &d, &l)); + assert_se(k = strndup(d, l)); + printf("%s %s (expected=%i)\n", SD_ID128_TO_STRING(boot_id), k, n); + + assert_se(safe_atoi(k + STRLEN("NUMBER="), &x) >= 0); + assert_se(n == x); +} + +static void test_check_numbers_down(sd_journal *j, int count) { + int i; + + for (i = 1; i <= count; i++) { + int r; + test_check_number(j, i); + assert_ret(r = sd_journal_next(j)); + if (i == count) + assert_se(r == 0); + else + assert_se(r == 1); + } + +} + +static void test_check_numbers_up(sd_journal *j, int count) { + for (int i = count; i >= 1; i--) { + int r; + test_check_number(j, i); + assert_ret(r = sd_journal_previous(j)); + if (i == 1) + assert_se(r == 0); + else + assert_se(r == 1); + } + +} + +static void setup_sequential(void) { + JournalFile *f1, *f2, *f3; + sd_id128_t id; + + f1 = test_open("one.journal"); + f2 = test_open("two.journal"); + f3 = test_open("three.journal"); + assert_se(sd_id128_randomize(&id) >= 0); + log_info("boot_id: %s", SD_ID128_TO_STRING(id)); + append_number(f1, 1, &id, NULL); + append_number(f1, 2, &id, NULL); + append_number(f1, 3, &id, NULL); + append_number(f2, 4, &id, NULL); + assert_se(sd_id128_randomize(&id) >= 0); + log_info("boot_id: %s", SD_ID128_TO_STRING(id)); + append_number(f2, 5, &id, NULL); + append_number(f2, 6, &id, NULL); + append_number(f3, 7, &id, NULL); + append_number(f3, 8, &id, NULL); + assert_se(sd_id128_randomize(&id) >= 0); + log_info("boot_id: %s", SD_ID128_TO_STRING(id)); + append_number(f3, 9, &id, NULL); + test_close(f1); + test_close(f2); + test_close(f3); +} + +static void setup_interleaved(void) { + JournalFile *f1, *f2, *f3; + sd_id128_t id; + + f1 = test_open("one.journal"); + f2 = test_open("two.journal"); + f3 = test_open("three.journal"); + assert_se(sd_id128_randomize(&id) >= 0); + log_info("boot_id: %s", SD_ID128_TO_STRING(id)); + append_number(f1, 1, &id, NULL); + append_number(f2, 2, &id, NULL); + append_number(f3, 3, &id, NULL); + append_number(f1, 4, &id, NULL); + append_number(f2, 5, &id, NULL); + append_number(f3, 6, &id, NULL); + append_number(f1, 7, &id, NULL); + append_number(f2, 8, &id, NULL); + append_number(f3, 9, &id, NULL); + test_close(f1); + test_close(f2); + test_close(f3); +} + +static void setup_unreferenced_data(void) { + JournalFile *f1, *f2, *f3; + sd_id128_t id; + + /* For issue #29275. */ + + f1 = test_open_strict("one.journal"); + f2 = test_open_strict("two.journal"); + f3 = test_open_strict("three.journal"); + assert_se(sd_id128_randomize(&id) >= 0); + log_info("boot_id: %s", SD_ID128_TO_STRING(id)); + append_number(f1, 1, &id, NULL); + append_number(f1, 2, &id, NULL); + append_number(f1, 3, &id, NULL); + assert_se(sd_id128_randomize(&id) >= 0); + log_info("boot_id: %s", SD_ID128_TO_STRING(id)); + append_unreferenced_data(f1, &id); + append_number(f2, 4, &id, NULL); + append_number(f2, 5, &id, NULL); + append_number(f2, 6, &id, NULL); + assert_se(sd_id128_randomize(&id) >= 0); + log_info("boot_id: %s", SD_ID128_TO_STRING(id)); + append_unreferenced_data(f2, &id); + append_number(f3, 7, &id, NULL); + append_number(f3, 8, &id, NULL); + append_number(f3, 9, &id, NULL); + test_close(f1); + test_close(f2); + test_close(f3); +} + +static void mkdtemp_chdir_chattr(char *path) { + assert_se(mkdtemp(path)); + assert_se(chdir(path) >= 0); + + /* Speed up things a bit on btrfs, ensuring that CoW is turned off for all files created in our + * directory during the test run */ + (void) chattr_path(path, FS_NOCOW_FL, FS_NOCOW_FL, NULL); +} + +static void test_skip_one(void (*setup)(void)) { + char t[] = "/var/tmp/journal-skip-XXXXXX"; + sd_journal *j; + int r; + + mkdtemp_chdir_chattr(t); + + setup(); + + /* Seek to head, iterate down. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ + test_check_numbers_down(j, 9); + sd_journal_close(j); + + /* Seek to head, iterate down. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ + assert_se(sd_journal_previous(j) == 0); /* no-op */ + test_check_numbers_down(j, 9); + sd_journal_close(j); + + /* Seek to head twice, iterate down. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ + assert_ret(sd_journal_seek_head(j)); + assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ + test_check_numbers_down(j, 9); + sd_journal_close(j); + + /* Seek to head, move to previous, then iterate down. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_se(sd_journal_previous(j) == 0); /* no-op */ + assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ + test_check_numbers_down(j, 9); + sd_journal_close(j); + + /* Seek to head, walk several steps, then iterate down. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_se(sd_journal_previous(j) == 0); /* no-op */ + assert_se(sd_journal_previous(j) == 0); /* no-op */ + assert_se(sd_journal_previous(j) == 0); /* no-op */ + assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ + assert_se(sd_journal_previous(j) == 0); /* no-op */ + assert_se(sd_journal_previous(j) == 0); /* no-op */ + test_check_numbers_down(j, 9); + sd_journal_close(j); + + /* Seek to tail, iterate up. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ + test_check_numbers_up(j, 9); + sd_journal_close(j); + + /* Seek to tail twice, iterate up. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ + assert_ret(sd_journal_seek_tail(j)); + assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ + test_check_numbers_up(j, 9); + sd_journal_close(j); + + /* Seek to tail, move to next, then iterate up. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_se(sd_journal_next(j) == 0); /* no-op */ + assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ + test_check_numbers_up(j, 9); + sd_journal_close(j); + + /* Seek to tail, walk several steps, then iterate up. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_se(sd_journal_next(j) == 0); /* no-op */ + assert_se(sd_journal_next(j) == 0); /* no-op */ + assert_se(sd_journal_next(j) == 0); /* no-op */ + assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry. */ + assert_se(sd_journal_next(j) == 0); /* no-op */ + assert_se(sd_journal_next(j) == 0); /* no-op */ + test_check_numbers_up(j, 9); + sd_journal_close(j); + + /* Seek to tail, skip to head, iterate down. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_se(sd_journal_previous_skip(j, 9) == 9); /* pointing to the first entry. */ + test_check_numbers_down(j, 9); + sd_journal_close(j); + + /* Seek to tail, skip to head in a more complex way, then iterate down. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_tail(j)); + assert_se(sd_journal_next(j) == 0); + assert_se(sd_journal_previous_skip(j, 4) == 4); + assert_se(sd_journal_previous_skip(j, 5) == 5); + assert_se(sd_journal_previous(j) == 0); + assert_se(sd_journal_previous_skip(j, 5) == 0); + assert_se(sd_journal_next(j) == 1); + assert_se(sd_journal_previous_skip(j, 5) == 1); + assert_se(sd_journal_next(j) == 1); + assert_se(sd_journal_next(j) == 1); + assert_se(sd_journal_previous(j) == 1); + assert_se(sd_journal_next(j) == 1); + assert_se(sd_journal_next(j) == 1); + assert_se(sd_journal_previous_skip(j, 5) == 3); + test_check_numbers_down(j, 9); + sd_journal_close(j); + + /* Seek to head, skip to tail, iterate up. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_se(sd_journal_next_skip(j, 9) == 9); + test_check_numbers_up(j, 9); + sd_journal_close(j); + + /* Seek to head, skip to tail in a more complex way, then iterate up. */ + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_seek_head(j)); + assert_se(sd_journal_previous(j) == 0); + assert_se(sd_journal_next_skip(j, 4) == 4); + assert_se(sd_journal_next_skip(j, 5) == 5); + assert_se(sd_journal_next(j) == 0); + assert_se(sd_journal_next_skip(j, 5) == 0); + assert_se(sd_journal_previous(j) == 1); + assert_se(sd_journal_next_skip(j, 5) == 1); + assert_se(sd_journal_previous(j) == 1); + assert_se(sd_journal_previous(j) == 1); + assert_se(sd_journal_next(j) == 1); + assert_se(sd_journal_previous(j) == 1); + assert_se(sd_journal_previous(j) == 1); + assert_se(r = sd_journal_next_skip(j, 5) == 3); + test_check_numbers_up(j, 9); + sd_journal_close(j); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } + + puts("------------------------------------------------------------"); +} + +TEST(skip) { + test_skip_one(setup_sequential); + test_skip_one(setup_interleaved); +} + +static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) { + char t[] = "/var/tmp/journal-boot-id-XXXXXX"; + sd_journal *j; + _cleanup_free_ BootId *boots = NULL; + size_t n_boots; + + mkdtemp_chdir_chattr(t); + + setup(); + + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_se(journal_get_boots(j, &boots, &n_boots) >= 0); + assert_se(boots); + assert_se(n_boots == n_boots_expected); + sd_journal_close(j); + + FOREACH_ARRAY(b, boots, n_boots) { + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_se(journal_find_boot_by_id(j, b->id) == 1); + sd_journal_close(j); + } + + for (int i = - (int) n_boots + 1; i <= (int) n_boots; i++) { + sd_id128_t id; + + assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_se(journal_find_boot_by_offset(j, i, &id) == 1); + if (i <= 0) + assert_se(sd_id128_equal(id, boots[n_boots + i - 1].id)); + else + assert_se(sd_id128_equal(id, boots[i - 1].id)); + sd_journal_close(j); + } + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } + + puts("------------------------------------------------------------"); +} + +TEST(boot_id) { + test_boot_id_one(setup_sequential, 3); + test_boot_id_one(setup_unreferenced_data, 3); +} + +static void test_sequence_numbers_one(void) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + char t[] = "/var/tmp/journal-seq-XXXXXX"; + JournalFile *one, *two; + uint64_t seqnum = 0; + sd_id128_t seqnum_id; + + m = mmap_cache_new(); + assert_se(m != NULL); + + mkdtemp_chdir_chattr(t); + + assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, + UINT64_MAX, NULL, m, NULL, &one) == 0); + + append_number(one, 1, NULL, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 1); + append_number(one, 2, NULL, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 2); + + assert_se(one->header->state == STATE_ONLINE); + assert_se(!sd_id128_equal(one->header->file_id, one->header->machine_id)); + assert_se(!sd_id128_equal(one->header->file_id, one->header->tail_entry_boot_id)); + assert_se(sd_id128_equal(one->header->file_id, one->header->seqnum_id)); + + memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t)); + + assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, + UINT64_MAX, NULL, m, one, &two) == 0); + + assert_se(two->header->state == STATE_ONLINE); + assert_se(!sd_id128_equal(two->header->file_id, one->header->file_id)); + assert_se(sd_id128_equal(two->header->machine_id, one->header->machine_id)); + assert_se(sd_id128_is_null(two->header->tail_entry_boot_id)); /* Not written yet. */ + assert_se(sd_id128_equal(two->header->seqnum_id, one->header->seqnum_id)); + + append_number(two, 3, NULL, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 3); + append_number(two, 4, NULL, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 4); + + /* Verify tail_entry_boot_id. */ + assert_se(sd_id128_equal(two->header->tail_entry_boot_id, one->header->tail_entry_boot_id)); + + test_close(two); + + append_number(one, 5, NULL, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 5); + + append_number(one, 6, NULL, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 6); + + test_close(one); + + /* If the machine-id is not initialized, the header file verification + * (which happens when re-opening a journal file) will fail. */ + if (sd_id128_get_machine(NULL) >= 0) { + /* restart server */ + seqnum = 0; + + assert_se(journal_file_open(-1, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0, + UINT64_MAX, NULL, m, NULL, &two) == 0); + + assert_se(sd_id128_equal(two->header->seqnum_id, seqnum_id)); + + append_number(two, 7, NULL, &seqnum); + printf("seqnum=%"PRIu64"\n", seqnum); + assert_se(seqnum == 5); + + /* So..., here we have the same seqnum in two files with the + * same seqnum_id. */ + + test_close(two); + } + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } +} + +TEST(sequence_numbers) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + test_sequence_numbers_one(); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + test_sequence_numbers_one(); +} + +static int intro(void) { + /* journal_file_open() requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return log_tests_skipped("/etc/machine-id not found"); + + arg_keep = saved_argc > 1; + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/libsystemd/sd-journal/test-journal-stream.c b/src/libsystemd/sd-journal/test-journal-stream.c new file mode 100644 index 0000000000..2897de4905 --- /dev/null +++ b/src/libsystemd/sd-journal/test-journal-stream.c @@ -0,0 +1,201 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-journal.h" + +#include "alloc-util.h" +#include "chattr-util.h" +#include "io-util.h" +#include "journal-file-util.h" +#include "journal-internal.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "rm-rf.h" +#include "tests.h" + +#define N_ENTRIES 200 + +static void verify_contents(sd_journal *j, unsigned skip) { + unsigned i; + + assert_se(j); + + i = 0; + SD_JOURNAL_FOREACH(j) { + const void *d; + char *k, *c; + size_t l; + unsigned u = 0; + + assert_se(sd_journal_get_cursor(j, &k) >= 0); + printf("cursor: %s\n", k); + free(k); + + assert_se(sd_journal_get_data(j, "MAGIC", &d, &l) >= 0); + printf("\t%.*s\n", (int) l, (const char*) d); + + assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0); + assert_se(k = strndup(d, l)); + printf("\t%s\n", k); + + if (skip > 0) { + assert_se(safe_atou(k + 7, &u) >= 0); + assert_se(i == u); + i += skip; + } + + free(k); + + assert_se(sd_journal_get_cursor(j, &c) >= 0); + assert_se(sd_journal_test_cursor(j, c) > 0); + free(c); + } + + if (skip > 0) + assert_se(i == N_ENTRIES); +} + +static void run_test(void) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + JournalFile *one, *two, *three; + char t[] = "/var/tmp/journal-stream-XXXXXX"; + unsigned i; + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + char *z; + const void *data; + size_t l; + dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; + + m = mmap_cache_new(); + assert_se(m != NULL); + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL); + + assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one) == 0); + assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two) == 0); + assert_se(journal_file_open(-1, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three) == 0); + + for (i = 0; i < N_ENTRIES; i++) { + char *p, *q; + dual_timestamp ts; + struct iovec iovec[2]; + + dual_timestamp_get(&ts); + + if (ts.monotonic <= previous_ts.monotonic) + ts.monotonic = previous_ts.monotonic + 1; + + if (ts.realtime <= previous_ts.realtime) + ts.realtime = previous_ts.realtime + 1; + + previous_ts = ts; + + assert_se(asprintf(&p, "NUMBER=%u", i) >= 0); + iovec[0] = IOVEC_MAKE(p, strlen(p)); + + assert_se(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo") >= 0); + + iovec[1] = IOVEC_MAKE(q, strlen(q)); + + if (i % 10 == 0) + assert_se(journal_file_append_entry(three, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + else { + if (i % 3 == 0) + assert_se(journal_file_append_entry(two, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + + assert_se(journal_file_append_entry(one, &ts, NULL, iovec, 2, NULL, NULL, NULL, NULL) == 0); + } + + free(p); + free(q); + } + + (void) journal_file_offline_close(one); + (void) journal_file_offline_close(two); + (void) journal_file_offline_close(three); + + assert_se(sd_journal_open_directory(&j, t, 0) >= 0); + + assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); + SD_JOURNAL_FOREACH_BACKWARDS(j) { + _cleanup_free_ char *c; + + assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); + printf("\t%.*s\n", (int) l, (const char*) data); + + assert_se(sd_journal_get_cursor(j, &c) >= 0); + assert_se(sd_journal_test_cursor(j, c) > 0); + } + + SD_JOURNAL_FOREACH(j) { + _cleanup_free_ char *c; + + assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0); + printf("\t%.*s\n", (int) l, (const char*) data); + + assert_se(sd_journal_get_cursor(j, &c) >= 0); + assert_se(sd_journal_test_cursor(j, c) > 0); + } + + sd_journal_flush_matches(j); + + verify_contents(j, 1); + + printf("NEXT TEST\n"); + assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); + + assert_se(z = journal_make_match_string(j)); + printf("resulting match expression is: %s\n", z); + free(z); + + verify_contents(j, 5); + + printf("NEXT TEST\n"); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "MAGIC=waldo", 0) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=10", 0) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=11", 0) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=12", 0) >= 0); + + assert_se(z = journal_make_match_string(j)); + printf("resulting match expression is: %s\n", z); + free(z); + + verify_contents(j, 0); + + assert_se(sd_journal_query_unique(j, "NUMBER") >= 0); + SD_JOURNAL_FOREACH_UNIQUE(j, data, l) + printf("%.*s\n", (int) l, (const char*) data); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); +} + +int main(int argc, char *argv[]) { + + /* journal_file_open() requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return log_tests_skipped("/etc/machine-id not found"); + + test_setup_logging(LOG_DEBUG); + + /* Run this test multiple times with different configurations of features. */ + + assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "0", 1) >= 0); + run_test(); + + assert_se(setenv("SYSTEMD_JOURNAL_KEYED_HASH", "1", 1) >= 0); + run_test(); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + run_test(); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + run_test(); + + return 0; +} diff --git a/src/libsystemd/sd-journal/test-journal-verify.c b/src/libsystemd/sd-journal/test-journal-verify.c new file mode 100644 index 0000000000..7853b36495 --- /dev/null +++ b/src/libsystemd/sd-journal/test-journal-verify.c @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "chattr-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "journal-file-util.h" +#include "journal-verify.h" +#include "log.h" +#include "mmap-cache.h" +#include "rm-rf.h" +#include "strv.h" +#include "terminal-util.h" +#include "tests.h" + +#define N_ENTRIES 6000 +#define RANDOM_RANGE 77 + +static void bit_toggle(const char *fn, uint64_t p) { + uint8_t b; + ssize_t r; + int fd; + + fd = open(fn, O_RDWR|O_CLOEXEC); + assert_se(fd >= 0); + + r = pread(fd, &b, 1, p/8); + assert_se(r == 1); + + b ^= 1 << (p % 8); + + r = pwrite(fd, &b, 1, p/8); + assert_se(r == 1); + + safe_close(fd); +} + +static int raw_verify(const char *fn, const char *verification_key) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + JournalFile *f; + int r; + + m = mmap_cache_new(); + assert_se(m != NULL); + + r = journal_file_open( + /* fd= */ -1, + fn, + O_RDONLY, + JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), + 0666, + /* compress_threshold_bytes= */ UINT64_MAX, + /* metrics= */ NULL, + m, + /* template= */ NULL, + &f); + if (r < 0) + return r; + + r = journal_file_verify(f, verification_key, NULL, NULL, NULL, false); + (void) journal_file_close(f); + + return r; +} + +static int run_test(const char *verification_key, ssize_t max_iterations) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + char t[] = "/var/tmp/journal-XXXXXX"; + struct stat st; + JournalFile *f; + JournalFile *df; + usec_t from = 0, to = 0, total = 0; + uint64_t start, end; + int r; + + m = mmap_cache_new(); + assert_se(m != NULL); + + /* journal_file_open() requires a valid machine id */ + if (sd_id128_get_machine(NULL) < 0) + return log_tests_skipped("No valid machine ID found"); + + test_setup_logging(LOG_DEBUG); + + assert_se(mkdtemp(t)); + assert_se(chdir(t) >= 0); + (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL); + + log_info("Generating a test journal"); + + assert_se(journal_file_open( + /* fd= */ -1, + "test.journal", + O_RDWR|O_CREAT, + JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), + 0666, + /* compress_threshold_bytes= */ UINT64_MAX, + /* metrics= */ NULL, + m, + /* template= */ NULL, + &df) == 0); + + for (size_t n = 0; n < N_ENTRIES; n++) { + _cleanup_free_ char *test = NULL; + struct iovec iovec; + struct dual_timestamp ts; + + dual_timestamp_get(&ts); + assert_se(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE)); + iovec = IOVEC_MAKE_STRING(test); + assert_se(journal_file_append_entry( + df, + &ts, + /* boot_id= */ NULL, + &iovec, + /* n_iovec= */ 1, + /* seqnum= */ NULL, + /* seqnum_id= */ NULL, + /* ret_object= */ NULL, + /* ret_offset= */ NULL) == 0); + } + + (void) journal_file_offline_close(df); + + log_info("Verifying with key: %s", strna(verification_key)); + + assert_se(journal_file_open( + /* fd= */ -1, + "test.journal", + O_RDONLY, + JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), + 0666, + /* compress_threshold_bytes= */ UINT64_MAX, + /* metrics= */ NULL, + m, + /* template= */ NULL, + &f) == 0); + journal_file_print_header(f); + journal_file_dump(f); + + assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0); + + if (verification_key && JOURNAL_HEADER_SEALED(f->header)) + log_info("=> Validated from %s to %s, %s missing", + FORMAT_TIMESTAMP(from), + FORMAT_TIMESTAMP(to), + FORMAT_TIMESPAN(total > to ? total - to : 0, 0)); + + (void) journal_file_close(f); + assert_se(stat("test.journal", &st) >= 0); + + start = 38448 * 8 + 0; + end = max_iterations < 0 ? (uint64_t)st.st_size * 8 : start + max_iterations; + log_info("Toggling bits %"PRIu64 " to %"PRIu64, start, end); + + for (uint64_t p = start; p < end; p++) { + bit_toggle("test.journal", p); + + if (max_iterations < 0) + log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8); + + r = raw_verify("test.journal", verification_key); + /* Suppress the notice when running in the limited (CI) mode */ + if (verification_key && max_iterations < 0 && r >= 0) + log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8); + + bit_toggle("test.journal", p); + } + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + return 0; +} + +int main(int argc, char *argv[]) { + const char *verification_key = NULL; + int max_iterations = 512; + + if (argc > 1) { + /* Don't limit the number of iterations when the verification key + * is provided on the command line, we want to do that only in CIs */ + verification_key = argv[1]; + max_iterations = -1; + } + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + run_test(verification_key, max_iterations); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + run_test(verification_key, max_iterations); + +#if HAVE_GCRYPT + /* If we're running without any arguments and we're compiled with gcrypt + * check the journal verification stuff with a valid key as well */ + if (argc <= 1) { + verification_key = "c262bd-85187f-0b1b04-877cc5/1c7af8-35a4e900"; + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + run_test(verification_key, max_iterations); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + run_test(verification_key, max_iterations); + } +#endif + + return 0; +} diff --git a/src/libsystemd/sd-journal/test-journal.c b/src/libsystemd/sd-journal/test-journal.c new file mode 100644 index 0000000000..a0916a0a24 --- /dev/null +++ b/src/libsystemd/sd-journal/test-journal.c @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "chattr-util.h" +#include "io-util.h" +#include "journal-authenticate.h" +#include "journal-file-util.h" +#include "journal-vacuum.h" +#include "log.h" +#include "rm-rf.h" +#include "tests.h" + +static bool arg_keep = false; + +static void mkdtemp_chdir_chattr(char *path) { + assert_se(mkdtemp(path)); + assert_se(chdir(path) >= 0); + + /* Speed up things a bit on btrfs, ensuring that CoW is turned off for all files created in our + * directory during the test run */ + (void) chattr_path(path, FS_NOCOW_FL, FS_NOCOW_FL, NULL); +} + +static void test_non_empty_one(void) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + dual_timestamp ts; + JournalFile *f; + struct iovec iovec; + static const char test[] = "TEST1=1", test2[] = "TEST2=2"; + Object *o, *d; + uint64_t p; + sd_id128_t fake_boot_id; + char t[] = "/var/tmp/journal-XXXXXX"; + + m = mmap_cache_new(); + assert_se(m != NULL); + + mkdtemp_chdir_chattr(t); + + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f) == 0); + + assert_se(dual_timestamp_get(&ts)); + assert_se(sd_id128_randomize(&fake_boot_id) == 0); + + iovec = IOVEC_MAKE_STRING(test); + assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + + iovec = IOVEC_MAKE_STRING(test2); + assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + + iovec = IOVEC_MAKE_STRING(test); + assert_se(journal_file_append_entry(f, &ts, &fake_boot_id, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + +#if HAVE_GCRYPT + journal_file_append_tag(f); +#endif + journal_file_dump(f); + + assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); + assert_se(le64toh(o->entry.seqnum) == 1); + + assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); + assert_se(le64toh(o->entry.seqnum) == 2); + + assert_se(journal_file_next_entry(f, 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_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 0); + + assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1); + assert_se(le64toh(o->entry.seqnum) == 1); + + assert_se(journal_file_find_data_object(f, test, strlen(test), &d, NULL) == 1); + assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 1); + + assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 3); + + assert_se(journal_file_find_data_object(f, test2, strlen(test2), &d, NULL) == 1); + assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_UP, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 2); + + assert_se(journal_file_move_to_entry_for_data(f, d, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 2); + + assert_se(journal_file_find_data_object(f, "quux", 4, &d, NULL) == 0); + + assert_se(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 1); + + assert_se(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 3); + + assert_se(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1); + assert_se(le64toh(o->entry.seqnum) == 2); + + assert_se(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0); + + journal_file_rotate(&f, m, JOURNAL_SEAL|JOURNAL_COMPRESS, UINT64_MAX, NULL); + journal_file_rotate(&f, m, JOURNAL_SEAL|JOURNAL_COMPRESS, UINT64_MAX, NULL); + + (void) journal_file_offline_close(f); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } + + puts("------------------------------------------------------------"); +} + +TEST(non_empty) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + test_non_empty_one(); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + test_non_empty_one(); +} + +static void test_empty_one(void) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + JournalFile *f1, *f2, *f3, *f4; + char t[] = "/var/tmp/journal-XXXXXX"; + + m = mmap_cache_new(); + assert_se(m != NULL); + + mkdtemp_chdir_chattr(t); + + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1) == 0); + assert_se(journal_file_open(-1, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2) == 0); + assert_se(journal_file_open(-1, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3) == 0); + assert_se(journal_file_open(-1, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4) == 0); + + journal_file_print_header(f1); + puts(""); + journal_file_print_header(f2); + puts(""); + journal_file_print_header(f3); + puts(""); + journal_file_print_header(f4); + puts(""); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } + + (void) journal_file_offline_close(f1); + (void) journal_file_offline_close(f2); + (void) journal_file_offline_close(f3); + (void) journal_file_offline_close(f4); +} + +TEST(empty) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + test_empty_one(); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + test_empty_one(); +} + +#if HAVE_COMPRESSION +static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + dual_timestamp ts; + JournalFile *f; + struct iovec iovec; + Object *o; + uint64_t p; + char t[] = "/var/tmp/journal-XXXXXX"; + char data[2048] = "FIELD="; + bool is_compressed; + int r; + + assert_se(data_size <= sizeof(data)); + + m = mmap_cache_new(); + assert_se(m != NULL); + + mkdtemp_chdir_chattr(t); + + assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f) == 0); + + dual_timestamp_get(&ts); + + iovec = IOVEC_MAKE(data, data_size); + assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL, NULL) == 0); + +#if HAVE_GCRYPT + journal_file_append_tag(f); +#endif + journal_file_dump(f); + + /* We have to partially reimplement some of the dump logic, because the normal next_entry does the + * decompression for us. */ + p = le64toh(f->header->header_size); + for (;;) { + r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); + assert_se(r == 0); + if (o->object.type == OBJECT_DATA) + break; + + assert_se(p < le64toh(f->header->tail_object_offset)); + p = p + ALIGN64(le64toh(o->object.size)); + } + + is_compressed = COMPRESSION_FROM_OBJECT(o) != COMPRESSION_NONE; + + (void) journal_file_offline_close(f); + + log_info("Done..."); + + if (arg_keep) + log_info("Not removing %s", t); + else { + journal_directory_vacuum(".", 3000000, 0, 0, NULL, true); + + assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + } + + puts("------------------------------------------------------------"); + + return is_compressed; +} + +static void test_min_compress_size_one(void) { + /* Note that XZ will actually fail to compress anything under 80 bytes, so you have to choose the limits + * carefully */ + + /* DEFAULT_MIN_COMPRESS_SIZE is 512 */ + assert_se(!check_compressed(UINT64_MAX, 255)); + assert_se(check_compressed(UINT64_MAX, 513)); + + /* compress everything */ + assert_se(check_compressed(0, 96)); + assert_se(check_compressed(8, 96)); + + /* Ensure we don't try to compress less than 8 bytes */ + assert_se(!check_compressed(0, 7)); + + /* check boundary conditions */ + assert_se(check_compressed(256, 256)); + assert_se(!check_compressed(256, 255)); +} + +TEST(min_compress_size) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); + test_min_compress_size_one(); + + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); + test_min_compress_size_one(); +} +#endif + +static int intro(void) { + arg_keep = saved_argc > 1; + + /* journal_file_open() requires a valid machine id */ + if (access("/etc/machine-id", F_OK) != 0) + return log_tests_skipped("/etc/machine-id not found"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); -- cgit v1.2.3