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