]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journalctl.c
4c975d3e7c49c3b6c1bcea5df70918f1e86b23c5
[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
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
303 log_parse_environment();
304 log_open();
305
306 r = parse_argv(argc, argv);
307 if (r <= 0)
308 goto finish;
309
310 if (arg_new_id128) {
311 r = generate_new_id128();
312 goto finish;
313 }
314
315 #ifdef HAVE_ACL
316 if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
317 log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
318 #endif
319
320 if (arg_directory)
321 r = sd_journal_open_directory(&j, arg_directory, 0);
322 else
323 r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
324
325 if (r < 0) {
326 log_error("Failed to open journal: %s", strerror(-r));
327 goto finish;
328 }
329
330 r = add_this_boot(j);
331 if (r < 0)
332 goto finish;
333
334 r = add_matches(j, argv + optind);
335 if (r < 0)
336 goto finish;
337
338 if (!arg_quiet) {
339 usec_t start, end;
340 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
341
342 r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
343 if (r < 0) {
344 log_error("Failed to get cutoff: %s", strerror(-r));
345 goto finish;
346 }
347
348 if (r > 0) {
349 if (arg_follow)
350 printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
351 else
352 printf("Logs begin at %s, end at %s.\n",
353 format_timestamp(start_buf, sizeof(start_buf), start),
354 format_timestamp(end_buf, sizeof(end_buf), end));
355 }
356 }
357
358 if (arg_lines >= 0) {
359 r = sd_journal_seek_tail(j);
360 if (r < 0) {
361 log_error("Failed to seek to tail: %s", strerror(-r));
362 goto finish;
363 }
364
365 r = sd_journal_previous_skip(j, arg_lines);
366 } else {
367 r = sd_journal_seek_head(j);
368 if (r < 0) {
369 log_error("Failed to seek to head: %s", strerror(-r));
370 goto finish;
371 }
372
373 r = sd_journal_next(j);
374 }
375
376 if (r < 0) {
377 log_error("Failed to iterate through journal: %s", strerror(-r));
378 goto finish;
379 }
380
381 if (!arg_no_pager && !arg_follow) {
382 columns();
383 pager_open();
384 }
385
386 if (arg_output == OUTPUT_JSON) {
387 fputc('[', stdout);
388 fflush(stdout);
389 }
390
391 for (;;) {
392 for (;;) {
393 if (need_seek) {
394 r = sd_journal_next(j);
395 if (r < 0) {
396 log_error("Failed to iterate through journal: %s", strerror(-r));
397 goto finish;
398 }
399 }
400
401 if (r == 0)
402 break;
403
404 line ++;
405
406 r = output_journal(j, arg_output, line, 0, arg_show_all);
407 if (r < 0)
408 goto finish;
409
410 need_seek = true;
411 }
412
413 if (!arg_follow)
414 break;
415
416 r = sd_journal_wait(j, (uint64_t) -1);
417 if (r < 0) {
418 log_error("Couldn't wait for log event: %s", strerror(-r));
419 goto finish;
420 }
421 }
422
423 if (arg_output == OUTPUT_JSON)
424 fputs("\n]\n", stdout);
425
426 finish:
427 if (j)
428 sd_journal_close(j);
429
430 pager_close();
431
432 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
433 }