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