summaryrefslogtreecommitdiffstats
path: root/src/libsystemd/sd-journal/test-journal-append.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsystemd/sd-journal/test-journal-append.c')
-rw-r--r--src/libsystemd/sd-journal/test-journal-append.c269
1 files changed, 269 insertions, 0 deletions
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 0000000..24b98c8
--- /dev/null
+++ b/src/libsystemd/sd-journal/test-journal-append.c
@@ -0,0 +1,269 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "chattr-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "iovec-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_now(&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 <OFFSET, ITER) if --start-offset=\n"
+ " is set (default: false)\n"
+ " --run-one=OFFSET Single shot mode for reproducing issues. Takes the same\n"
+ " offset as --start-offset= and does only one iteration\n"
+ , program_invocation_short_name);
+ return 0;
+
+ case ARG_START_OFFSET:
+ r = safe_atou64(optarg, &start_offset);
+ if (r < 0)
+ return log_error_errno(r, "Invalid starting offset: %m");
+ break;
+
+ case ARG_ITERATIONS:
+ r = safe_atou64(optarg, &iterations);
+ if (r < 0)
+ return log_error_errno(r, "Invalid value for iterations: %m");
+ break;
+
+ case ARG_CORRUPT_STEP:
+ r = safe_atou64(optarg, &corrupt_step);
+ if (r < 0)
+ return log_error_errno(r, "Invalid value for corrupt-step: %m");
+ break;
+
+ case ARG_ITERATION_STEP:
+ r = safe_atou64(optarg, &iteration_step);
+ if (r < 0)
+ return log_error_errno(r, "Invalid value for iteration-step: %m");
+ break;
+
+ case ARG_SEQUENTIAL:
+ sequential = true;
+ break;
+
+ case ARG_RUN_ONE:
+ r = safe_atou64(optarg, &start_offset);
+ if (r < 0)
+ return log_error_errno(r, "Invalid offset: %m");
+
+ run_one = true;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (run_one)
+ /* Reproducer mode */
+ return journal_corrupt_and_append(start_offset, corrupt_step);
+
+ for (uint64_t i = 0; i < iterations; i++) {
+ uint64_t offset = UINT64_MAX;
+
+ log_info("Iteration #%" PRIu64 ", step: %" PRIu64, i, iteration_step);
+
+ if (sequential)
+ offset = (start_offset == UINT64_MAX ? 0 : start_offset) + i * iteration_step;
+
+ r = journal_corrupt_and_append(offset, corrupt_step);
+ if (r < 0)
+ return EXIT_FAILURE;
+ if (r > 0)
+ /* Reached the end of the journal file */
+ break;
+ }
+
+ return EXIT_SUCCESS;
+}