]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journalctl.c
1dee74a93558d52d738bb81bc1ba543560a9187e
[thirdparty/systemd.git] / src / journal / journalctl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <stddef.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <sys/poll.h>
30 #include <time.h>
31 #include <getopt.h>
32 #include <sys/stat.h>
33 #include <sys/ioctl.h>
34 #include <linux/fs.h>
35
36 #include <systemd/sd-journal.h>
37
38 #include "log.h"
39 #include "util.h"
40 #include "path-util.h"
41 #include "build.h"
42 #include "pager.h"
43 #include "logs-show.h"
44 #include "strv.h"
45 #include "journal-internal.h"
46 #include "journal-def.h"
47 #include "journal-verify.h"
48 #include "journal-authenticate.h"
49 #include "journal-qrcode.h"
50 #include "fsprg.h"
51
52 #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
53
54 static OutputMode arg_output = OUTPUT_SHORT;
55 static bool arg_follow = false;
56 static bool arg_show_all = false;
57 static bool arg_no_pager = false;
58 static int arg_lines = -1;
59 static bool arg_no_tail = false;
60 static bool arg_quiet = false;
61 static bool arg_local = false;
62 static bool arg_this_boot = false;
63 static const char *arg_directory = NULL;
64 static int arg_priorities = 0xFF;
65 static const char *arg_verify_key = NULL;
66 #ifdef HAVE_GCRYPT
67 static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
68 #endif
69
70 static enum {
71 ACTION_SHOW,
72 ACTION_NEW_ID128,
73 ACTION_PRINT_HEADER,
74 ACTION_SETUP_KEYS,
75 ACTION_VERIFY
76 } arg_action = ACTION_SHOW;
77
78 static int help(void) {
79
80 printf("%s [OPTIONS...] [MATCH]\n\n"
81 "Send control commands to or query the journal.\n\n"
82 " -h --help Show this help\n"
83 " --version Show package version\n"
84 " --no-pager Do not pipe output into a pager\n"
85 " -a --all Show all fields, including long and unprintable\n"
86 " -f --follow Follow journal\n"
87 " -n --lines=INTEGER Journal entries to show\n"
88 " --no-tail Show all lines, even in follow mode\n"
89 " -o --output=STRING Change journal output mode (short, short-monotonic,\n"
90 " verbose, export, json, cat)\n"
91 " -q --quiet Don't show privilege warning\n"
92 " -l --local Only local entries\n"
93 " -b --this-boot Show data only from current boot\n"
94 " -D --directory=PATH Show journal files from directory\n"
95 " -p --priority=RANGE Show only messages within the specified priority range\n\n"
96 "Commands:\n"
97 " --new-id128 Generate a new 128 Bit ID\n"
98 " --header Show journal header information\n"
99 #ifdef HAVE_GCRYPT
100 " --setup-keys Generate new FSS key pair\n"
101 " --interval=TIME Time interval for changing the FSS sealing key\n"
102 " --verify Verify journal file consistency\n"
103 " --verify-key=KEY Specify FSS verification key\n"
104 #endif
105 , program_invocation_short_name);
106
107 return 0;
108 }
109
110 static int parse_argv(int argc, char *argv[]) {
111
112 enum {
113 ARG_VERSION = 0x100,
114 ARG_NO_PAGER,
115 ARG_NO_TAIL,
116 ARG_NEW_ID128,
117 ARG_HEADER,
118 ARG_SETUP_KEYS,
119 ARG_INTERVAL,
120 ARG_VERIFY,
121 ARG_VERIFY_KEY
122 };
123
124 static const struct option options[] = {
125 { "help", no_argument, NULL, 'h' },
126 { "version" , no_argument, NULL, ARG_VERSION },
127 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
128 { "follow", no_argument, NULL, 'f' },
129 { "output", required_argument, NULL, 'o' },
130 { "all", no_argument, NULL, 'a' },
131 { "lines", required_argument, NULL, 'n' },
132 { "no-tail", no_argument, NULL, ARG_NO_TAIL },
133 { "new-id128", no_argument, NULL, ARG_NEW_ID128 },
134 { "quiet", no_argument, NULL, 'q' },
135 { "local", no_argument, NULL, 'l' },
136 { "this-boot", no_argument, NULL, 'b' },
137 { "directory", required_argument, NULL, 'D' },
138 { "header", no_argument, NULL, ARG_HEADER },
139 { "priority", no_argument, NULL, 'p' },
140 { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS },
141 { "interval", required_argument, NULL, ARG_INTERVAL },
142 { "verify", no_argument, NULL, ARG_VERIFY },
143 { "verify-key", required_argument, NULL, ARG_VERIFY_KEY },
144 { NULL, 0, NULL, 0 }
145 };
146
147 int c, r;
148
149 assert(argc >= 0);
150 assert(argv);
151
152 while ((c = getopt_long(argc, argv, "hfo:an:qlbD:p:", options, NULL)) >= 0) {
153
154 switch (c) {
155
156 case 'h':
157 help();
158 return 0;
159
160 case ARG_VERSION:
161 puts(PACKAGE_STRING);
162 puts(DISTRIBUTION);
163 puts(SYSTEMD_FEATURES);
164 return 0;
165
166 case ARG_NO_PAGER:
167 arg_no_pager = true;
168 break;
169
170 case 'f':
171 arg_follow = true;
172 break;
173
174 case 'o':
175 arg_output = output_mode_from_string(optarg);
176 if (arg_output < 0) {
177 log_error("Unknown output '%s'.", optarg);
178 return -EINVAL;
179 }
180
181 break;
182
183 case 'a':
184 arg_show_all = true;
185 break;
186
187 case 'n':
188 r = safe_atoi(optarg, &arg_lines);
189 if (r < 0 || arg_lines < 0) {
190 log_error("Failed to parse lines '%s'", optarg);
191 return -EINVAL;
192 }
193 break;
194
195 case ARG_NO_TAIL:
196 arg_no_tail = true;
197 break;
198
199 case ARG_NEW_ID128:
200 arg_action = ACTION_NEW_ID128;
201 break;
202
203 case 'q':
204 arg_quiet = true;
205 break;
206
207 case 'l':
208 arg_local = true;
209 break;
210
211 case 'b':
212 arg_this_boot = true;
213 break;
214
215 case 'D':
216 arg_directory = optarg;
217 break;
218
219 case ARG_HEADER:
220 arg_action = ACTION_PRINT_HEADER;
221 break;
222
223 case ARG_VERIFY:
224 arg_action = ACTION_VERIFY;
225 break;
226
227 #ifdef HAVE_GCRYPT
228 case ARG_SETUP_KEYS:
229 arg_action = ACTION_SETUP_KEYS;
230 break;
231
232
233 case ARG_VERIFY_KEY:
234 arg_action = ACTION_VERIFY;
235 arg_verify_key = optarg;
236 break;
237
238 case ARG_INTERVAL:
239 r = parse_usec(optarg, &arg_interval);
240 if (r < 0 || arg_interval <= 0) {
241 log_error("Failed to parse sealing key change interval: %s", optarg);
242 return -EINVAL;
243 }
244 break;
245 #else
246 case ARG_SETUP_KEYS:
247 case ARG_VERIFY_KEY:
248 case ARG_INTERVAL:
249 log_error("Forward-secure sealing not available.");
250 return -ENOTSUP;
251 #endif
252
253 case 'p': {
254 const char *dots;
255
256 dots = strstr(optarg, "..");
257 if (dots) {
258 char *a;
259 int from, to, i;
260
261 /* a range */
262 a = strndup(optarg, dots - optarg);
263 if (!a)
264 return log_oom();
265
266 from = log_level_from_string(a);
267 to = log_level_from_string(dots + 2);
268 free(a);
269
270 if (from < 0 || to < 0) {
271 log_error("Failed to parse log level range %s", optarg);
272 return -EINVAL;
273 }
274
275 arg_priorities = 0;
276
277 if (from < to) {
278 for (i = from; i <= to; i++)
279 arg_priorities |= 1 << i;
280 } else {
281 for (i = to; i <= from; i++)
282 arg_priorities |= 1 << i;
283 }
284
285 } else {
286 int p, i;
287
288 p = log_level_from_string(optarg);
289 if (p < 0) {
290 log_error("Unknown log level %s", optarg);
291 return -EINVAL;
292 }
293
294 arg_priorities = 0;
295
296 for (i = 0; i <= p; i++)
297 arg_priorities |= 1 << i;
298 }
299
300 break;
301 }
302
303 case '?':
304 return -EINVAL;
305
306 default:
307 log_error("Unknown option code %c", c);
308 return -EINVAL;
309 }
310 }
311
312 if (arg_follow && !arg_no_tail && arg_lines < 0)
313 arg_lines = 10;
314
315 return 1;
316 }
317
318 static bool on_tty(void) {
319 static int t = -1;
320
321 /* Note that this is invoked relatively early, before we start
322 * the pager. That means the value we return reflects whether
323 * we originally were started on a tty, not if we currently
324 * are. But this is intended, since we want colour and so on
325 * when run in our own pager. */
326
327 if (_unlikely_(t < 0))
328 t = isatty(STDOUT_FILENO) > 0;
329
330 return t;
331 }
332
333 static int generate_new_id128(void) {
334 sd_id128_t id;
335 int r;
336 unsigned i;
337
338 r = sd_id128_randomize(&id);
339 if (r < 0) {
340 log_error("Failed to generate ID: %s", strerror(-r));
341 return r;
342 }
343
344 printf("As string:\n"
345 SD_ID128_FORMAT_STR "\n\n"
346 "As UUID:\n"
347 "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
348 "As macro:\n"
349 "#define MESSAGE_XYZ SD_ID128_MAKE(",
350 SD_ID128_FORMAT_VAL(id),
351 SD_ID128_FORMAT_VAL(id));
352
353 for (i = 0; i < 16; i++)
354 printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
355
356 fputs(")\n", stdout);
357
358 return 0;
359 }
360
361 static int add_matches(sd_journal *j, char **args) {
362 char **i;
363 int r;
364
365 assert(j);
366
367 STRV_FOREACH(i, args) {
368
369 if (streq(*i, "+"))
370 r = sd_journal_add_disjunction(j);
371 else if (path_is_absolute(*i)) {
372 char *p, *t = NULL;
373 const char *path;
374 struct stat st;
375
376 p = canonicalize_file_name(*i);
377 path = p ? p : *i;
378
379 if (stat(path, &st) < 0) {
380 free(p);
381 log_error("Couldn't stat file: %m");
382 return -errno;
383 }
384
385 if (S_ISREG(st.st_mode) && (0111 & st.st_mode))
386 t = strappend("_EXE=", path);
387 else if (S_ISCHR(st.st_mode))
388 asprintf(&t, "_KERNEL_DEVICE=c%u:%u", major(st.st_rdev), minor(st.st_rdev));
389 else if (S_ISBLK(st.st_mode))
390 asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev));
391 else {
392 free(p);
393 log_error("File is not a device node, regular file or is not executable: %s", *i);
394 return -EINVAL;
395 }
396
397 free(p);
398
399 if (!t)
400 return log_oom();
401
402 r = sd_journal_add_match(j, t, 0);
403 free(t);
404 } else
405 r = sd_journal_add_match(j, *i, 0);
406
407 if (r < 0) {
408 log_error("Failed to add match '%s': %s", *i, strerror(-r));
409 return r;
410 }
411 }
412
413 return 0;
414 }
415
416 static int add_this_boot(sd_journal *j) {
417 char match[9+32+1] = "_BOOT_ID=";
418 sd_id128_t boot_id;
419 int r;
420
421 assert(j);
422
423 if (!arg_this_boot)
424 return 0;
425
426 r = sd_id128_get_boot(&boot_id);
427 if (r < 0) {
428 log_error("Failed to get boot id: %s", strerror(-r));
429 return r;
430 }
431
432 sd_id128_to_string(boot_id, match + 9);
433 r = sd_journal_add_match(j, match, strlen(match));
434 if (r < 0) {
435 log_error("Failed to add match: %s", strerror(-r));
436 return r;
437 }
438
439 return 0;
440 }
441
442 static int add_priorities(sd_journal *j) {
443 char match[] = "PRIORITY=0";
444 int i, r;
445
446 assert(j);
447
448 if (arg_priorities == 0xFF)
449 return 0;
450
451 for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
452 if (arg_priorities & (1 << i)) {
453 match[sizeof(match)-2] = '0' + i;
454
455 log_info("adding match %s", match);
456
457 r = sd_journal_add_match(j, match, strlen(match));
458 if (r < 0) {
459 log_error("Failed to add match: %s", strerror(-r));
460 return r;
461 }
462 }
463
464 return 0;
465 }
466
467 static int setup_keys(void) {
468 #ifdef HAVE_GCRYPT
469 size_t mpk_size, seed_size, state_size, i;
470 uint8_t *mpk, *seed, *state;
471 ssize_t l;
472 int fd = -1, r, attr = 0;
473 sd_id128_t machine, boot;
474 char *p = NULL, *k = NULL;
475 struct FSSHeader h;
476 uint64_t n;
477
478 r = sd_id128_get_machine(&machine);
479 if (r < 0) {
480 log_error("Failed to get machine ID: %s", strerror(-r));
481 return r;
482 }
483
484 r = sd_id128_get_boot(&boot);
485 if (r < 0) {
486 log_error("Failed to get boot ID: %s", strerror(-r));
487 return r;
488 }
489
490 if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
491 SD_ID128_FORMAT_VAL(machine)) < 0)
492 return log_oom();
493
494 if (access(p, F_OK) >= 0) {
495 log_error("Sealing key file %s exists already.", p);
496 r = -EEXIST;
497 goto finish;
498 }
499
500 if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX",
501 SD_ID128_FORMAT_VAL(machine)) < 0) {
502 r = log_oom();
503 goto finish;
504 }
505
506 mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR);
507 mpk = alloca(mpk_size);
508
509 seed_size = FSPRG_RECOMMENDED_SEEDLEN;
510 seed = alloca(seed_size);
511
512 state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
513 state = alloca(state_size);
514
515 fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
516 if (fd < 0) {
517 log_error("Failed to open /dev/random: %m");
518 r = -errno;
519 goto finish;
520 }
521
522 log_info("Generating seed...");
523 l = loop_read(fd, seed, seed_size, true);
524 if (l < 0 || (size_t) l != seed_size) {
525 log_error("Failed to read random seed: %s", strerror(EIO));
526 r = -EIO;
527 goto finish;
528 }
529
530 log_info("Generating key pair...");
531 FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR);
532
533 log_info("Generating sealing key...");
534 FSPRG_GenState0(state, mpk, seed, seed_size);
535
536 assert(arg_interval > 0);
537
538 n = now(CLOCK_REALTIME);
539 n /= arg_interval;
540
541 close_nointr_nofail(fd);
542 fd = mkostemp(k, O_WRONLY|O_CLOEXEC|O_NOCTTY);
543 if (fd < 0) {
544 log_error("Failed to open %s: %m", k);
545 r = -errno;
546 goto finish;
547 }
548
549 /* Enable secure remove, exclusion from dump, synchronous
550 * writing and in-place updating */
551 if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0)
552 log_warning("FS_IOC_GETFLAGS failed: %m");
553
554 attr |= FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL;
555
556 if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0)
557 log_warning("FS_IOC_SETFLAGS failed: %m");
558
559 zero(h);
560 memcpy(h.signature, "KSHHRHLP", 8);
561 h.machine_id = machine;
562 h.boot_id = boot;
563 h.header_size = htole64(sizeof(h));
564 h.start_usec = htole64(n * arg_interval);
565 h.interval_usec = htole64(arg_interval);
566 h.fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR);
567 h.fsprg_state_size = htole64(state_size);
568
569 l = loop_write(fd, &h, sizeof(h), false);
570 if (l < 0 || (size_t) l != sizeof(h)) {
571 log_error("Failed to write header: %s", strerror(EIO));
572 r = -EIO;
573 goto finish;
574 }
575
576 l = loop_write(fd, state, state_size, false);
577 if (l < 0 || (size_t) l != state_size) {
578 log_error("Failed to write state: %s", strerror(EIO));
579 r = -EIO;
580 goto finish;
581 }
582
583 if (link(k, p) < 0) {
584 log_error("Failed to link file: %m");
585 r = -errno;
586 goto finish;
587 }
588
589 if (isatty(STDOUT_FILENO)) {
590 fprintf(stderr,
591 "\n"
592 "The new key pair has been generated. The " ANSI_HIGHLIGHT_ON "secret sealing key" ANSI_HIGHLIGHT_OFF " has been written to\n"
593 "the following local file. This key file is automatically updated when the\n"
594 "sealing key is advanced. It should not be used on multiple hosts.\n"
595 "\n"
596 "\t%s\n"
597 "\n"
598 "Please write down the following " ANSI_HIGHLIGHT_ON "secret verification key" ANSI_HIGHLIGHT_OFF ". It should be stored\n"
599 "at a safe location and should not be saved locally on disk.\n"
600 "\n\t" ANSI_HIGHLIGHT_RED_ON, p);
601 fflush(stderr);
602 }
603 for (i = 0; i < seed_size; i++) {
604 if (i > 0 && i % 3 == 0)
605 putchar('-');
606 printf("%02x", ((uint8_t*) seed)[i]);
607 }
608
609 printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_interval);
610
611 if (isatty(STDOUT_FILENO)) {
612 char tsb[FORMAT_TIMESPAN_MAX], *hn;
613
614 fprintf(stderr,
615 ANSI_HIGHLIGHT_OFF "\n"
616 "The sealing key is automatically changed every %s.\n",
617 format_timespan(tsb, sizeof(tsb), arg_interval));
618
619 hn = gethostname_malloc();
620
621 if (hn) {
622 hostname_cleanup(hn);
623 fprintf(stderr, "\nThe keys have been generated for host %s/" SD_ID128_FORMAT_STR ".\n", hn, SD_ID128_FORMAT_VAL(machine));
624 } else
625 fprintf(stderr, "\nThe keys have been generated for host " SD_ID128_FORMAT_STR ".\n", SD_ID128_FORMAT_VAL(machine));
626
627 #ifdef HAVE_QRENCODE
628 fprintf(stderr, "\nTo transfer the verification key to your phone please scan the QR code below:\n\n");
629 print_qr_code(stderr, seed, seed_size, n, arg_interval, hn, machine);
630 #endif
631 free(hn);
632 }
633
634 r = 0;
635
636 finish:
637 if (fd >= 0)
638 close_nointr_nofail(fd);
639
640 if (k) {
641 unlink(k);
642 free(k);
643 }
644
645 free(p);
646
647 return r;
648 #else
649 log_error("Forward-secure sealing not available.");
650 return -ENOTSUP;
651 #endif
652 }
653
654 static int verify(sd_journal *j) {
655 int r = 0;
656 Iterator i;
657 JournalFile *f;
658
659 assert(j);
660
661 HASHMAP_FOREACH(f, j->files, i) {
662 int k;
663 usec_t from, to, total;
664
665 #ifdef HAVE_GCRYPT
666 if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header))
667 log_warning("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path);
668 #endif
669
670 k = journal_file_verify(f, arg_verify_key, &from, &to, &total, true);
671 if (k == -EINVAL) {
672 /* If the key was invalid give up right-away. */
673 return k;
674 } else if (k < 0) {
675 log_warning("FAIL: %s (%s)", f->path, strerror(-k));
676 r = k;
677 } else {
678 char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX], c[FORMAT_TIMESPAN_MAX];
679 log_info("PASS: %s", f->path);
680
681 if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header))
682 log_info("=> Validated from %s to %s, %s missing",
683 format_timestamp(a, sizeof(a), from),
684 format_timestamp(b, sizeof(b), to),
685 format_timespan(c, sizeof(c), total > to ? total - to : 0));
686 }
687 }
688
689 return r;
690 }
691
692 int main(int argc, char *argv[]) {
693 int r;
694 sd_journal *j = NULL;
695 unsigned line = 0;
696 bool need_seek = false;
697 sd_id128_t previous_boot_id;
698 bool previous_boot_id_valid = false;
699 bool have_pager;
700
701 log_parse_environment();
702 log_open();
703
704 r = parse_argv(argc, argv);
705 if (r <= 0)
706 goto finish;
707
708 if (arg_action == ACTION_NEW_ID128) {
709 r = generate_new_id128();
710 goto finish;
711 }
712
713 if (arg_action == ACTION_SETUP_KEYS) {
714 r = setup_keys();
715 goto finish;
716 }
717
718 if (arg_directory)
719 r = sd_journal_open_directory(&j, arg_directory, 0);
720 else
721 r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
722
723 if (r < 0) {
724 log_error("Failed to open journal: %s", strerror(-r));
725 goto finish;
726 }
727
728 if (arg_action == ACTION_VERIFY) {
729 r = verify(j);
730 goto finish;
731 }
732
733 if (arg_action == ACTION_PRINT_HEADER) {
734 journal_print_header(j);
735 r = 0;
736 goto finish;
737 }
738
739 #ifdef HAVE_ACL
740 if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
741 log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
742 #endif
743
744 r = add_this_boot(j);
745 if (r < 0)
746 goto finish;
747
748 r = add_matches(j, argv + optind);
749 if (r < 0)
750 goto finish;
751
752 r = add_priorities(j);
753 if (r < 0)
754 goto finish;
755
756 if (!arg_quiet) {
757 usec_t start, end;
758 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
759
760 r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
761 if (r < 0) {
762 log_error("Failed to get cutoff: %s", strerror(-r));
763 goto finish;
764 }
765
766 if (r > 0) {
767 if (arg_follow)
768 printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
769 else
770 printf("Logs begin at %s, end at %s.\n",
771 format_timestamp(start_buf, sizeof(start_buf), start),
772 format_timestamp(end_buf, sizeof(end_buf), end));
773 }
774 }
775
776 if (arg_lines >= 0) {
777 r = sd_journal_seek_tail(j);
778 if (r < 0) {
779 log_error("Failed to seek to tail: %s", strerror(-r));
780 goto finish;
781 }
782
783 r = sd_journal_previous_skip(j, arg_lines);
784 } else {
785 r = sd_journal_seek_head(j);
786 if (r < 0) {
787 log_error("Failed to seek to head: %s", strerror(-r));
788 goto finish;
789 }
790
791 r = sd_journal_next(j);
792 }
793
794 if (r < 0) {
795 log_error("Failed to iterate through journal: %s", strerror(-r));
796 goto finish;
797 }
798
799 on_tty();
800 have_pager = !arg_no_pager && !arg_follow && pager_open();
801
802 if (arg_output == OUTPUT_JSON) {
803 fputc('[', stdout);
804 fflush(stdout);
805 }
806
807 for (;;) {
808 for (;;) {
809 sd_id128_t boot_id;
810 int flags =
811 arg_show_all * OUTPUT_SHOW_ALL |
812 have_pager * OUTPUT_FULL_WIDTH |
813 on_tty() * OUTPUT_COLOR;
814
815 if (need_seek) {
816 r = sd_journal_next(j);
817 if (r < 0) {
818 log_error("Failed to iterate through journal: %s", strerror(-r));
819 goto finish;
820 }
821 }
822
823 if (r == 0)
824 break;
825
826 r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
827 if (r >= 0) {
828 if (previous_boot_id_valid &&
829 !sd_id128_equal(boot_id, previous_boot_id))
830 printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n");
831
832 previous_boot_id = boot_id;
833 previous_boot_id_valid = true;
834 }
835
836 line ++;
837
838 r = output_journal(j, arg_output, line, 0, flags);
839 if (r < 0)
840 goto finish;
841
842 need_seek = true;
843 }
844
845 if (!arg_follow)
846 break;
847
848 r = sd_journal_wait(j, (uint64_t) -1);
849 if (r < 0) {
850 log_error("Couldn't wait for log event: %s", strerror(-r));
851 goto finish;
852 }
853 }
854
855 if (arg_output == OUTPUT_JSON)
856 fputs("\n]\n", stdout);
857
858 finish:
859 if (j)
860 sd_journal_close(j);
861
862 pager_close();
863
864 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
865 }