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