]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journalctl.c
abcfabe75d4e56d02b7f2d1b1a72c85081195638
[thirdparty/systemd.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 }