]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd/sd-journal/test-journal-append.c
journal-remote: drop dependencies to journald
[thirdparty/systemd.git] / src / libsystemd / sd-journal / test-journal-append.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <getopt.h>
5 #include <stdio.h>
6 #include <unistd.h>
7
8 #include "chattr-util.h"
9 #include "fd-util.h"
10 #include "fs-util.h"
11 #include "io-util.h"
12 #include "journal-file-util.h"
13 #include "log.h"
14 #include "mmap-cache.h"
15 #include "parse-util.h"
16 #include "random-util.h"
17 #include "rm-rf.h"
18 #include "strv.h"
19 #include "terminal-util.h"
20 #include "tests.h"
21 #include "tmpfile-util.h"
22
23 static int journal_append_message(JournalFile *mj, const char *message) {
24 struct iovec iovec;
25 struct dual_timestamp ts;
26
27 assert(mj);
28 assert(message);
29
30 dual_timestamp_get(&ts);
31 iovec = IOVEC_MAKE_STRING(message);
32 return journal_file_append_entry(
33 mj,
34 &ts,
35 /* boot_id= */ NULL,
36 &iovec,
37 /* n_iovec= */ 1,
38 /* seqnum= */ NULL,
39 /* seqnum_id= */ NULL,
40 /* ret_object= */ NULL,
41 /* ret_offset= */ NULL);
42 }
43
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;
48 uint64_t start, end;
49 int r;
50
51 mmap_cache = mmap_cache_new();
52 assert_se(mmap_cache);
53
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");
57
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);
61
62 log_debug("Opening journal %s/system.journal", tempdir);
63
64 r = journal_file_open(
65 /* fd= */ -1,
66 "system.journal",
67 O_RDWR|O_CREAT,
68 JOURNAL_COMPRESS,
69 0644,
70 /* compress_threshold_bytes= */ UINT64_MAX,
71 /* metrics= */ NULL,
72 mmap_cache,
73 /* template= */ NULL,
74 &mj);
75 if (r < 0)
76 return log_error_errno(r, "Failed to open the journal: %m");
77
78 assert_se(mj);
79
80 /* Add a couple of initial messages */
81 for (int i = 0; i < 10; i++) {
82 _cleanup_free_ char *message = NULL;
83
84 assert_se(asprintf(&message, "MESSAGE=Initial message %d", i) >= 0);
85 r = journal_append_message(mj, message);
86 if (r < 0)
87 return log_error_errno(r, "Failed to write to the journal: %m");
88 }
89
90 start = start_offset == UINT64_MAX ? random_u64() % mj->last_stat.st_size : start_offset;
91 end = (uint64_t) mj->last_stat.st_size;
92
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);
96 fflush(stdout);
97
98 if (start >= end)
99 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
100 "Start offset >= journal size, can't continue");
101
102 for (uint64_t offset = start; offset < end; offset += step) {
103 _cleanup_free_ char *message = NULL;
104 uint8_t b;
105
106 /* Flip a bit in the journal file */
107 r = pread(mj->fd, &b, 1, offset);
108 assert_se(r == 1);
109 b |= 0x1;
110 r = pwrite(mj->fd, &b, 1, offset);
111 assert_se(r == 1);
112
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(
117 /* fd= */ -1,
118 "system.journal",
119 O_RDWR|O_CREAT,
120 JOURNAL_COMPRESS,
121 0644,
122 /* compress_threshold_bytes= */ UINT64_MAX,
123 /* metrics= */ NULL,
124 mmap_cache,
125 /* template= */ NULL,
126 &mj);
127 if (r < 0) {
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");
132 break;
133 }
134
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);
138 if (r < 0) {
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");
142 break;
143 }
144 }
145
146 return 0;
147 }
148
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;
155 int c, r;
156
157 test_setup_logging(LOG_DEBUG);
158
159 enum {
160 ARG_START_OFFSET = 0x1000,
161 ARG_ITERATIONS,
162 ARG_ITERATION_STEP,
163 ARG_CORRUPT_STEP,
164 ARG_SEQUENTIAL,
165 ARG_RUN_ONE,
166 };
167
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 },
176 {}
177 };
178
179 assert_se(argc >= 0);
180 assert_se(argv);
181
182 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
183 switch (c) {
184
185 case 'h':
186 printf("Syntax:\n"
187 " %s [OPTION...]\n"
188 "Options:\n"
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"
193 " (default: 100)\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);
203 return 0;
204
205 case ARG_START_OFFSET:
206 r = safe_atou64(optarg, &start_offset);
207 if (r < 0)
208 return log_error_errno(r, "Invalid starting offset: %m");
209 break;
210
211 case ARG_ITERATIONS:
212 r = safe_atou64(optarg, &iterations);
213 if (r < 0)
214 return log_error_errno(r, "Invalid value for iterations: %m");
215 break;
216
217 case ARG_CORRUPT_STEP:
218 r = safe_atou64(optarg, &corrupt_step);
219 if (r < 0)
220 return log_error_errno(r, "Invalid value for corrupt-step: %m");
221 break;
222
223 case ARG_ITERATION_STEP:
224 r = safe_atou64(optarg, &iteration_step);
225 if (r < 0)
226 return log_error_errno(r, "Invalid value for iteration-step: %m");
227 break;
228
229 case ARG_SEQUENTIAL:
230 sequential = true;
231 break;
232
233 case ARG_RUN_ONE:
234 r = safe_atou64(optarg, &start_offset);
235 if (r < 0)
236 return log_error_errno(r, "Invalid offset: %m");
237
238 run_one = true;
239 break;
240
241 case '?':
242 return -EINVAL;
243
244 default:
245 assert_not_reached();
246 }
247
248 if (run_one)
249 /* Reproducer mode */
250 return journal_corrupt_and_append(start_offset, corrupt_step);
251
252 for (uint64_t i = 0; i < iterations; i++) {
253 uint64_t offset = UINT64_MAX;
254
255 log_info("Iteration #%" PRIu64 ", step: %" PRIu64, i, iteration_step);
256
257 if (sequential)
258 offset = (start_offset == UINT64_MAX ? 0 : start_offset) + i * iteration_step;
259
260 r = journal_corrupt_and_append(offset, corrupt_step);
261 if (r < 0)
262 return EXIT_FAILURE;
263 if (r > 0)
264 /* Reached the end of the journal file */
265 break;
266 }
267
268 return EXIT_SUCCESS;
269 }