chiark / gitweb /
62bb904dbe9e5433528bea4c71701521c23c8633
[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
45 static OutputMode arg_output = OUTPUT_SHORT;
46 static bool arg_follow = false;
47 static bool arg_show_all = false;
48 static bool arg_no_pager = false;
49 static int arg_lines = -1;
50 static bool arg_no_tail = false;
51 static bool arg_new_id128 = false;
52 static bool arg_print_header = false;
53 static bool arg_quiet = false;
54 static bool arg_local = false;
55 static bool arg_this_boot = false;
56 static const char *arg_directory = NULL;
57 static int arg_priorities = 0xFF;
58
59 static int help(void) {
60
61         printf("%s [OPTIONS...] [MATCH]\n\n"
62                "Send control commands to or query the journal.\n\n"
63                "  -h --help           Show this help\n"
64                "     --version        Show package version\n"
65                "     --no-pager       Do not pipe output into a pager\n"
66                "  -a --all            Show all fields, including long and unprintable\n"
67                "  -f --follow         Follow journal\n"
68                "  -n --lines=INTEGER  Journal entries to show\n"
69                "     --no-tail        Show all lines, even in follow mode\n"
70                "  -o --output=STRING  Change journal output mode (short, short-monotonic,\n"
71                "                      verbose, export, json, cat)\n"
72                "  -q --quiet          Don't show privilege warning\n"
73                "  -l --local          Only local entries\n"
74                "  -b --this-boot      Show data only from current boot\n"
75                "  -D --directory=PATH Show journal files from directory\n"
76                "  -p --priority=RANGE Show only messages within the specified priority range\n"
77                "     --header         Show journal header information\n"
78                "     --new-id128      Generate a new 128 Bit id\n",
79                program_invocation_short_name);
80
81         return 0;
82 }
83
84 static int parse_argv(int argc, char *argv[]) {
85
86         enum {
87                 ARG_VERSION = 0x100,
88                 ARG_NO_PAGER,
89                 ARG_NO_TAIL,
90                 ARG_NEW_ID128,
91                 ARG_HEADER
92         };
93
94         static const struct option options[] = {
95                 { "help",      no_argument,       NULL, 'h'           },
96                 { "version" ,  no_argument,       NULL, ARG_VERSION   },
97                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
98                 { "follow",    no_argument,       NULL, 'f'           },
99                 { "output",    required_argument, NULL, 'o'           },
100                 { "all",       no_argument,       NULL, 'a'           },
101                 { "lines",     required_argument, NULL, 'n'           },
102                 { "no-tail",   no_argument,       NULL, ARG_NO_TAIL   },
103                 { "new-id128", no_argument,       NULL, ARG_NEW_ID128 },
104                 { "quiet",     no_argument,       NULL, 'q'           },
105                 { "local",     no_argument,       NULL, 'l'           },
106                 { "this-boot", no_argument,       NULL, 'b'           },
107                 { "directory", required_argument, NULL, 'D'           },
108                 { "header",    no_argument,       NULL, ARG_HEADER    },
109                 { "priority",  no_argument,       NULL, 'p'           },
110                 { NULL,        0,                 NULL, 0             }
111         };
112
113         int c, r;
114
115         assert(argc >= 0);
116         assert(argv);
117
118         while ((c = getopt_long(argc, argv, "hfo:an:qlbD:p:", options, NULL)) >= 0) {
119
120                 switch (c) {
121
122                 case 'h':
123                         help();
124                         return 0;
125
126                 case ARG_VERSION:
127                         puts(PACKAGE_STRING);
128                         puts(DISTRIBUTION);
129                         puts(SYSTEMD_FEATURES);
130                         return 0;
131
132                 case ARG_NO_PAGER:
133                         arg_no_pager = true;
134                         break;
135
136                 case 'f':
137                         arg_follow = true;
138                         break;
139
140                 case 'o':
141                         arg_output =  output_mode_from_string(optarg);
142                         if (arg_output < 0) {
143                                 log_error("Unknown output '%s'.", optarg);
144                                 return -EINVAL;
145                         }
146
147                         break;
148
149                 case 'a':
150                         arg_show_all = true;
151                         break;
152
153                 case 'n':
154                         r = safe_atoi(optarg, &arg_lines);
155                         if (r < 0 || arg_lines < 0) {
156                                 log_error("Failed to parse lines '%s'", optarg);
157                                 return -EINVAL;
158                         }
159                         break;
160
161                 case ARG_NO_TAIL:
162                         arg_no_tail = true;
163                         break;
164
165                 case ARG_NEW_ID128:
166                         arg_new_id128 = true;
167                         break;
168
169                 case 'q':
170                         arg_quiet = true;
171                         break;
172
173                 case 'l':
174                         arg_local = true;
175                         break;
176
177                 case 'b':
178                         arg_this_boot = true;
179                         break;
180
181                 case 'D':
182                         arg_directory = optarg;
183                         break;
184
185                 case ARG_HEADER:
186                         arg_print_header = true;
187                         break;
188
189                 case 'p': {
190                         const char *dots;
191
192                         dots = strstr(optarg, "..");
193                         if (dots) {
194                                 char *a;
195                                 int from, to, i;
196
197                                 /* a range */
198                                 a = strndup(optarg, dots - optarg);
199                                 if (!a)
200                                         return log_oom();
201
202                                 from = log_level_from_string(a);
203                                 to = log_level_from_string(dots + 2);
204                                 free(a);
205
206                                 if (from < 0 || to < 0) {
207                                         log_error("Failed to parse log level range %s", optarg);
208                                         return -EINVAL;
209                                 }
210
211                                 arg_priorities = 0;
212
213                                 if (from < to) {
214                                         for (i = from; i <= to; i++)
215                                                 arg_priorities |= 1 << i;
216                                 } else {
217                                         for (i = to; i <= from; i++)
218                                                 arg_priorities |= 1 << i;
219                                 }
220
221                         } else {
222                                 int p, i;
223
224                                 p = log_level_from_string(optarg);
225                                 if (p < 0) {
226                                         log_error("Unknown log level %s", optarg);
227                                         return -EINVAL;
228                                 }
229
230                                 arg_priorities = 0;
231
232                                 for (i = 0; i <= p; i++)
233                                         arg_priorities |= 1 << i;
234                         }
235
236                         break;
237                 }
238
239                 case '?':
240                         return -EINVAL;
241
242                 default:
243                         log_error("Unknown option code %c", c);
244                         return -EINVAL;
245                 }
246         }
247
248         if (arg_follow && !arg_no_tail && arg_lines < 0)
249                 arg_lines = 10;
250
251         return 1;
252 }
253
254 static bool on_tty(void) {
255         static int t = -1;
256
257         /* Note that this is invoked relatively early, before we start
258          * the pager. That means the value we return reflects whether
259          * we originally were started on a tty, not if we currently
260          * are. But this is intended, since we want colour and so on
261          * when run in our own pager. */
262
263         if (_unlikely_(t < 0))
264                 t = isatty(STDOUT_FILENO) > 0;
265
266         return t;
267 }
268
269 static int generate_new_id128(void) {
270         sd_id128_t id;
271         int r;
272         unsigned i;
273
274         r = sd_id128_randomize(&id);
275         if (r < 0) {
276                 log_error("Failed to generate ID: %s", strerror(-r));
277                 return r;
278         }
279
280         printf("As string:\n"
281                SD_ID128_FORMAT_STR "\n\n"
282                "As UUID:\n"
283                "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
284                "As macro:\n"
285               "#define MESSAGE_XYZ SD_ID128_MAKE(",
286                SD_ID128_FORMAT_VAL(id),
287                SD_ID128_FORMAT_VAL(id));
288
289         for (i = 0; i < 16; i++)
290                 printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
291
292         fputs(")\n", stdout);
293
294         return 0;
295 }
296
297 static int add_matches(sd_journal *j, char **args) {
298         char **i;
299         int r;
300
301         assert(j);
302
303         STRV_FOREACH(i, args) {
304
305                 if (streq(*i, "+"))
306                         r = sd_journal_add_disjunction(j);
307                 else if (path_is_absolute(*i)) {
308                         char *p, *t = NULL;
309                         const char *path;
310                         struct stat st;
311
312                         p = canonicalize_file_name(*i);
313                         path = p ? p : *i;
314
315                         if (stat(path, &st) < 0)  {
316                                 free(p);
317                                 log_error("Couldn't stat file: %m");
318                                 return -errno;
319                         }
320
321                         if (S_ISREG(st.st_mode) && (0111 & st.st_mode))
322                                 t = strappend("_EXE=", path);
323                         else if (S_ISCHR(st.st_mode))
324                                 asprintf(&t, "_KERNEL_DEVICE=c%u:%u", major(st.st_rdev), minor(st.st_rdev));
325                         else if (S_ISBLK(st.st_mode))
326                                 asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev));
327                         else {
328                                 free(p);
329                                 log_error("File is not a device node, regular file or is not executable: %s", *i);
330                                 return -EINVAL;
331                         }
332
333                         free(p);
334
335                         if (!t)
336                                 return log_oom();
337
338                         r = sd_journal_add_match(j, t, 0);
339                         free(t);
340                 } else
341                         r = sd_journal_add_match(j, *i, 0);
342
343                 if (r < 0) {
344                         log_error("Failed to add match '%s': %s", *i, strerror(-r));
345                         return r;
346                 }
347         }
348
349         return 0;
350 }
351
352 static int add_this_boot(sd_journal *j) {
353         char match[9+32+1] = "_BOOT_ID=";
354         sd_id128_t boot_id;
355         int r;
356
357         assert(j);
358
359         if (!arg_this_boot)
360                 return 0;
361
362         r = sd_id128_get_boot(&boot_id);
363         if (r < 0) {
364                 log_error("Failed to get boot id: %s", strerror(-r));
365                 return r;
366         }
367
368         sd_id128_to_string(boot_id, match + 9);
369         r = sd_journal_add_match(j, match, strlen(match));
370         if (r < 0) {
371                 log_error("Failed to add match: %s", strerror(-r));
372                 return r;
373         }
374
375         return 0;
376 }
377
378 static int add_priorities(sd_journal *j) {
379         char match[] = "PRIORITY=0";
380         int i, r;
381
382         assert(j);
383
384         if (arg_priorities == 0xFF)
385                 return 0;
386
387         for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
388                 if (arg_priorities & (1 << i)) {
389                         match[sizeof(match)-2] = '0' + i;
390
391                         log_info("adding match %s", match);
392
393                         r = sd_journal_add_match(j, match, strlen(match));
394                         if (r < 0) {
395                                 log_error("Failed to add match: %s", strerror(-r));
396                                 return r;
397                         }
398                 }
399
400         return 0;
401 }
402
403 int main(int argc, char *argv[]) {
404         int r;
405         sd_journal *j = NULL;
406         unsigned line = 0;
407         bool need_seek = false;
408         sd_id128_t previous_boot_id;
409         bool previous_boot_id_valid = false;
410         bool have_pager;
411
412         log_parse_environment();
413         log_open();
414
415         r = parse_argv(argc, argv);
416         if (r <= 0)
417                 goto finish;
418
419         if (arg_new_id128) {
420                 r = generate_new_id128();
421                 goto finish;
422         }
423
424 #ifdef HAVE_ACL
425         if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
426                 log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
427 #endif
428
429         if (arg_directory)
430                 r = sd_journal_open_directory(&j, arg_directory, 0);
431         else
432                 r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
433
434         if (r < 0) {
435                 log_error("Failed to open journal: %s", strerror(-r));
436                 goto finish;
437         }
438
439         if (arg_print_header) {
440                 journal_print_header(j);
441                 r = 0;
442                 goto finish;
443         }
444
445         r = add_this_boot(j);
446         if (r < 0)
447                 goto finish;
448
449         r = add_matches(j, argv + optind);
450         if (r < 0)
451                 goto finish;
452
453         r = add_priorities(j);
454         if (r < 0)
455                 goto finish;
456
457         if (!arg_quiet) {
458                 usec_t start, end;
459                 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
460
461                 r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
462                 if (r < 0) {
463                         log_error("Failed to get cutoff: %s", strerror(-r));
464                         goto finish;
465                 }
466
467                 if (r > 0) {
468                         if (arg_follow)
469                                 printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
470                         else
471                                 printf("Logs begin at %s, end at %s.\n",
472                                        format_timestamp(start_buf, sizeof(start_buf), start),
473                                        format_timestamp(end_buf, sizeof(end_buf), end));
474                 }
475         }
476
477         if (arg_lines >= 0) {
478                 r = sd_journal_seek_tail(j);
479                 if (r < 0) {
480                         log_error("Failed to seek to tail: %s", strerror(-r));
481                         goto finish;
482                 }
483
484                 r = sd_journal_previous_skip(j, arg_lines);
485         } else {
486                 r = sd_journal_seek_head(j);
487                 if (r < 0) {
488                         log_error("Failed to seek to head: %s", strerror(-r));
489                         goto finish;
490                 }
491
492                 r = sd_journal_next(j);
493         }
494
495         if (r < 0) {
496                 log_error("Failed to iterate through journal: %s", strerror(-r));
497                 goto finish;
498         }
499
500         on_tty();
501         have_pager = !arg_no_pager && !arg_follow && pager_open();
502
503         if (arg_output == OUTPUT_JSON) {
504                 fputc('[', stdout);
505                 fflush(stdout);
506         }
507
508         for (;;) {
509                 for (;;) {
510                         sd_id128_t boot_id;
511                         int flags =
512                                 arg_show_all * OUTPUT_SHOW_ALL |
513                                 have_pager * OUTPUT_FULL_WIDTH |
514                                 on_tty() * OUTPUT_COLOR;
515
516                         if (need_seek) {
517                                 r = sd_journal_next(j);
518                                 if (r < 0) {
519                                         log_error("Failed to iterate through journal: %s", strerror(-r));
520                                         goto finish;
521                                 }
522                         }
523
524                         if (r == 0)
525                                 break;
526
527                         r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
528                         if (r >= 0) {
529                                 if (previous_boot_id_valid &&
530                                     !sd_id128_equal(boot_id, previous_boot_id))
531                                         printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n");
532
533                                 previous_boot_id = boot_id;
534                                 previous_boot_id_valid = true;
535                         }
536
537                         line ++;
538
539                         r = output_journal(j, arg_output, line, 0, flags);
540                         if (r < 0)
541                                 goto finish;
542
543                         need_seek = true;
544                 }
545
546                 if (!arg_follow)
547                         break;
548
549                 r = sd_journal_wait(j, (uint64_t) -1);
550                 if (r < 0) {
551                         log_error("Couldn't wait for log event: %s", strerror(-r));
552                         goto finish;
553                 }
554         }
555
556         if (arg_output == OUTPUT_JSON)
557                 fputs("\n]\n", stdout);
558
559 finish:
560         if (j)
561                 sd_journal_close(j);
562
563         pager_close();
564
565         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
566 }