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