chiark / gitweb /
10959423f695d377426c09f33637c8b9a595482f
[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;
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                                 char *t;
323
324                                 t = strappend("_EXE=", path);
325                                 if (!t) {
326                                         free(p);
327                                         return log_oom();
328                                 }
329
330                                 r = sd_journal_add_match(j, t, 0);
331                                 free(t);
332                         } else {
333                                 free(p);
334                                 log_error("File is not a regular file or is not executable: %s", *i);
335                                 return -EINVAL;
336                         }
337
338                         free(p);
339                 } else
340                         r = sd_journal_add_match(j, *i, 0);
341
342                 if (r < 0) {
343                         log_error("Failed to add match '%s': %s", *i, strerror(-r));
344                         return r;
345                 }
346         }
347
348         return 0;
349 }
350
351 static int add_this_boot(sd_journal *j) {
352         char match[9+32+1] = "_BOOT_ID=";
353         sd_id128_t boot_id;
354         int r;
355
356         assert(j);
357
358         if (!arg_this_boot)
359                 return 0;
360
361         r = sd_id128_get_boot(&boot_id);
362         if (r < 0) {
363                 log_error("Failed to get boot id: %s", strerror(-r));
364                 return r;
365         }
366
367         sd_id128_to_string(boot_id, match + 9);
368         r = sd_journal_add_match(j, match, strlen(match));
369         if (r < 0) {
370                 log_error("Failed to add match: %s", strerror(-r));
371                 return r;
372         }
373
374         return 0;
375 }
376
377 static int add_priorities(sd_journal *j) {
378         char match[] = "PRIORITY=0";
379         int i, r;
380
381         assert(j);
382
383         if (arg_priorities == 0xFF)
384                 return 0;
385
386         for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
387                 if (arg_priorities & (1 << i)) {
388                         match[sizeof(match)-2] = '0' + i;
389
390                         log_info("adding match %s", match);
391
392                         r = sd_journal_add_match(j, match, strlen(match));
393                         if (r < 0) {
394                                 log_error("Failed to add match: %s", strerror(-r));
395                                 return r;
396                         }
397                 }
398
399         return 0;
400 }
401
402 int main(int argc, char *argv[]) {
403         int r;
404         sd_journal *j = NULL;
405         unsigned line = 0;
406         bool need_seek = false;
407         sd_id128_t previous_boot_id;
408         bool previous_boot_id_valid = false;
409         bool have_pager;
410
411         log_parse_environment();
412         log_open();
413
414         r = parse_argv(argc, argv);
415         if (r <= 0)
416                 goto finish;
417
418         if (arg_new_id128) {
419                 r = generate_new_id128();
420                 goto finish;
421         }
422
423 #ifdef HAVE_ACL
424         if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
425                 log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
426 #endif
427
428         if (arg_directory)
429                 r = sd_journal_open_directory(&j, arg_directory, 0);
430         else
431                 r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
432
433         if (r < 0) {
434                 log_error("Failed to open journal: %s", strerror(-r));
435                 goto finish;
436         }
437
438         if (arg_print_header) {
439                 journal_print_header(j);
440                 r = 0;
441                 goto finish;
442         }
443
444         r = add_this_boot(j);
445         if (r < 0)
446                 goto finish;
447
448         r = add_matches(j, argv + optind);
449         if (r < 0)
450                 goto finish;
451
452         r = add_priorities(j);
453         if (r < 0)
454                 goto finish;
455
456         if (!arg_quiet) {
457                 usec_t start, end;
458                 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
459
460                 r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
461                 if (r < 0) {
462                         log_error("Failed to get cutoff: %s", strerror(-r));
463                         goto finish;
464                 }
465
466                 if (r > 0) {
467                         if (arg_follow)
468                                 printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
469                         else
470                                 printf("Logs begin at %s, end at %s.\n",
471                                        format_timestamp(start_buf, sizeof(start_buf), start),
472                                        format_timestamp(end_buf, sizeof(end_buf), end));
473                 }
474         }
475
476         if (arg_lines >= 0) {
477                 r = sd_journal_seek_tail(j);
478                 if (r < 0) {
479                         log_error("Failed to seek to tail: %s", strerror(-r));
480                         goto finish;
481                 }
482
483                 r = sd_journal_previous_skip(j, arg_lines);
484         } else {
485                 r = sd_journal_seek_head(j);
486                 if (r < 0) {
487                         log_error("Failed to seek to head: %s", strerror(-r));
488                         goto finish;
489                 }
490
491                 r = sd_journal_next(j);
492         }
493
494         if (r < 0) {
495                 log_error("Failed to iterate through journal: %s", strerror(-r));
496                 goto finish;
497         }
498
499         on_tty();
500         have_pager = !arg_no_pager && !arg_follow && pager_open();
501
502         if (arg_output == OUTPUT_JSON) {
503                 fputc('[', stdout);
504                 fflush(stdout);
505         }
506
507         for (;;) {
508                 for (;;) {
509                         sd_id128_t boot_id;
510                         int flags =
511                                 arg_show_all * OUTPUT_SHOW_ALL |
512                                 have_pager * OUTPUT_FULL_WIDTH |
513                                 on_tty() * OUTPUT_COLOR;
514
515                         if (need_seek) {
516                                 r = sd_journal_next(j);
517                                 if (r < 0) {
518                                         log_error("Failed to iterate through journal: %s", strerror(-r));
519                                         goto finish;
520                                 }
521                         }
522
523                         if (r == 0)
524                                 break;
525
526                         r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
527                         if (r >= 0) {
528                                 if (previous_boot_id_valid &&
529                                     !sd_id128_equal(boot_id, previous_boot_id))
530                                         printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n");
531
532                                 previous_boot_id = boot_id;
533                                 previous_boot_id_valid = true;
534                         }
535
536                         line ++;
537
538                         r = output_journal(j, arg_output, line, 0, flags);
539                         if (r < 0)
540                                 goto finish;
541
542                         need_seek = true;
543                 }
544
545                 if (!arg_follow)
546                         break;
547
548                 r = sd_journal_wait(j, (uint64_t) -1);
549                 if (r < 0) {
550                         log_error("Couldn't wait for log event: %s", strerror(-r));
551                         goto finish;
552                 }
553         }
554
555         if (arg_output == OUTPUT_JSON)
556                 fputs("\n]\n", stdout);
557
558 finish:
559         if (j)
560                 sd_journal_close(j);
561
562         pager_close();
563
564         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
565 }