1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "chattr-util.h"
12 #include "journal-file-util.h"
14 #include "mmap-cache.h"
15 #include "parse-util.h"
16 #include "random-util.h"
19 #include "terminal-util.h"
21 #include "tmpfile-util.h"
23 static int journal_append_message(JournalFile
*mj
, const char *message
) {
25 struct dual_timestamp ts
;
30 dual_timestamp_get(&ts
);
31 iovec
= IOVEC_MAKE_STRING(message
);
32 return journal_file_append_entry(
39 /* seqnum_id= */ NULL
,
40 /* ret_object= */ NULL
,
41 /* ret_offset= */ NULL
);
44 static int journal_corrupt_and_append(uint64_t start_offset
, uint64_t step
) {
45 _cleanup_(mmap_cache_unrefp
) MMapCache
*mmap_cache
= NULL
;
46 _cleanup_(rm_rf_physical_and_freep
) char *tempdir
= NULL
;
47 _cleanup_(journal_file_offline_closep
) JournalFile
*mj
= NULL
;
51 mmap_cache
= mmap_cache_new();
52 assert_se(mmap_cache
);
54 /* journal_file_open() requires a valid machine id */
55 if (sd_id128_get_machine(NULL
) < 0)
56 return log_tests_skipped("No valid machine ID found");
58 assert_se(mkdtemp_malloc("/tmp/journal-append-XXXXXX", &tempdir
) >= 0);
59 assert_se(chdir(tempdir
) >= 0);
60 (void) chattr_path(tempdir
, FS_NOCOW_FL
, FS_NOCOW_FL
, NULL
);
62 log_debug("Opening journal %s/system.journal", tempdir
);
64 r
= journal_file_open(
70 /* compress_threshold_bytes= */ UINT64_MAX
,
76 return log_error_errno(r
, "Failed to open the journal: %m");
80 /* Add a couple of initial messages */
81 for (int i
= 0; i
< 10; i
++) {
82 _cleanup_free_
char *message
= NULL
;
84 assert_se(asprintf(&message
, "MESSAGE=Initial message %d", i
) >= 0);
85 r
= journal_append_message(mj
, message
);
87 return log_error_errno(r
, "Failed to write to the journal: %m");
90 start
= start_offset
== UINT64_MAX
? random_u64() % mj
->last_stat
.st_size
: start_offset
;
91 end
= (uint64_t) mj
->last_stat
.st_size
;
93 /* Print the initial offset at which we start flipping bits, which can be
94 * later used to reproduce a potential fail */
95 log_info("Start offset: %" PRIu64
", corrupt-step: %" PRIu64
, start
, step
);
99 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
100 "Start offset >= journal size, can't continue");
102 for (uint64_t offset
= start
; offset
< end
; offset
+= step
) {
103 _cleanup_free_
char *message
= NULL
;
106 /* Flip a bit in the journal file */
107 r
= pread(mj
->fd
, &b
, 1, offset
);
110 r
= pwrite(mj
->fd
, &b
, 1, offset
);
113 /* Close and reopen the journal to flush all caches and remap
114 * the corrupted journal */
115 mj
= journal_file_offline_close(mj
);
116 r
= journal_file_open(
122 /* compress_threshold_bytes= */ UINT64_MAX
,
125 /* template= */ NULL
,
128 /* The corrupted journal might get rejected during reopening
129 * if it's corrupted enough (especially its header), so
130 * treat this as a success if it doesn't crash */
131 log_info_errno(r
, "Failed to reopen the journal: %m");
135 /* Try to write something to the (possibly corrupted) journal */
136 assert_se(asprintf(&message
, "MESSAGE=Hello world %" PRIu64
, offset
) >= 0);
137 r
= journal_append_message(mj
, message
);
139 /* We care only about crashes or sanitizer errors,
140 * failed write without any crash is a success */
141 log_info_errno(r
, "Failed to write to the journal: %m");
149 int main(int argc
, char *argv
[]) {
150 uint64_t start_offset
= UINT64_MAX
;
151 uint64_t iterations
= 100;
152 uint64_t iteration_step
= 1;
153 uint64_t corrupt_step
= 31;
154 bool sequential
= false, run_one
= false;
157 test_setup_logging(LOG_DEBUG
);
160 ARG_START_OFFSET
= 0x1000,
168 static const struct option options
[] = {
169 { "help", no_argument
, NULL
, 'h' },
170 { "start-offset", required_argument
, NULL
, ARG_START_OFFSET
},
171 { "iterations", required_argument
, NULL
, ARG_ITERATIONS
},
172 { "iteration-step", required_argument
, NULL
, ARG_ITERATION_STEP
},
173 { "corrupt-step", required_argument
, NULL
, ARG_CORRUPT_STEP
},
174 { "sequential", no_argument
, NULL
, ARG_SEQUENTIAL
},
175 { "run-one", required_argument
, NULL
, ARG_RUN_ONE
},
179 assert_se(argc
>= 0);
182 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
189 " --start-offset=OFFSET Offset at which to start corrupting the journal\n"
190 " (default: random offset is picked, unless\n"
191 " --sequential is used - in that case we use 0 + iteration)\n"
192 " --iterations=ITER Number of iterations to perform before exiting\n"
194 " --iteration-step=STEP Iteration step (default: 1)\n"
195 " --corrupt-step=STEP Corrupt every n-th byte starting from OFFSET (default: 31)\n"
196 " --sequential Go through offsets sequentially instead of picking\n"
197 " a random one on each iteration. If set, we go through\n"
198 " offsets <0; ITER), or <OFFSET, ITER) if --start-offset=\n"
199 " is set (default: false)\n"
200 " --run-one=OFFSET Single shot mode for reproducing issues. Takes the same\n"
201 " offset as --start-offset= and does only one iteration\n"
202 , program_invocation_short_name
);
205 case ARG_START_OFFSET
:
206 r
= safe_atou64(optarg
, &start_offset
);
208 return log_error_errno(r
, "Invalid starting offset: %m");
212 r
= safe_atou64(optarg
, &iterations
);
214 return log_error_errno(r
, "Invalid value for iterations: %m");
217 case ARG_CORRUPT_STEP
:
218 r
= safe_atou64(optarg
, &corrupt_step
);
220 return log_error_errno(r
, "Invalid value for corrupt-step: %m");
223 case ARG_ITERATION_STEP
:
224 r
= safe_atou64(optarg
, &iteration_step
);
226 return log_error_errno(r
, "Invalid value for iteration-step: %m");
234 r
= safe_atou64(optarg
, &start_offset
);
236 return log_error_errno(r
, "Invalid offset: %m");
245 assert_not_reached();
249 /* Reproducer mode */
250 return journal_corrupt_and_append(start_offset
, corrupt_step
);
252 for (uint64_t i
= 0; i
< iterations
; i
++) {
253 uint64_t offset
= UINT64_MAX
;
255 log_info("Iteration #%" PRIu64
", step: %" PRIu64
, i
, iteration_step
);
258 offset
= (start_offset
== UINT64_MAX
? 0 : start_offset
) + i
* iteration_step
;
260 r
= journal_corrupt_and_append(offset
, corrupt_step
);
264 /* Reached the end of the journal file */