chiark / gitweb /
43cd2a3feff46b3ff9ca88df5756eb91f665f799
[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 (path_is_absolute(*i)) {
229                         char *p;
230                         const char *path;
231                         struct stat st;
232
233                         p = canonicalize_file_name(*i);
234                         path = p ? p : *i;
235
236                         if (stat(path, &st) < 0)  {
237                                 free(p);
238                                 log_error("Couldn't stat file: %m");
239                                 return -errno;
240                         }
241
242                         if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
243                                 char *t;
244
245                                 t = strappend("_EXE=", path);
246                                 if (!t) {
247                                         free(p);
248                                         log_error("Out of memory");
249                                         return -ENOMEM;
250                                 }
251
252                                 r = sd_journal_add_match(j, t, strlen(t));
253                                 free(t);
254                         } else {
255                                 free(p);
256                                 log_error("File is not a regular file or is not executable: %s", *i);
257                                 return -EINVAL;
258                         }
259
260                         free(p);
261                 } else
262                         r = sd_journal_add_match(j, *i, strlen(*i));
263
264                 if (r < 0) {
265                         log_error("Failed to add match: %s", strerror(-r));
266                         return r;
267                 }
268         }
269
270         return 0;
271 }
272
273 static int add_this_boot(sd_journal *j) {
274         char match[9+32+1] = "_BOOT_ID=";
275         sd_id128_t boot_id;
276         int r;
277
278         if (!arg_this_boot)
279                 return 0;
280
281         r = sd_id128_get_boot(&boot_id);
282         if (r < 0) {
283                 log_error("Failed to get boot id: %s", strerror(-r));
284                 return r;
285         }
286
287         sd_id128_to_string(boot_id, match + 9);
288         r = sd_journal_add_match(j, match, strlen(match));
289         if (r < 0) {
290                 log_error("Failed to add match: %s", strerror(-r));
291                 return r;
292         }
293
294         return 0;
295 }
296
297 int main(int argc, char *argv[]) {
298         int r;
299         sd_journal *j = NULL;
300         unsigned line = 0;
301         bool need_seek = false;
302         sd_id128_t previous_boot_id;
303         bool previous_boot_id_valid = false;
304
305         log_parse_environment();
306         log_open();
307
308         r = parse_argv(argc, argv);
309         if (r <= 0)
310                 goto finish;
311
312         if (arg_new_id128) {
313                 r = generate_new_id128();
314                 goto finish;
315         }
316
317 #ifdef HAVE_ACL
318         if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
319                 log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
320 #endif
321
322         if (arg_directory)
323                 r = sd_journal_open_directory(&j, arg_directory, 0);
324         else
325                 r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
326
327         if (r < 0) {
328                 log_error("Failed to open journal: %s", strerror(-r));
329                 goto finish;
330         }
331
332         r = add_this_boot(j);
333         if (r < 0)
334                 goto finish;
335
336         r = add_matches(j, argv + optind);
337         if (r < 0)
338                 goto finish;
339
340         if (!arg_quiet) {
341                 usec_t start, end;
342                 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
343
344                 r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
345                 if (r < 0) {
346                         log_error("Failed to get cutoff: %s", strerror(-r));
347                         goto finish;
348                 }
349
350                 if (r > 0) {
351                         if (arg_follow)
352                                 printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
353                         else
354                                 printf("Logs begin at %s, end at %s.\n",
355                                        format_timestamp(start_buf, sizeof(start_buf), start),
356                                        format_timestamp(end_buf, sizeof(end_buf), end));
357                 }
358         }
359
360         if (arg_lines >= 0) {
361                 r = sd_journal_seek_tail(j);
362                 if (r < 0) {
363                         log_error("Failed to seek to tail: %s", strerror(-r));
364                         goto finish;
365                 }
366
367                 r = sd_journal_previous_skip(j, arg_lines);
368         } else {
369                 r = sd_journal_seek_head(j);
370                 if (r < 0) {
371                         log_error("Failed to seek to head: %s", strerror(-r));
372                         goto finish;
373                 }
374
375                 r = sd_journal_next(j);
376         }
377
378         if (r < 0) {
379                 log_error("Failed to iterate through journal: %s", strerror(-r));
380                 goto finish;
381         }
382
383         if (!arg_no_pager && !arg_follow) {
384                 columns();
385                 pager_open();
386         }
387
388         if (arg_output == OUTPUT_JSON) {
389                 fputc('[', stdout);
390                 fflush(stdout);
391         }
392
393         for (;;) {
394                 for (;;) {
395                         sd_id128_t boot_id;
396
397                         if (need_seek) {
398                                 r = sd_journal_next(j);
399                                 if (r < 0) {
400                                         log_error("Failed to iterate through journal: %s", strerror(-r));
401                                         goto finish;
402                                 }
403                         }
404
405                         if (r == 0)
406                                 break;
407
408                         r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
409                         if (r >= 0) {
410                                 if (previous_boot_id_valid &&
411                                     !sd_id128_equal(boot_id, previous_boot_id))
412                                         printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n");
413
414                                 previous_boot_id = boot_id;
415                                 previous_boot_id_valid = true;
416                         }
417
418                         line ++;
419
420                         r = output_journal(j, arg_output, line, 0, arg_show_all);
421                         if (r < 0)
422                                 goto finish;
423
424                         need_seek = true;
425                 }
426
427                 if (!arg_follow)
428                         break;
429
430                 r = sd_journal_wait(j, (uint64_t) -1);
431                 if (r < 0) {
432                         log_error("Couldn't wait for log event: %s", strerror(-r));
433                         goto finish;
434                 }
435         }
436
437         if (arg_output == OUTPUT_JSON)
438                 fputs("\n]\n", stdout);
439
440 finish:
441         if (j)
442                 sd_journal_close(j);
443
444         pager_close();
445
446         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
447 }