]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/logs-show.c
c90400b81955c1cfe39d6e6211bccb0c9c44e788
[thirdparty/systemd.git] / src / shared / logs-show.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <signal.h>
6 #include <stdint.h>
7 #include <stdlib.h>
8 #include <sys/socket.h>
9 #include <syslog.h>
10 #include <unistd.h>
11
12 #include "sd-id128.h"
13 #include "sd-journal.h"
14
15 #include "alloc-util.h"
16 #include "fd-util.h"
17 #include "format-util.h"
18 #include "glyph-util.h"
19 #include "hashmap.h"
20 #include "hostname-util.h"
21 #include "id128-util.h"
22 #include "io-util.h"
23 #include "journal-internal.h"
24 #include "journal-util.h"
25 #include "json.h"
26 #include "locale-util.h"
27 #include "log.h"
28 #include "logs-show.h"
29 #include "macro.h"
30 #include "namespace-util.h"
31 #include "output-mode.h"
32 #include "parse-util.h"
33 #include "pretty-print.h"
34 #include "process-util.h"
35 #include "sparse-endian.h"
36 #include "stdio-util.h"
37 #include "string-table.h"
38 #include "string-util.h"
39 #include "strv.h"
40 #include "terminal-util.h"
41 #include "time-util.h"
42 #include "utf8.h"
43 #include "web-util.h"
44
45 /* up to three lines (each up to 100 characters) or 300 characters, whichever is less */
46 #define PRINT_LINE_THRESHOLD 3
47 #define PRINT_CHAR_THRESHOLD 300
48
49 #define JSON_THRESHOLD 4096U
50
51 static int print_catalog(FILE *f, sd_journal *j) {
52 _cleanup_free_ char *t = NULL, *z = NULL;
53 const char *newline, *prefix;
54 int r;
55
56 assert(j);
57
58 r = sd_journal_get_catalog(j, &t);
59 if (r == -ENOENT)
60 return 0;
61 if (r < 0)
62 return log_error_errno(r, "Failed to find catalog entry: %m");
63
64 if (is_locale_utf8())
65 prefix = strjoina(special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), special_glyph(SPECIAL_GLYPH_LIGHT_SHADE));
66 else
67 prefix = "--";
68
69 newline = strjoina(ansi_normal(), "\n", ansi_grey(), prefix, ansi_normal(), " ", ansi_green());
70
71 z = strreplace(strstrip(t), "\n", newline);
72 if (!z)
73 return log_oom();
74
75 fprintf(f, "%s%s %s%s", ansi_grey(), prefix, ansi_normal(), ansi_green());
76 fputs(z, f);
77 fprintf(f, "%s\n", ansi_normal());
78
79 return 1;
80 }
81
82 static int url_from_catalog(sd_journal *j, char **ret) {
83 _cleanup_free_ char *t = NULL, *url = NULL;
84 const char *weblink;
85 int r;
86
87 assert(j);
88 assert(ret);
89
90 r = sd_journal_get_catalog(j, &t);
91 if (r == -ENOENT)
92 goto notfound;
93 if (r < 0)
94 return log_error_errno(r, "Failed to find catalog entry: %m");
95
96 weblink = find_line_startswith(t, "Documentation:");
97 if (!weblink)
98 goto notfound;
99
100 /* Skip whitespace to value */
101 weblink += strspn(weblink, " \t");
102
103 /* Cut out till next whitespace/newline */
104 url = strdupcspn(weblink, WHITESPACE);
105 if (!url)
106 return log_oom();
107
108 if (!documentation_url_is_valid(url))
109 goto notfound;
110
111 *ret = TAKE_PTR(url);
112 return 1;
113
114 notfound:
115 *ret = NULL;
116 return 0;
117 }
118
119 static int parse_field(
120 const void *data,
121 size_t length,
122 const char *field,
123 size_t field_len,
124 char **target,
125 size_t *target_len) {
126
127 size_t nl;
128 char *buf;
129
130 assert(data);
131 assert(field);
132 assert(target);
133
134 if (length < field_len)
135 return 0;
136
137 if (memcmp(data, field, field_len))
138 return 0;
139
140 nl = length - field_len;
141
142 buf = newdup_suffix0(char, (const char*) data + field_len, nl);
143 if (!buf)
144 return log_oom();
145
146 free_and_replace(*target, buf);
147
148 if (target_len)
149 *target_len = nl;
150
151 return 1;
152 }
153
154 typedef struct ParseFieldVec {
155 const char *field;
156 size_t field_len;
157 char **target;
158 size_t *target_len;
159 } ParseFieldVec;
160
161 #define PARSE_FIELD_VEC_ENTRY(_field, _target, _target_len) { \
162 .field = _field, \
163 .field_len = strlen(_field), \
164 .target = _target, \
165 .target_len = _target_len \
166 }
167
168 static int parse_fieldv(
169 const void *data,
170 size_t length,
171 const ParseFieldVec *fields,
172 size_t n_fields) {
173
174 int r;
175
176 for (size_t i = 0; i < n_fields; i++) {
177 const ParseFieldVec *f = &fields[i];
178
179 r = parse_field(data, length, f->field, f->field_len, f->target, f->target_len);
180 if (r < 0)
181 return r;
182 if (r > 0)
183 break;
184 }
185
186 return 0;
187 }
188
189 static int field_set_test(const Set *fields, const char *name, size_t n) {
190 char *s;
191
192 if (!fields)
193 return 1;
194
195 s = strndupa_safe(name, n);
196 return set_contains(fields, s);
197 }
198
199 static bool shall_print(const char *p, size_t l, OutputFlags flags) {
200 assert(p);
201
202 if (flags & OUTPUT_SHOW_ALL)
203 return true;
204
205 if (l >= PRINT_CHAR_THRESHOLD)
206 return false;
207
208 if (!utf8_is_printable(p, l))
209 return false;
210
211 return true;
212 }
213
214 static bool print_multiline(
215 FILE *f,
216 unsigned prefix,
217 unsigned n_columns,
218 OutputFlags flags,
219 int priority,
220 bool audit,
221 const char* message,
222 size_t message_len,
223 size_t highlight[2]) {
224
225 const char *color_on = "", *color_off = "", *highlight_on = "";
226 const char *pos, *end;
227 bool ellipsized = false;
228 int line = 0;
229
230 if (flags & OUTPUT_COLOR) {
231 get_log_colors(priority, &color_on, &color_off, &highlight_on);
232
233 if (audit && strempty(color_on)) {
234 color_on = ANSI_BLUE;
235 color_off = ANSI_NORMAL;
236 }
237 }
238
239 /* A special case: make sure that we print a newline when
240 the message is empty. */
241 if (message_len == 0)
242 fputs("\n", f);
243
244 for (pos = message;
245 pos < message + message_len;
246 pos = end + 1, line++) {
247 bool tail_line;
248 int len, indent = (line > 0) * prefix;
249 for (end = pos; end < message + message_len && *end != '\n'; end++)
250 ;
251 len = end - pos;
252 assert(len >= 0);
253
254 /* We need to figure out when we are showing not-last line, *and*
255 * will skip subsequent lines. In that case, we will put the dots
256 * at the end of the line, instead of putting dots in the middle
257 * or not at all.
258 */
259 tail_line =
260 line + 1 == PRINT_LINE_THRESHOLD ||
261 end + 1 >= message + PRINT_CHAR_THRESHOLD;
262
263 if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) ||
264 (prefix + len + 1 < n_columns && !tail_line)) {
265 if (highlight &&
266 (size_t) (pos - message) <= highlight[0] &&
267 highlight[0] < (size_t) len) {
268
269 fprintf(f, "%*s%s%.*s",
270 indent, "",
271 color_on, (int) highlight[0], pos);
272 fprintf(f, "%s%.*s",
273 highlight_on,
274 (int) (MIN((size_t) len, highlight[1]) - highlight[0]),
275 pos + highlight[0]);
276 if ((size_t) len > highlight[1])
277 fprintf(f, "%s%.*s",
278 color_on,
279 (int) (len - highlight[1]),
280 pos + highlight[1]);
281 fprintf(f, "%s\n", color_off);
282
283 } else
284 fprintf(f, "%*s%s%.*s%s\n",
285 indent, "",
286 color_on, len, pos, color_off);
287 continue;
288 }
289
290 /* Beyond this point, ellipsization will happen. */
291 ellipsized = true;
292
293 if (prefix < n_columns && n_columns - prefix >= 3) {
294 if (n_columns - prefix > (unsigned) len + 3)
295 fprintf(f, "%*s%s%.*s...%s\n",
296 indent, "",
297 color_on, len, pos, color_off);
298 else {
299 _cleanup_free_ char *e = NULL;
300
301 e = ellipsize_mem(pos, len, n_columns - prefix,
302 tail_line ? 100 : 90);
303 if (!e)
304 fprintf(f, "%*s%s%.*s%s\n",
305 indent, "",
306 color_on, len, pos, color_off);
307 else
308 fprintf(f, "%*s%s%s%s\n",
309 indent, "",
310 color_on, e, color_off);
311 }
312 } else
313 fputs("...\n", f);
314
315 if (tail_line)
316 break;
317 }
318
319 return ellipsized;
320 }
321
322 static int output_timestamp_monotonic(
323 FILE *f,
324 OutputMode mode,
325 const dual_timestamp *display_ts,
326 const sd_id128_t *boot_id,
327 const dual_timestamp *previous_display_ts,
328 const sd_id128_t *previous_boot_id) {
329
330 int written_chars = 0;
331
332 assert(f);
333 assert(display_ts);
334 assert(boot_id);
335 assert(previous_display_ts);
336 assert(previous_boot_id);
337
338 if (!VALID_MONOTONIC(display_ts->monotonic))
339 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid monotonic timestamp available");
340
341 written_chars += fprintf(f, "[%5"PRI_USEC".%06"PRI_USEC, display_ts->monotonic / USEC_PER_SEC, display_ts->monotonic % USEC_PER_SEC);
342
343 if (mode == OUTPUT_SHORT_DELTA) {
344 uint64_t delta;
345 bool reliable_ts = true;
346
347 if (VALID_MONOTONIC(previous_display_ts->monotonic) && sd_id128_equal(*boot_id, *previous_boot_id))
348 delta = usec_sub_unsigned(display_ts->monotonic, previous_display_ts->monotonic);
349 else if (VALID_REALTIME(display_ts->realtime) && VALID_REALTIME(previous_display_ts->realtime)) {
350 delta = usec_sub_unsigned(display_ts->realtime, previous_display_ts->realtime);
351 reliable_ts = false;
352 } else {
353 written_chars += fprintf(f, "%16s", "");
354 goto finish;
355 }
356
357 written_chars += fprintf(f, " <%5"PRI_USEC".%06"PRI_USEC"%s>", delta / USEC_PER_SEC, delta % USEC_PER_SEC, reliable_ts ? " " : "*");
358 }
359
360 finish:
361 written_chars += fprintf(f, "%s", "]");
362 return written_chars;
363 }
364
365 static int output_timestamp_realtime(
366 FILE *f,
367 sd_journal *j,
368 OutputMode mode,
369 OutputFlags flags,
370 const dual_timestamp *display_ts) {
371
372 char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, 64U)];
373 int r;
374
375 assert(f);
376 assert(j);
377 assert(display_ts);
378
379 if (!VALID_REALTIME(display_ts->realtime))
380 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available");
381
382 if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) {
383 const char *k;
384
385 if (flags & OUTPUT_UTC)
386 k = format_timestamp_style(buf, sizeof(buf), display_ts->realtime, TIMESTAMP_UTC);
387 else
388 k = format_timestamp(buf, sizeof(buf), display_ts->realtime);
389 if (!k)
390 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
391 "Failed to format timestamp: %" PRIu64, display_ts->realtime);
392
393 } else {
394 struct tm tm;
395 time_t t;
396
397 t = (time_t) (display_ts->realtime / USEC_PER_SEC);
398
399 switch (mode) {
400
401 case OUTPUT_SHORT_UNIX:
402 xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, display_ts->realtime % USEC_PER_SEC);
403 break;
404
405 case OUTPUT_SHORT_ISO:
406 case OUTPUT_SHORT_ISO_PRECISE: {
407 size_t tail = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S",
408 localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC));
409 if (tail == 0)
410 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
411 "Failed to format ISO time");
412
413 /* No usec in strftime, need to append */
414 if (mode == OUTPUT_SHORT_ISO_PRECISE) {
415 assert(ELEMENTSOF(buf) - tail >= 7);
416 snprintf(buf + tail, ELEMENTSOF(buf) - tail, ".%06"PRI_USEC, display_ts->realtime % USEC_PER_SEC);
417 tail += 7;
418 }
419
420 int h = tm.tm_gmtoff / 60 / 60;
421 int m = labs((tm.tm_gmtoff / 60) % 60);
422 snprintf(buf + tail, ELEMENTSOF(buf) - tail, "%+03d:%02d", h, m);
423 break;
424 }
425
426 case OUTPUT_SHORT:
427 case OUTPUT_SHORT_PRECISE:
428
429 if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S",
430 localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0)
431 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
432 "Failed to format syslog time");
433
434 if (mode == OUTPUT_SHORT_PRECISE) {
435 size_t k;
436
437 assert(sizeof(buf) > strlen(buf));
438 k = sizeof(buf) - strlen(buf);
439
440 r = snprintf(buf + strlen(buf), k, ".%06"PRIu64, display_ts->realtime % USEC_PER_SEC);
441 if (r <= 0 || (size_t) r >= k) /* too long? */
442 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
443 "Failed to format precise time");
444 }
445 break;
446
447 default:
448 assert_not_reached();
449 }
450 }
451
452 fputs(buf, f);
453 return (int) strlen(buf);
454 }
455
456 static int output_short(
457 FILE *f,
458 sd_journal *j,
459 OutputMode mode,
460 unsigned n_columns,
461 OutputFlags flags,
462 const Set *output_fields,
463 const size_t highlight[2],
464 const dual_timestamp *display_ts,
465 const sd_id128_t *boot_id,
466 const dual_timestamp *previous_display_ts,
467 const sd_id128_t *previous_boot_id) {
468
469 int r;
470 const void *data;
471 size_t length, n = 0;
472 _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL,
473 *message = NULL, *priority = NULL, *transport = NULL,
474 *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL;
475 size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0,
476 priority_len = 0, transport_len = 0, config_file_len = 0,
477 unit_len = 0, user_unit_len = 0, documentation_url_len = 0;
478 int p = LOG_INFO;
479 bool ellipsized = false, audit;
480 const ParseFieldVec fields[] = {
481 PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len),
482 PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len),
483 PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len),
484 PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len),
485 PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len),
486 PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len),
487 PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len),
488 PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len),
489 PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len),
490 PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len),
491 PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len),
492 PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len),
493 };
494 size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0};
495
496 assert(f);
497 assert(j);
498 assert(display_ts);
499 assert(boot_id);
500 assert(previous_display_ts);
501 assert(previous_boot_id);
502
503 /* Set the threshold to one bigger than the actual print threshold, so that if the line is actually
504 * longer than what we're willing to print, ellipsization will occur. This way we won't output a
505 * misleading line without any indication of truncation.
506 */
507 (void) sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1);
508
509 JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
510 r = parse_fieldv(data, length, fields, ELEMENTSOF(fields));
511 if (r < 0)
512 return r;
513 }
514 if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
515 log_debug_errno(r, "Skipping message we can't read: %m");
516 return 0;
517 }
518 if (r < 0)
519 return log_error_errno(r, "Failed to get journal fields: %m");
520
521 if (!message) {
522 log_debug("Skipping message without MESSAGE= field.");
523 return 0;
524 }
525
526 if (!(flags & OUTPUT_SHOW_ALL))
527 strip_tab_ansi(&message, &message_len, highlight_shifted);
528
529 if (flags & OUTPUT_TRUNCATE_NEWLINE)
530 truncate_nl_full(message, &message_len);
531
532 if (priority_len == 1 && *priority >= '0' && *priority <= '7')
533 p = *priority - '0';
534
535 audit = streq_ptr(transport, "audit");
536
537 if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA))
538 r = output_timestamp_monotonic(f, mode, display_ts, boot_id, previous_display_ts, previous_boot_id);
539 else
540 r = output_timestamp_realtime(f, j, mode, flags, display_ts);
541 if (r < 0)
542 return r;
543 n += r;
544
545 if (flags & OUTPUT_NO_HOSTNAME) {
546 /* Suppress display of the hostname if this is requested. */
547 hostname = mfree(hostname);
548 hostname_len = 0;
549 }
550
551 if (hostname && shall_print(hostname, hostname_len, flags)) {
552 fprintf(f, " %.*s", (int) hostname_len, hostname);
553 n += hostname_len + 1;
554 }
555
556 if (mode == OUTPUT_WITH_UNIT && ((unit && shall_print(unit, unit_len, flags)) ||
557 (user_unit && shall_print(user_unit, user_unit_len, flags)))) {
558 if (unit) {
559 fprintf(f, " %.*s", (int) unit_len, unit);
560 n += unit_len + 1;
561 }
562 if (user_unit) {
563 if (unit)
564 fprintf(f, "/%.*s", (int) user_unit_len, user_unit);
565 else
566 fprintf(f, " %.*s", (int) user_unit_len, user_unit);
567 n += unit_len + 1;
568 }
569 } else if (identifier && shall_print(identifier, identifier_len, flags)) {
570 fprintf(f, " %.*s", (int) identifier_len, identifier);
571 n += identifier_len + 1;
572 } else if (comm && shall_print(comm, comm_len, flags)) {
573 fprintf(f, " %.*s", (int) comm_len, comm);
574 n += comm_len + 1;
575 } else
576 fputs(" unknown", f);
577
578 if (pid && shall_print(pid, pid_len, flags)) {
579 fprintf(f, "[%.*s]", (int) pid_len, pid);
580 n += pid_len + 2;
581 } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) {
582 fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid);
583 n += fake_pid_len + 2;
584 }
585
586 fputs(": ", f);
587
588 if (urlify_enabled()) {
589 _cleanup_free_ char *c = NULL;
590
591 /* Insert a hyperlink to a documentation URL before the message. Note that we don't make the
592 * whole message a hyperlink, since otherwise the whole screen might end up being just
593 * hyperlinks. Moreover, we want to be able to highlight parts of the message (such as the
594 * config file, see below) hence let's keep the documentation URL link separate. */
595
596 if (documentation_url && shall_print(documentation_url, documentation_url_len, flags)) {
597 c = strndup(documentation_url, documentation_url_len);
598 if (!c)
599 return log_oom();
600
601 if (!documentation_url_is_valid(c)) /* Eat up invalid links */
602 c = mfree(c);
603 }
604
605 if (!c)
606 (void) url_from_catalog(j, &c); /* Acquire from catalog if not embedded in log message itself */
607
608 if (c) {
609 _cleanup_free_ char *urlified = NULL;
610
611 if (terminal_urlify(c, special_glyph(SPECIAL_GLYPH_EXTERNAL_LINK), &urlified) >= 0) {
612 fputs(urlified, f);
613 fputc(' ', f);
614 }
615 }
616 }
617
618 if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len))
619 fprintf(f, "[%s blob data]\n", FORMAT_BYTES(message_len));
620 else {
621
622 /* URLify config_file string in message, if the message starts with it.
623 * Skip URLification if the highlighted pattern overlaps. */
624 if (config_file &&
625 message_len >= config_file_len &&
626 memcmp(message, config_file, config_file_len) == 0 &&
627 (message_len == config_file_len || IN_SET(message[config_file_len], ':', ' ')) &&
628 (!highlight || highlight_shifted[0] == 0 || highlight_shifted[0] > config_file_len)) {
629
630 _cleanup_free_ char *t = NULL, *urlified = NULL;
631
632 t = strndup(config_file, config_file_len);
633 if (t && terminal_urlify_path(t, NULL, &urlified) >= 0) {
634 size_t urlified_len = strlen(urlified);
635 size_t shift = urlified_len - config_file_len;
636 char *joined;
637
638 joined = realloc(urlified, message_len + shift);
639 if (joined) {
640 memcpy(joined + urlified_len, message + config_file_len, message_len - config_file_len);
641 free_and_replace(message, joined);
642 TAKE_PTR(urlified);
643 message_len += shift;
644 if (highlight) {
645 highlight_shifted[0] += shift;
646 highlight_shifted[1] += shift;
647 }
648 }
649 }
650 }
651
652 ellipsized |=
653 print_multiline(f, n + 2, n_columns, flags, p, audit,
654 message, message_len,
655 highlight_shifted);
656 }
657
658 if (flags & OUTPUT_CATALOG)
659 (void) print_catalog(f, j);
660
661 return ellipsized;
662 }
663
664 static int output_verbose(
665 FILE *f,
666 sd_journal *j,
667 OutputMode mode,
668 unsigned n_columns,
669 OutputFlags flags,
670 const Set *output_fields,
671 const size_t highlight[2],
672 const dual_timestamp *display_ts,
673 const sd_id128_t *boot_id,
674 const dual_timestamp *previous_display_ts,
675 const sd_id128_t *previous_boot_id) {
676
677 const void *data;
678 size_t length;
679 _cleanup_free_ char *cursor = NULL;
680 char buf[FORMAT_TIMESTAMP_MAX + 7];
681 const char *timestamp;
682 int r;
683
684 assert(f);
685 assert(j);
686 assert(display_ts);
687 assert(boot_id);
688 assert(previous_display_ts);
689 assert(previous_boot_id);
690
691 (void) sd_journal_set_data_threshold(j, 0);
692
693 if (!VALID_REALTIME(display_ts->realtime))
694 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available");
695
696 r = sd_journal_get_cursor(j, &cursor);
697 if (r < 0)
698 return log_error_errno(r, "Failed to get cursor: %m");
699
700 timestamp = format_timestamp_style(buf, sizeof buf, display_ts->realtime,
701 flags & OUTPUT_UTC ? TIMESTAMP_US_UTC : TIMESTAMP_US);
702 fprintf(f, "%s%s%s %s[%s]%s\n",
703 timestamp && (flags & OUTPUT_COLOR) ? ANSI_UNDERLINE : "",
704 timestamp ?: "(no timestamp)",
705 timestamp && (flags & OUTPUT_COLOR) ? ANSI_NORMAL : "",
706 (flags & OUTPUT_COLOR) ? ANSI_GREY : "",
707 cursor,
708 (flags & OUTPUT_COLOR) ? ANSI_NORMAL : "");
709
710 JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
711 _cleanup_free_ char *urlified = NULL;
712 const char *on = "", *off = "";
713 const char *c, *p = NULL;
714 size_t fieldlen, valuelen;
715
716 c = memchr(data, '=', length);
717 if (!c)
718 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field.");
719
720 fieldlen = c - (const char*) data;
721 if (!journal_field_valid(data, fieldlen, true))
722 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field.");
723
724 r = field_set_test(output_fields, data, fieldlen);
725 if (r < 0)
726 return r;
727 if (r == 0)
728 continue;
729
730 valuelen = length - 1 - fieldlen;
731 p = c + 1;
732
733 if (flags & OUTPUT_COLOR) {
734 if (startswith(data, "MESSAGE=")) {
735 on = ANSI_HIGHLIGHT;
736 off = ANSI_NORMAL;
737 } else if (startswith(data, "CONFIG_FILE=")) {
738 _cleanup_free_ char *u = NULL;
739
740 u = memdup_suffix0(p, valuelen);
741 if (!u)
742 return log_oom();
743
744 if (terminal_urlify_path(u, NULL, &urlified) >= 0) {
745 p = urlified;
746 valuelen = strlen(urlified);
747 }
748
749 } else if (startswith(data, "_")) {
750 /* Highlight trusted data as such */
751 on = ANSI_GREEN;
752 off = ANSI_NORMAL;
753 }
754 }
755
756 if ((flags & OUTPUT_SHOW_ALL) ||
757 (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH)
758 && utf8_is_printable(data, length))) {
759 fprintf(f, " %s%.*s=", on, (int) fieldlen, (const char*)data);
760 print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, false,
761 p, valuelen,
762 NULL);
763 fputs(off, f);
764 } else
765 fprintf(f, " %s%.*s=[%s blob data]%s\n",
766 on,
767 (int) (c - (const char*) data),
768 (const char*) data,
769 FORMAT_BYTES(length - (c - (const char *) data) - 1),
770 off);
771 }
772 if (r < 0)
773 return r;
774
775 if (flags & OUTPUT_CATALOG)
776 (void) print_catalog(f, j);
777
778 return 0;
779 }
780
781 static int output_export(
782 FILE *f,
783 sd_journal *j,
784 OutputMode mode,
785 unsigned n_columns,
786 OutputFlags flags,
787 const Set *output_fields,
788 const size_t highlight[2],
789 const dual_timestamp *display_ts,
790 const sd_id128_t *boot_id,
791 const dual_timestamp *previous_display_ts,
792 const sd_id128_t *previous_boot_id) {
793
794 sd_id128_t journal_boot_id, seqnum_id;
795 _cleanup_free_ char *cursor = NULL;
796 usec_t monotonic, realtime;
797 const void *data;
798 uint64_t seqnum;
799 size_t length;
800 int r;
801
802 assert(j);
803 assert(display_ts);
804 assert(boot_id);
805 assert(previous_display_ts);
806 assert(previous_boot_id);
807
808 (void) sd_journal_set_data_threshold(j, 0);
809
810 r = sd_journal_get_cursor(j, &cursor);
811 if (r < 0)
812 return log_error_errno(r, "Failed to get cursor: %m");
813
814 r = sd_journal_get_realtime_usec(j, &realtime);
815 if (r < 0)
816 return log_error_errno(r, "Failed to get realtime timestamp: %m");
817
818 r = sd_journal_get_monotonic_usec(j, &monotonic, &journal_boot_id);
819 if (r < 0)
820 return log_error_errno(r, "Failed to get monotonic timestamp: %m");
821
822 r = sd_journal_get_seqnum(j, &seqnum, &seqnum_id);
823 if (r < 0)
824 return log_error_errno(r, "Failed to get seqnum: %m");
825
826 fprintf(f,
827 "__CURSOR=%s\n"
828 "__REALTIME_TIMESTAMP=" USEC_FMT "\n"
829 "__MONOTONIC_TIMESTAMP=" USEC_FMT "\n"
830 "__SEQNUM=%" PRIu64 "\n"
831 "__SEQNUM_ID=%s\n"
832 "_BOOT_ID=%s\n",
833 cursor,
834 realtime,
835 monotonic,
836 seqnum,
837 SD_ID128_TO_STRING(seqnum_id),
838 SD_ID128_TO_STRING(journal_boot_id));
839
840 JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
841 size_t fieldlen;
842 const char *c;
843
844 /* We already printed the boot id from the data in the header, hence let's suppress it here */
845 if (memory_startswith(data, length, "_BOOT_ID="))
846 continue;
847
848 c = memchr(data, '=', length);
849 if (!c)
850 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field.");
851
852 fieldlen = c - (const char*) data;
853 if (!journal_field_valid(data, fieldlen, true))
854 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field.");
855
856 r = field_set_test(output_fields, data, fieldlen);
857 if (r < 0)
858 return r;
859 if (!r)
860 continue;
861
862 if (utf8_is_printable_newline(data, length, false))
863 fwrite(data, length, 1, f);
864 else {
865 uint64_t le64;
866
867 fwrite(data, fieldlen, 1, f);
868 fputc('\n', f);
869 le64 = htole64(length - fieldlen - 1);
870 fwrite(&le64, sizeof(le64), 1, f);
871 fwrite(c + 1, length - fieldlen - 1, 1, f);
872 }
873
874 fputc('\n', f);
875 }
876 if (IN_SET(r, -EADDRNOTAVAIL, -EBADMSG)) {
877 log_debug_errno(r, "Skipping message we can't read: %m");
878 return 0;
879 }
880
881 if (r < 0)
882 return r;
883
884 fputc('\n', f);
885
886 return 0;
887 }
888
889 void json_escape(
890 FILE *f,
891 const char* p,
892 size_t l,
893 OutputFlags flags) {
894
895 assert(f);
896 assert(p);
897
898 if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD)
899 fputs("null", f);
900
901 else if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(p, l)) {
902 bool not_first = false;
903
904 fputs("[ ", f);
905
906 while (l > 0) {
907 if (not_first)
908 fprintf(f, ", %u", (uint8_t) *p);
909 else {
910 not_first = true;
911 fprintf(f, "%u", (uint8_t) *p);
912 }
913
914 p++;
915 l--;
916 }
917
918 fputs(" ]", f);
919 } else {
920 fputc('"', f);
921
922 while (l > 0) {
923 if (IN_SET(*p, '"', '\\')) {
924 fputc('\\', f);
925 fputc(*p, f);
926 } else if (*p == '\n')
927 fputs("\\n", f);
928 else if ((uint8_t) *p < ' ')
929 fprintf(f, "\\u%04x", (uint8_t) *p);
930 else
931 fputc(*p, f);
932
933 p++;
934 l--;
935 }
936
937 fputc('"', f);
938 }
939 }
940
941 typedef struct JsonData {
942 JsonVariant* name;
943 JsonVariant* values;
944 } JsonData;
945
946 static JsonData* json_data_free(JsonData *d) {
947 if (!d)
948 return NULL;
949
950 json_variant_unref(d->name);
951 json_variant_unref(d->values);
952
953 return mfree(d);
954 }
955
956 DEFINE_TRIVIAL_CLEANUP_FUNC(JsonData*, json_data_free);
957
958 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(json_data_hash_ops_free,
959 char, string_hash_func, string_compare_func,
960 JsonData, json_data_free);
961
962 static int update_json_data(
963 Hashmap *h,
964 OutputFlags flags,
965 const char *name,
966 const void *value,
967 size_t size) {
968
969 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
970 JsonData *d;
971 int r;
972
973 assert(name);
974 assert(value);
975
976 if (size == SIZE_MAX)
977 size = strlen(value);
978
979 if (!(flags & OUTPUT_SHOW_ALL) && strlen(name) + 1 + size >= JSON_THRESHOLD)
980 r = json_variant_new_null(&v);
981 else if (utf8_is_printable(value, size))
982 r = json_variant_new_stringn(&v, value, size);
983 else
984 r = json_variant_new_array_bytes(&v, value, size);
985 if (r < 0)
986 return log_error_errno(r, "Failed to allocate JSON data: %m");
987
988 d = hashmap_get(h, name);
989 if (d) {
990 r = json_variant_append_array(&d->values, v);
991 if (r < 0)
992 return log_error_errno(r, "Failed to append JSON value into array: %m");
993 } else {
994 _cleanup_(json_data_freep) JsonData *e = NULL;
995
996 e = new0(JsonData, 1);
997 if (!e)
998 return log_oom();
999
1000 r = json_variant_new_string(&e->name, name);
1001 if (r < 0)
1002 return log_error_errno(r, "Failed to allocate JSON name variant: %m");
1003
1004 r = json_variant_append_array(&e->values, v);
1005 if (r < 0)
1006 return log_error_errno(r, "Failed to create JSON value array: %m");
1007
1008 r = hashmap_put(h, json_variant_string(e->name), e);
1009 if (r < 0)
1010 return log_error_errno(r, "Failed to insert JSON data into hashmap: %m");
1011
1012 TAKE_PTR(e);
1013 }
1014
1015 return 0;
1016 }
1017
1018 static int update_json_data_split(
1019 Hashmap *h,
1020 OutputFlags flags,
1021 const Set *output_fields,
1022 const void *data,
1023 size_t size) {
1024
1025 size_t fieldlen;
1026 const char *eq;
1027 char *name;
1028
1029 assert(h);
1030 assert(data || size == 0);
1031
1032 if (memory_startswith(data, size, "_BOOT_ID="))
1033 return 0;
1034
1035 eq = memchr(data, '=', MIN(size, JSON_THRESHOLD));
1036 if (!eq)
1037 return 0;
1038
1039 fieldlen = eq - (const char*) data;
1040 if (!journal_field_valid(data, fieldlen, true))
1041 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field.");
1042
1043 name = strndupa_safe(data, fieldlen);
1044 if (output_fields && !set_contains(output_fields, name))
1045 return 0;
1046
1047 return update_json_data(h, flags, name, eq + 1, size - fieldlen - 1);
1048 }
1049
1050 static int output_json(
1051 FILE *f,
1052 sd_journal *j,
1053 OutputMode mode,
1054 unsigned n_columns,
1055 OutputFlags flags,
1056 const Set *output_fields,
1057 const size_t highlight[2],
1058 const dual_timestamp *display_ts,
1059 const sd_id128_t *boot_id,
1060 const dual_timestamp *previous_display_ts,
1061 const sd_id128_t *previous_boot_id) {
1062
1063 char usecbuf[CONST_MAX(DECIMAL_STR_MAX(usec_t), DECIMAL_STR_MAX(uint64_t))];
1064 _cleanup_(json_variant_unrefp) JsonVariant *object = NULL;
1065 _cleanup_hashmap_free_ Hashmap *h = NULL;
1066 sd_id128_t journal_boot_id, seqnum_id;
1067 _cleanup_free_ char *cursor = NULL;
1068 usec_t realtime, monotonic;
1069 JsonVariant **array = NULL;
1070 JsonData *d;
1071 uint64_t seqnum;
1072 size_t n = 0;
1073 int r;
1074
1075 assert(j);
1076 assert(display_ts);
1077 assert(boot_id);
1078 assert(previous_display_ts);
1079 assert(previous_boot_id);
1080
1081 (void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD);
1082
1083 r = sd_journal_get_cursor(j, &cursor);
1084 if (r < 0)
1085 return log_error_errno(r, "Failed to get cursor: %m");
1086
1087 r = sd_journal_get_realtime_usec(j, &realtime);
1088 if (r < 0)
1089 return log_error_errno(r, "Failed to get realtime timestamp: %m");
1090
1091 r = sd_journal_get_monotonic_usec(j, &monotonic, &journal_boot_id);
1092 if (r < 0)
1093 return log_error_errno(r, "Failed to get monotonic timestamp: %m");
1094
1095 r = sd_journal_get_seqnum(j, &seqnum, &seqnum_id);
1096 if (r < 0)
1097 return log_error_errno(r, "Failed to get seqnum: %m");
1098
1099 h = hashmap_new(&json_data_hash_ops_free);
1100 if (!h)
1101 return log_oom();
1102
1103 r = update_json_data(h, flags, "__CURSOR", cursor, SIZE_MAX);
1104 if (r < 0)
1105 return r;
1106
1107 xsprintf(usecbuf, USEC_FMT, realtime);
1108 r = update_json_data(h, flags, "__REALTIME_TIMESTAMP", usecbuf, SIZE_MAX);
1109 if (r < 0)
1110 return r;
1111
1112 xsprintf(usecbuf, USEC_FMT, monotonic);
1113 r = update_json_data(h, flags, "__MONOTONIC_TIMESTAMP", usecbuf, SIZE_MAX);
1114 if (r < 0)
1115 return r;
1116
1117 r = update_json_data(h, flags, "_BOOT_ID", SD_ID128_TO_STRING(journal_boot_id), SIZE_MAX);
1118 if (r < 0)
1119 return r;
1120
1121 xsprintf(usecbuf, USEC_FMT, seqnum);
1122 r = update_json_data(h, flags, "__SEQNUM", usecbuf, SIZE_MAX);
1123 if (r < 0)
1124 return r;
1125
1126 r = update_json_data(h, flags, "__SEQNUM_ID", SD_ID128_TO_STRING(seqnum_id), SIZE_MAX);
1127 if (r < 0)
1128 return r;
1129
1130 for (;;) {
1131 const void *data;
1132 size_t size;
1133
1134 r = sd_journal_enumerate_data(j, &data, &size);
1135 if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
1136 log_debug_errno(r, "Skipping message we can't read: %m");
1137 return 0;
1138 }
1139 if (r < 0)
1140 return log_error_errno(r, "Failed to read journal: %m");
1141 if (r == 0)
1142 break;
1143
1144 r = update_json_data_split(h, flags, output_fields, data, size);
1145 if (r < 0)
1146 return r;
1147 }
1148
1149 array = new(JsonVariant*, hashmap_size(h)*2);
1150 if (!array)
1151 return log_oom();
1152
1153 CLEANUP_ARRAY(array, n, json_variant_unref_many);
1154
1155 HASHMAP_FOREACH(d, h) {
1156 assert(json_variant_elements(d->values) > 0);
1157
1158 array[n++] = json_variant_ref(d->name);
1159
1160 if (json_variant_elements(d->values) == 1)
1161 array[n++] = json_variant_ref(json_variant_by_index(d->values, 0));
1162 else
1163 array[n++] = json_variant_ref(d->values);
1164 }
1165
1166 r = json_variant_new_object(&object, array, n);
1167 if (r < 0)
1168 return log_error_errno(r, "Failed to allocate JSON object: %m");
1169
1170 return json_variant_dump(object,
1171 output_mode_to_json_format_flags(mode) |
1172 (FLAGS_SET(flags, OUTPUT_COLOR) ? JSON_FORMAT_COLOR : 0),
1173 f, NULL);
1174 }
1175
1176 static int output_cat_field(
1177 FILE *f,
1178 sd_journal *j,
1179 OutputFlags flags,
1180 int prio,
1181 const char *field,
1182 const size_t highlight[2]) {
1183
1184 const char *color_on = "", *color_off = "", *highlight_on = "";
1185 const void *data;
1186 size_t l, fl;
1187 int r;
1188
1189 if (FLAGS_SET(flags, OUTPUT_COLOR))
1190 get_log_colors(prio, &color_on, &color_off, &highlight_on);
1191
1192 r = sd_journal_get_data(j, field, &data, &l);
1193 if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
1194 log_debug_errno(r, "Skipping message we can't read: %m");
1195 return 0;
1196 }
1197 if (r == -ENOENT) /* An entry without the requested field */
1198 return 0;
1199 if (r < 0)
1200 return log_error_errno(r, "Failed to get data: %m");
1201
1202 fl = strlen(field);
1203 assert(l >= fl + 1);
1204 assert(((char*) data)[fl] == '=');
1205
1206 data = (const uint8_t*) data + fl + 1;
1207 l -= fl + 1;
1208
1209 if (FLAGS_SET(flags, OUTPUT_COLOR)) {
1210 if (highlight) {
1211 assert(highlight[0] <= highlight[1]);
1212 assert(highlight[1] <= l);
1213
1214 fputs(color_on, f);
1215 fwrite((const char*) data, 1, highlight[0], f);
1216 fputs(highlight_on, f);
1217 fwrite((const char*) data + highlight[0], 1, highlight[1] - highlight[0], f);
1218 fputs(color_on, f);
1219 fwrite((const char*) data + highlight[1], 1, l - highlight[1], f);
1220 fputs(color_off, f);
1221 } else {
1222 fputs(color_on, f);
1223 fwrite((const char*) data, 1, l, f);
1224 fputs(color_off, f);
1225 }
1226 } else
1227 fwrite((const char*) data, 1, l, f);
1228
1229 fputc('\n', f);
1230 return 0;
1231 }
1232
1233 static int output_cat(
1234 FILE *f,
1235 sd_journal *j,
1236 OutputMode mode,
1237 unsigned n_columns,
1238 OutputFlags flags,
1239 const Set *output_fields,
1240 const size_t highlight[2],
1241 const dual_timestamp *display_ts,
1242 const sd_id128_t *boot_id,
1243 const dual_timestamp *previous_display_ts,
1244 const sd_id128_t *previous_boot_id) {
1245
1246 int r, prio = LOG_INFO;
1247 const char *field;
1248
1249 assert(j);
1250 assert(f);
1251 assert(display_ts);
1252 assert(boot_id);
1253 assert(previous_display_ts);
1254 assert(previous_boot_id);
1255
1256 (void) sd_journal_set_data_threshold(j, 0);
1257
1258 if (FLAGS_SET(flags, OUTPUT_COLOR)) {
1259 const void *data;
1260 size_t l;
1261
1262 /* Determine priority of this entry, so that we can color it nicely */
1263
1264 r = sd_journal_get_data(j, "PRIORITY", &data, &l);
1265 if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
1266 log_debug_errno(r, "Skipping message we can't read: %m");
1267 return 0;
1268 }
1269 if (r < 0) {
1270 if (r != -ENOENT)
1271 return log_error_errno(r, "Failed to get data: %m");
1272
1273 /* An entry without PRIORITY */
1274 } else if (l == 10 && memcmp(data, "PRIORITY=", 9) == 0) {
1275 char c = ((char*) data)[9];
1276
1277 if (c >= '0' && c <= '7')
1278 prio = c - '0';
1279 }
1280 }
1281
1282 if (set_isempty(output_fields))
1283 return output_cat_field(f, j, flags, prio, "MESSAGE", highlight);
1284
1285 SET_FOREACH(field, output_fields) {
1286 r = output_cat_field(f, j, flags, prio, field, streq(field, "MESSAGE") ? highlight : NULL);
1287 if (r < 0)
1288 return r;
1289 }
1290
1291 return 0;
1292 }
1293
1294 static int get_display_timestamp(
1295 sd_journal *j,
1296 dual_timestamp *ret_display_ts,
1297 sd_id128_t *ret_boot_id) {
1298
1299 const void *data;
1300 _cleanup_free_ char *realtime = NULL, *monotonic = NULL;
1301 size_t length = 0, realtime_len = 0, monotonic_len = 0;
1302 const ParseFieldVec message_fields[] = {
1303 PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len),
1304 PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len),
1305 };
1306 int r;
1307 bool realtime_good = false, monotonic_good = false, boot_id_good = false;
1308
1309 assert(j);
1310 assert(ret_display_ts);
1311 assert(ret_boot_id);
1312
1313 JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
1314 r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields));
1315 if (r < 0)
1316 return r;
1317
1318 if (realtime && monotonic)
1319 break;
1320 }
1321 if (r < 0)
1322 return r;
1323
1324 if (realtime)
1325 realtime_good = safe_atou64(realtime, &ret_display_ts->realtime) >= 0;
1326 if (!realtime_good || !VALID_REALTIME(ret_display_ts->realtime))
1327 realtime_good = sd_journal_get_realtime_usec(j, &ret_display_ts->realtime) >= 0;
1328 if (!realtime_good)
1329 ret_display_ts->realtime = USEC_INFINITY;
1330
1331 if (monotonic)
1332 monotonic_good = safe_atou64(monotonic, &ret_display_ts->monotonic) >= 0;
1333 if (!monotonic_good || !VALID_MONOTONIC(ret_display_ts->monotonic))
1334 monotonic_good = boot_id_good = sd_journal_get_monotonic_usec(j, &ret_display_ts->monotonic, ret_boot_id) >= 0;
1335 if (!monotonic_good)
1336 ret_display_ts->monotonic = USEC_INFINITY;
1337
1338 if (!boot_id_good)
1339 boot_id_good = sd_journal_get_monotonic_usec(j, NULL, ret_boot_id) >= 0;
1340 if (!boot_id_good)
1341 *ret_boot_id = SD_ID128_NULL;
1342
1343 /* Restart all data before */
1344 sd_journal_restart_data(j);
1345 sd_journal_restart_unique(j);
1346 sd_journal_restart_fields(j);
1347
1348 return 0;
1349 }
1350
1351 typedef int (*output_func_t)(
1352 FILE *f,
1353 sd_journal *j,
1354 OutputMode mode,
1355 unsigned n_columns,
1356 OutputFlags flags,
1357 const Set *output_fields,
1358 const size_t highlight[2],
1359 const dual_timestamp *display_ts,
1360 const sd_id128_t *boot_id,
1361 const dual_timestamp *previous_display_ts,
1362 const sd_id128_t *previous_boot_id);
1363
1364
1365 static output_func_t output_funcs[_OUTPUT_MODE_MAX] = {
1366 [OUTPUT_SHORT] = output_short,
1367 [OUTPUT_SHORT_ISO] = output_short,
1368 [OUTPUT_SHORT_ISO_PRECISE] = output_short,
1369 [OUTPUT_SHORT_PRECISE] = output_short,
1370 [OUTPUT_SHORT_MONOTONIC] = output_short,
1371 [OUTPUT_SHORT_DELTA] = output_short,
1372 [OUTPUT_SHORT_UNIX] = output_short,
1373 [OUTPUT_SHORT_FULL] = output_short,
1374 [OUTPUT_VERBOSE] = output_verbose,
1375 [OUTPUT_EXPORT] = output_export,
1376 [OUTPUT_JSON] = output_json,
1377 [OUTPUT_JSON_PRETTY] = output_json,
1378 [OUTPUT_JSON_SSE] = output_json,
1379 [OUTPUT_JSON_SEQ] = output_json,
1380 [OUTPUT_CAT] = output_cat,
1381 [OUTPUT_WITH_UNIT] = output_short,
1382 };
1383
1384 int show_journal_entry(
1385 FILE *f,
1386 sd_journal *j,
1387 OutputMode mode,
1388 unsigned n_columns,
1389 OutputFlags flags,
1390 Set *output_fields,
1391 const size_t highlight[2],
1392 bool *ellipsized,
1393 dual_timestamp *previous_display_ts,
1394 sd_id128_t *previous_boot_id) {
1395
1396 dual_timestamp display_ts = DUAL_TIMESTAMP_NULL;
1397 sd_id128_t boot_id = SD_ID128_NULL;
1398 int r;
1399
1400 assert(mode >= 0);
1401 assert(mode < _OUTPUT_MODE_MAX);
1402 assert(previous_display_ts);
1403 assert(previous_boot_id);
1404
1405 if (n_columns <= 0)
1406 n_columns = columns();
1407
1408 r = get_display_timestamp(j, &display_ts, &boot_id);
1409 if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
1410 log_debug_errno(r, "Skipping message we can't read: %m");
1411 return 0;
1412 }
1413 if (r < 0)
1414 return log_error_errno(r, "Failed to get journal fields: %m");
1415
1416 r = output_funcs[mode](
1417 f,
1418 j,
1419 mode,
1420 n_columns,
1421 flags,
1422 output_fields,
1423 highlight,
1424 &display_ts,
1425 &boot_id,
1426 previous_display_ts,
1427 previous_boot_id);
1428
1429 /* Store timestamp and boot ID for next iteration */
1430 *previous_display_ts = display_ts;
1431 *previous_boot_id = boot_id;
1432
1433 if (ellipsized && r > 0)
1434 *ellipsized = true;
1435
1436 return r;
1437 }
1438
1439 static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) {
1440 assert(f);
1441 assert(flags);
1442
1443 if (!(*flags & OUTPUT_BEGIN_NEWLINE))
1444 return 0;
1445
1446 /* Print a beginning new line if that's request, but only once
1447 * on the first line we print. */
1448
1449 fputc('\n', f);
1450 *flags &= ~OUTPUT_BEGIN_NEWLINE;
1451 return 0;
1452 }
1453
1454 int show_journal(
1455 FILE *f,
1456 sd_journal *j,
1457 OutputMode mode,
1458 unsigned n_columns,
1459 usec_t not_before,
1460 unsigned how_many,
1461 OutputFlags flags,
1462 bool *ellipsized) {
1463
1464 int r;
1465 unsigned line = 0;
1466 bool need_seek = false;
1467 int warn_cutoff = flags & OUTPUT_WARN_CUTOFF;
1468 dual_timestamp previous_display_ts = DUAL_TIMESTAMP_NULL;
1469 sd_id128_t previous_boot_id = SD_ID128_NULL;
1470
1471 assert(j);
1472 assert(mode >= 0);
1473 assert(mode < _OUTPUT_MODE_MAX);
1474
1475 if (how_many == UINT_MAX)
1476 need_seek = true;
1477 else {
1478 /* Seek to end */
1479 r = sd_journal_seek_tail(j);
1480 if (r < 0)
1481 return log_error_errno(r, "Failed to seek to tail: %m");
1482
1483 r = sd_journal_previous_skip(j, how_many);
1484 if (r < 0)
1485 return log_error_errno(r, "Failed to skip previous: %m");
1486 }
1487
1488 for (;;) {
1489 usec_t usec;
1490
1491 if (need_seek) {
1492 r = sd_journal_next(j);
1493 if (r < 0)
1494 return log_error_errno(r, "Failed to iterate through journal: %m");
1495 }
1496
1497 if (r == 0)
1498 break;
1499
1500 need_seek = true;
1501
1502 if (not_before > 0) {
1503 r = sd_journal_get_monotonic_usec(j, &usec, NULL);
1504
1505 /* -ESTALE is returned if the timestamp is not from this boot */
1506 if (r == -ESTALE)
1507 continue;
1508 if (r < 0)
1509 return log_error_errno(r, "Failed to get journal time: %m");
1510
1511 if (usec < not_before)
1512 continue;
1513 }
1514
1515 line++;
1516 maybe_print_begin_newline(f, &flags);
1517
1518 r = show_journal_entry(
1519 f,
1520 j,
1521 mode,
1522 n_columns,
1523 flags,
1524 /* output_fields= */ NULL,
1525 /* highlight= */ NULL,
1526 ellipsized,
1527 &previous_display_ts,
1528 &previous_boot_id);
1529 if (r < 0)
1530 return r;
1531 }
1532
1533 if (warn_cutoff && line < how_many && not_before > 0) {
1534 sd_id128_t boot_id;
1535 usec_t cutoff = 0;
1536
1537 /* Check whether the cutoff line is too early */
1538
1539 r = sd_id128_get_boot(&boot_id);
1540 if (r < 0)
1541 return log_error_errno(r, "Failed to get boot id: %m");
1542
1543 r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL);
1544 if (r < 0)
1545 return log_error_errno(r, "Failed to get journal cutoff time: %m");
1546
1547 if (r > 0 && not_before < cutoff) {
1548 maybe_print_begin_newline(f, &flags);
1549
1550 /* If we logged *something* and no permission error happened, than we can reliably
1551 * emit the warning about rotation. If we didn't log anything and access errors
1552 * happened, emit hint about permissions. Otherwise, give a generic message, since we
1553 * can't diagnose the issue. */
1554
1555 bool noaccess = journal_access_blocked(j);
1556
1557 if (line == 0 && noaccess)
1558 fprintf(f, "Warning: some journal files were not opened due to insufficient permissions.\n");
1559 else if (!noaccess)
1560 fprintf(f, "Notice: journal has been rotated since unit was started, output may be incomplete.\n");
1561 else
1562 fprintf(f, "Warning: journal has been rotated since unit was started and some journal "
1563 "files were not opened due to insufficient permissions, output may be incomplete.\n");
1564 }
1565
1566 warn_cutoff = false;
1567 }
1568
1569 return 0;
1570 }
1571
1572 int add_matches_for_unit(sd_journal *j, const char *unit) {
1573 const char *m1, *m2, *m3, *m4;
1574 int r;
1575
1576 assert(j);
1577 assert(unit);
1578
1579 m1 = strjoina("_SYSTEMD_UNIT=", unit);
1580 m2 = strjoina("COREDUMP_UNIT=", unit);
1581 m3 = strjoina("UNIT=", unit);
1582 m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit);
1583
1584 (void)(
1585 /* Look for messages from the service itself */
1586 (r = sd_journal_add_match(j, m1, 0)) ||
1587
1588 /* Look for coredumps of the service */
1589 (r = sd_journal_add_disjunction(j)) ||
1590 (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) ||
1591 (r = sd_journal_add_match(j, "_UID=0", 0)) ||
1592 (r = sd_journal_add_match(j, m2, 0)) ||
1593
1594 /* Look for messages from PID 1 about this service */
1595 (r = sd_journal_add_disjunction(j)) ||
1596 (r = sd_journal_add_match(j, "_PID=1", 0)) ||
1597 (r = sd_journal_add_match(j, m3, 0)) ||
1598
1599 /* Look for messages from authorized daemons about this service */
1600 (r = sd_journal_add_disjunction(j)) ||
1601 (r = sd_journal_add_match(j, "_UID=0", 0)) ||
1602 (r = sd_journal_add_match(j, m4, 0))
1603 );
1604
1605 if (r == 0 && endswith(unit, ".slice")) {
1606 const char *m5;
1607
1608 m5 = strjoina("_SYSTEMD_SLICE=", unit);
1609
1610 /* Show all messages belonging to a slice */
1611 (void)(
1612 (r = sd_journal_add_disjunction(j)) ||
1613 (r = sd_journal_add_match(j, m5, 0))
1614 );
1615 }
1616
1617 return r;
1618 }
1619
1620 int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) {
1621 int r;
1622 char *m1, *m2, *m3, *m4;
1623 char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)];
1624
1625 assert(j);
1626 assert(unit);
1627
1628 m1 = strjoina("_SYSTEMD_USER_UNIT=", unit);
1629 m2 = strjoina("USER_UNIT=", unit);
1630 m3 = strjoina("COREDUMP_USER_UNIT=", unit);
1631 m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit);
1632 sprintf(muid, "_UID="UID_FMT, uid);
1633
1634 (void) (
1635 /* Look for messages from the user service itself */
1636 (r = sd_journal_add_match(j, m1, 0)) ||
1637 (r = sd_journal_add_match(j, muid, 0)) ||
1638
1639 /* Look for messages from systemd about this service */
1640 (r = sd_journal_add_disjunction(j)) ||
1641 (r = sd_journal_add_match(j, m2, 0)) ||
1642 (r = sd_journal_add_match(j, muid, 0)) ||
1643
1644 /* Look for coredumps of the service */
1645 (r = sd_journal_add_disjunction(j)) ||
1646 (r = sd_journal_add_match(j, m3, 0)) ||
1647 (r = sd_journal_add_match(j, muid, 0)) ||
1648 (r = sd_journal_add_match(j, "_UID=0", 0)) ||
1649
1650 /* Look for messages from authorized daemons about this service */
1651 (r = sd_journal_add_disjunction(j)) ||
1652 (r = sd_journal_add_match(j, m4, 0)) ||
1653 (r = sd_journal_add_match(j, muid, 0)) ||
1654 (r = sd_journal_add_match(j, "_UID=0", 0))
1655 );
1656
1657 if (r == 0 && endswith(unit, ".slice")) {
1658 const char *m5;
1659
1660 m5 = strjoina("_SYSTEMD_USER_SLICE=", unit);
1661
1662 /* Show all messages belonging to a slice */
1663 (void)(
1664 (r = sd_journal_add_disjunction(j)) ||
1665 (r = sd_journal_add_match(j, m5, 0)) ||
1666 (r = sd_journal_add_match(j, muid, 0))
1667 );
1668 }
1669
1670 return r;
1671 }
1672
1673 static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) {
1674 _cleanup_close_pair_ int pair[2] = PIPE_EBADF;
1675 _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, rootfd = -EBADF;
1676 char buf[SD_ID128_UUID_STRING_MAX];
1677 pid_t pid, child;
1678 ssize_t k;
1679 int r;
1680
1681 assert(machine);
1682 assert(boot_id);
1683
1684 r = container_get_leader(machine, &pid);
1685 if (r < 0)
1686 return r;
1687
1688 r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd);
1689 if (r < 0)
1690 return r;
1691
1692 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
1693 return -errno;
1694
1695 r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG,
1696 pidnsfd, mntnsfd, -1, -1, rootfd, &child);
1697 if (r < 0)
1698 return r;
1699 if (r == 0) {
1700 int fd;
1701
1702 pair[0] = safe_close(pair[0]);
1703
1704 fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
1705 if (fd < 0)
1706 _exit(EXIT_FAILURE);
1707
1708 r = loop_read_exact(fd, buf, 36, false);
1709 safe_close(fd);
1710 if (r < 0)
1711 _exit(EXIT_FAILURE);
1712
1713 k = send(pair[1], buf, 36, MSG_NOSIGNAL);
1714 if (k != 36)
1715 _exit(EXIT_FAILURE);
1716
1717 _exit(EXIT_SUCCESS);
1718 }
1719
1720 pair[1] = safe_close(pair[1]);
1721
1722 r = wait_for_terminate_and_check("(sd-bootidns)", child, 0);
1723 if (r < 0)
1724 return r;
1725 if (r != EXIT_SUCCESS)
1726 return -EIO;
1727
1728 k = recv(pair[0], buf, 36, 0);
1729 if (k != 36)
1730 return -EIO;
1731
1732 buf[36] = 0;
1733 r = sd_id128_from_string(buf, boot_id);
1734 if (r < 0)
1735 return r;
1736
1737 return 0;
1738 }
1739
1740 int add_match_boot_id(sd_journal *j, sd_id128_t id) {
1741 char match[STRLEN("_BOOT_ID=") + SD_ID128_STRING_MAX];
1742
1743 assert(j);
1744 assert(!sd_id128_is_null(id));
1745
1746 sd_id128_to_string(id, stpcpy(match, "_BOOT_ID="));
1747 return sd_journal_add_match(j, match, strlen(match));
1748 }
1749
1750 int add_match_this_boot(sd_journal *j, const char *machine) {
1751 sd_id128_t boot_id;
1752 int r;
1753
1754 assert(j);
1755
1756 if (machine) {
1757 r = get_boot_id_for_machine(machine, &boot_id);
1758 if (r < 0)
1759 return log_error_errno(r, "Failed to get boot id of container %s: %m", machine);
1760 } else {
1761 r = sd_id128_get_boot(&boot_id);
1762 if (r < 0)
1763 return log_error_errno(r, "Failed to get boot id: %m");
1764 }
1765
1766 r = add_match_boot_id(j, boot_id);
1767 if (r < 0)
1768 return log_error_errno(r, "Failed to add match: %m");
1769
1770 r = sd_journal_add_conjunction(j);
1771 if (r < 0)
1772 return log_error_errno(r, "Failed to add conjunction: %m");
1773
1774 return 0;
1775 }
1776
1777 int show_journal_by_unit(
1778 FILE *f,
1779 const char *unit,
1780 const char *log_namespace,
1781 OutputMode mode,
1782 unsigned n_columns,
1783 usec_t not_before,
1784 unsigned how_many,
1785 uid_t uid,
1786 OutputFlags flags,
1787 int journal_open_flags,
1788 bool system_unit,
1789 bool *ellipsized) {
1790
1791 _cleanup_(sd_journal_closep) sd_journal *j = NULL;
1792 int r;
1793
1794 assert(mode >= 0);
1795 assert(mode < _OUTPUT_MODE_MAX);
1796 assert(unit);
1797
1798 if (how_many <= 0)
1799 return 0;
1800
1801 r = sd_journal_open_namespace(&j, log_namespace, journal_open_flags | SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE);
1802 if (r < 0)
1803 return log_error_errno(r, "Failed to open journal: %m");
1804
1805 if (system_unit)
1806 r = add_matches_for_unit(j, unit);
1807 else
1808 r = add_matches_for_user_unit(j, unit, uid);
1809 if (r < 0)
1810 return log_error_errno(r, "Failed to add unit matches: %m");
1811
1812 r = sd_journal_add_conjunction(j);
1813 if (r < 0)
1814 return log_error_errno(r, "Failed to add conjunction: %m");
1815
1816 r = add_match_this_boot(j, NULL);
1817 if (r < 0)
1818 return r;
1819
1820 if (DEBUG_LOGGING) {
1821 _cleanup_free_ char *filter = NULL;
1822
1823 filter = journal_make_match_string(j);
1824 if (!filter)
1825 return log_oom();
1826
1827 log_debug("Journal filter: %s", filter);
1828 }
1829
1830 return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized);
1831 }
1832
1833 static int discover_next_boot(
1834 sd_journal *j,
1835 sd_id128_t previous_boot_id,
1836 bool advance_older,
1837 BootId *ret) {
1838
1839 _cleanup_set_free_ Set *broken_ids = NULL;
1840 int r;
1841
1842 assert(j);
1843 assert(ret);
1844
1845 /* We expect the journal to be on the last position of a boot
1846 * (in relation to the direction we are going), so that the next
1847 * invocation of sd_journal_next/previous will be from a different
1848 * boot. We then collect any information we desire and then jump
1849 * to the last location of the new boot by using a _BOOT_ID match
1850 * coming from the other journal direction. */
1851
1852 /* Make sure we aren't restricted by any _BOOT_ID matches, so that
1853 * we can actually advance to a *different* boot. */
1854 sd_journal_flush_matches(j);
1855
1856 for (;;) {
1857 sd_id128_t *id_dup;
1858 BootId boot;
1859
1860 r = sd_journal_step_one(j, !advance_older);
1861 if (r < 0)
1862 return r;
1863 if (r == 0) {
1864 *ret = (BootId) {};
1865 return 0; /* End of journal, yay. */
1866 }
1867
1868 r = sd_journal_get_monotonic_usec(j, NULL, &boot.id);
1869 if (r < 0)
1870 return r;
1871
1872 /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that
1873 * normally, this will only require a single iteration, as we moved to the last entry of the previous
1874 * boot entry already. However, it might happen that the per-journal-field entry arrays are less
1875 * complete than the main entry array, and hence might reference an entry that's not actually the last
1876 * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to
1877 * speed things up, but let's not trust that it is complete, and hence, manually advance as
1878 * necessary. */
1879
1880 if (!sd_id128_is_null(previous_boot_id) && sd_id128_equal(boot.id, previous_boot_id))
1881 continue;
1882
1883 if (set_contains(broken_ids, &boot.id))
1884 continue;
1885
1886 /* Yay, we found a new boot ID from the entry object. Let's check there exist corresponding
1887 * entries matching with the _BOOT_ID= data. */
1888
1889 r = add_match_boot_id(j, boot.id);
1890 if (r < 0)
1891 return r;
1892
1893 /* First, seek to the first (or the last when we are going upwards) occurrence of this boot ID.
1894 * You may think this is redundant. Yes, that's redundant unless the journal is corrupted.
1895 * But when the journal is corrupted, especially, badly 'truncated', then the below may fail.
1896 * See https://github.com/systemd/systemd/pull/29334#issuecomment-1736567951. */
1897 if (advance_older)
1898 r = sd_journal_seek_tail(j);
1899 else
1900 r = sd_journal_seek_head(j);
1901 if (r < 0)
1902 return r;
1903
1904 r = sd_journal_step_one(j, 0);
1905 if (r < 0)
1906 return r;
1907 if (r == 0) {
1908 log_debug("Whoopsie! We found a boot ID %s but can't read its first entry. "
1909 "The journal seems to be corrupted. Ignoring the boot ID.",
1910 SD_ID128_TO_STRING(boot.id));
1911 goto try_again;
1912 }
1913
1914 r = sd_journal_get_realtime_usec(j, &boot.first_usec);
1915 if (r < 0)
1916 return r;
1917
1918 /* Next, seek to the last occurrence of this boot ID. */
1919 if (advance_older)
1920 r = sd_journal_seek_head(j);
1921 else
1922 r = sd_journal_seek_tail(j);
1923 if (r < 0)
1924 return r;
1925
1926 r = sd_journal_step_one(j, 0);
1927 if (r < 0)
1928 return r;
1929 if (r == 0) {
1930 log_debug("Whoopsie! We found a boot ID %s but can't read its last entry. "
1931 "The journal seems to be corrupted. Ignoring the boot ID.",
1932 SD_ID128_TO_STRING(boot.id));
1933 goto try_again;
1934 }
1935
1936 r = sd_journal_get_realtime_usec(j, &boot.last_usec);
1937 if (r < 0)
1938 return r;
1939
1940 sd_journal_flush_matches(j);
1941 *ret = boot;
1942 return 1;
1943
1944 try_again:
1945 /* Save the bad boot ID. */
1946 id_dup = newdup(sd_id128_t, &boot.id, 1);
1947 if (!id_dup)
1948 return -ENOMEM;
1949
1950 r = set_ensure_consume(&broken_ids, &id128_hash_ops_free, id_dup);
1951 if (r < 0)
1952 return r;
1953
1954 /* Move to the previous position again. */
1955 sd_journal_flush_matches(j);
1956
1957 if (!sd_id128_is_null(previous_boot_id)) {
1958 r = add_match_boot_id(j, previous_boot_id);
1959 if (r < 0)
1960 return r;
1961 }
1962
1963 if (advance_older)
1964 r = sd_journal_seek_head(j);
1965 else
1966 r = sd_journal_seek_tail(j);
1967 if (r < 0)
1968 return r;
1969
1970 r = sd_journal_step_one(j, 0);
1971 if (r < 0)
1972 return r;
1973 if (r == 0)
1974 return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
1975 "Whoopsie! Cannot seek to the last entry of boot %s.",
1976 SD_ID128_TO_STRING(previous_boot_id));
1977
1978 sd_journal_flush_matches(j);
1979 }
1980 }
1981
1982 int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id) {
1983 int r;
1984
1985 assert(j);
1986 assert(!sd_id128_is_null(boot_id));
1987
1988 sd_journal_flush_matches(j);
1989
1990 r = add_match_boot_id(j, boot_id);
1991 if (r < 0)
1992 return r;
1993
1994 r = sd_journal_seek_head(j); /* seek to oldest */
1995 if (r < 0)
1996 return r;
1997
1998 r = sd_journal_next(j); /* read the oldest entry */
1999 if (r < 0)
2000 return r;
2001
2002 /* At this point the read pointer is positioned at the oldest occurrence of the reference boot ID.
2003 * After flushing the matches, one more invocation of _previous() will hence place us at the
2004 * following entry, which must then have an older boot ID */
2005
2006 sd_journal_flush_matches(j);
2007 return r > 0;
2008 }
2009
2010 int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) {
2011 bool advance_older;
2012 int r;
2013
2014 assert(j);
2015 assert(ret);
2016
2017 /* Adjust for the asymmetry that offset 0 is the last (and current) boot, while 1 is considered the
2018 * (chronological) first boot in the journal. */
2019 advance_older = offset <= 0;
2020
2021 if (advance_older)
2022 r = sd_journal_seek_tail(j); /* seek to newest */
2023 else
2024 r = sd_journal_seek_head(j); /* seek to oldest */
2025 if (r < 0)
2026 return r;
2027
2028 /* No sd_journal_next()/_previous() here.
2029 *
2030 * At this point the read pointer is positioned after the newest/before the oldest entry in the whole
2031 * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest
2032 * entry we have. */
2033
2034 sd_id128_t boot_id = SD_ID128_NULL;
2035 for (int off = !advance_older; ; off += advance_older ? -1 : 1) {
2036 BootId boot;
2037
2038 r = discover_next_boot(j, boot_id, advance_older, &boot);
2039 if (r < 0)
2040 return r;
2041 if (r == 0) {
2042 *ret = SD_ID128_NULL;
2043 return false;
2044 }
2045
2046 boot_id = boot.id;
2047 log_debug("Found boot ID %s by offset %i", SD_ID128_TO_STRING(boot_id), off);
2048
2049 if (off == offset)
2050 break;
2051 }
2052
2053 *ret = boot_id;
2054 return true;
2055 }
2056
2057 int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) {
2058 _cleanup_free_ BootId *boots = NULL;
2059 size_t n_boots = 0;
2060 int r;
2061
2062 assert(j);
2063 assert(ret_boots);
2064 assert(ret_n_boots);
2065
2066 r = sd_journal_seek_head(j); /* seek to oldest */
2067 if (r < 0)
2068 return r;
2069
2070 /* No sd_journal_next()/_previous() here.
2071 *
2072 * At this point the read pointer is positioned before the oldest entry in the whole journal. The
2073 * next invocation of _next() will hence position us at the oldest entry we have. */
2074
2075 sd_id128_t previous_boot_id = SD_ID128_NULL;
2076 for (;;) {
2077 BootId boot;
2078
2079 r = discover_next_boot(j, previous_boot_id, /* advance_older = */ false, &boot);
2080 if (r < 0)
2081 return r;
2082 if (r == 0)
2083 break;
2084
2085 previous_boot_id = boot.id;
2086
2087 FOREACH_ARRAY(i, boots, n_boots)
2088 if (sd_id128_equal(i->id, boot.id))
2089 /* The boot id is already stored, something wrong with the journal files.
2090 * Exiting as otherwise this problem would cause an infinite loop. */
2091 break;
2092
2093 if (!GREEDY_REALLOC(boots, n_boots + 1))
2094 return -ENOMEM;
2095
2096 boots[n_boots++] = boot;
2097 }
2098
2099 *ret_boots = TAKE_PTR(boots);
2100 *ret_n_boots = n_boots;
2101 return n_boots > 0;
2102 }