journalctl.c 14.1 KB
Newer Older
1
2
3
4
5
6
7
8
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/

/***
  This file is part of systemd.

  Copyright 2011 Lennart Poettering

  systemd is free software; you can redistribute it and/or modify it
9
10
  under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
11
12
13
14
15
  (at your option) any later version.

  systemd is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  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
20
21
22
23
24
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

#include <fcntl.h>
#include <errno.h>
#include <stddef.h>
25
26
27
28
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
29
#include <sys/poll.h>
30
#include <time.h>
31
#include <getopt.h>
32
#include <sys/stat.h>
33

34
35
#include <systemd/sd-journal.h>

36
#include "log.h"
37
#include "util.h"
38
#include "path-util.h"
39
40
#include "build.h"
#include "pager.h"
41
#include "logs-show.h"
42
#include "strv.h"
43

44
static OutputMode arg_output = OUTPUT_SHORT;
45
46
static bool arg_follow = false;
static bool arg_show_all = false;
47
static bool arg_no_pager = false;
Lennart Poettering's avatar
Lennart Poettering committed
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
57
static int help(void) {

58
        printf("%s [OPTIONS...] [MATCH]\n\n"
59
               "Send control commands to or query the journal.\n\n"
60
61
62
               "  -h --help           Show this help\n"
               "     --version        Show package version\n"
               "     --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
68
               "  -o --output=STRING  Change journal output mode (short, short-monotonic,\n"
               "                      verbose, export, json, cat)\n"
69
               "  -q --quiet          Don't show privilege warning\n"
70
71
               "  -l --local          Only local entries\n"
               "  -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
75
76
77
78
79
80
81
82
               program_invocation_short_name);

        return 0;
}

static int parse_argv(int argc, char *argv[]) {

        enum {
                ARG_VERSION = 0x100,
83
                ARG_NO_PAGER,
84
                ARG_NO_TAIL,
85
                ARG_NEW_ID128
86
87
88
89
90
91
92
93
94
        };

        static const struct option options[] = {
                { "help",      no_argument,       NULL, 'h'           },
                { "version" ,  no_argument,       NULL, ARG_VERSION   },
                { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
                { "follow",    no_argument,       NULL, 'f'           },
                { "output",    required_argument, NULL, 'o'           },
                { "all",       no_argument,       NULL, 'a'           },
Lennart Poettering's avatar
Lennart Poettering committed
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
103
104
                { NULL,        0,                 NULL, 0             }
        };

Lennart Poettering's avatar
Lennart Poettering committed
105
        int c, r;
106
107
108
109

        assert(argc >= 0);
        assert(argv);

110
        while ((c = getopt_long(argc, argv, "hfo:an:qlbD:", options, NULL)) >= 0) {
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

                switch (c) {

                case 'h':
                        help();
                        return 0;

                case ARG_VERSION:
                        puts(PACKAGE_STRING);
                        puts(DISTRIBUTION);
                        puts(SYSTEMD_FEATURES);
                        return 0;

                case ARG_NO_PAGER:
                        arg_no_pager = true;
                        break;

                case 'f':
                        arg_follow = true;
                        break;

                case 'o':
133
134
                        arg_output =  output_mode_from_string(optarg);
                        if (arg_output < 0) {
135
136
137
                                log_error("Unknown output '%s'.", optarg);
                                return -EINVAL;
                        }
138

139
140
141
142
143
144
                        break;

                case 'a':
                        arg_show_all = true;
                        break;

Lennart Poettering's avatar
Lennart Poettering committed
145
146
                case 'n':
                        r = safe_atoi(optarg, &arg_lines);
147
                        if (r < 0 || arg_lines < 0) {
Lennart Poettering's avatar
Lennart Poettering committed
148
149
150
151
152
                                log_error("Failed to parse lines '%s'", optarg);
                                return -EINVAL;
                        }
                        break;

153
154
155
156
                case ARG_NO_TAIL:
                        arg_no_tail = true;
                        break;

157
158
                case ARG_NEW_ID128:
                        arg_new_id128 = true;
159
160
                        break;

161
162
                case 'q':
                        arg_quiet = true;
163
                        break;
164

165
166
167
168
                case 'l':
                        arg_local = true;
                        break;

169
170
171
172
                case 'b':
                        arg_this_boot = true;
                        break;

173
174
175
176
                case 'D':
                        arg_directory = optarg;
                        break;

177
178
179
180
181
182
183
184
185
                case '?':
                        return -EINVAL;

                default:
                        log_error("Unknown option code %c", c);
                        return -EINVAL;
                }
        }

186
        if (arg_follow && !arg_no_tail && arg_lines < 0)
187
188
                arg_lines = 10;

189
190
191
        return 1;
}

192
static int generate_new_id128(void) {
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
        sd_id128_t id;
        int r;
        unsigned i;

        r = sd_id128_randomize(&id);
        if (r < 0) {
                log_error("Failed to generate ID: %s", strerror(-r));
                return r;
        }

        printf("As string:\n"
               SD_ID128_FORMAT_STR "\n\n"
               "As UUID:\n"
               "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
               "As macro:\n"
              "#define MESSAGE_XYZ SD_ID128_MAKE(",
               SD_ID128_FORMAT_VAL(id),
               SD_ID128_FORMAT_VAL(id));

        for (i = 0; i < 16; i++)
                printf("%02x%s", id.bytes[i], i != 15 ? "," : "");

        fputs(")\n", stdout);

        return 0;
}

220
221
222
static int add_matches(sd_journal *j, char **args) {
        char **i;
        int r;
223

224
        assert(j);
225

226
        STRV_FOREACH(i, args) {
227

228
229
                if (path_is_absolute(*i)) {
                        char *p;
230
                        const char *path;
231
                        struct stat st;
232

233
234
                        p = canonicalize_file_name(*i);
                        path = p ? p : *i;
235
236
237
238

                        if (stat(path, &st) < 0)  {
                                free(p);
                                log_error("Couldn't stat file: %m");
239
                                return -errno;
240
241
242
243
244
245
246
247
248
                        }

                        if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
                                char *t;

                                t = strappend("_EXE=", path);
                                if (!t) {
                                        free(p);
                                        log_error("Out of memory");
249
                                        return -ENOMEM;
250
251
252
253
                                }

                                r = sd_journal_add_match(j, t, strlen(t));
                                free(t);
254
                        } else {
255
                                free(p);
256
257
                                log_error("File is not a regular file or is not executable: %s", *i);
                                return -EINVAL;
258
                        }
259
260
261

                        free(p);
                } else
262
                        r = sd_journal_add_match(j, *i, strlen(*i));
263

264
265
                if (r < 0) {
                        log_error("Failed to add match: %s", strerror(-r));
266
                        return r;
267
268
269
                }
        }

270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
        return 0;
}

static int add_this_boot(sd_journal *j) {
        char match[9+32+1] = "_BOOT_ID=";
        sd_id128_t boot_id;
        int r;

        if (!arg_this_boot)
                return 0;

        r = sd_id128_get_boot(&boot_id);
        if (r < 0) {
                log_error("Failed to get boot id: %s", strerror(-r));
                return r;
        }

        sd_id128_to_string(boot_id, match + 9);
        r = sd_journal_add_match(j, match, strlen(match));
        if (r < 0) {
                log_error("Failed to add match: %s", strerror(-r));
                return r;
        }

        return 0;
}

int main(int argc, char *argv[]) {
        int r;
        sd_journal *j = NULL;
        unsigned line = 0;
        bool need_seek = false;
302
303
        sd_id128_t previous_boot_id;
        bool previous_boot_id_valid = false;
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

        log_parse_environment();
        log_open();

        r = parse_argv(argc, argv);
        if (r <= 0)
                goto finish;

        if (arg_new_id128) {
                r = generate_new_id128();
                goto finish;
        }

#ifdef HAVE_ACL
        if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
                log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
#endif

        if (arg_directory)
                r = sd_journal_open_directory(&j, arg_directory, 0);
        else
                r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);

        if (r < 0) {
                log_error("Failed to open journal: %s", strerror(-r));
                goto finish;
        }

        r = add_this_boot(j);
        if (r < 0)
                goto finish;

        r = add_matches(j, argv + optind);
        if (r < 0)
                goto finish;

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
        if (!arg_quiet) {
                usec_t start, end;
                char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];

                r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
                if (r < 0) {
                        log_error("Failed to get cutoff: %s", strerror(-r));
                        goto finish;
                }

                if (r > 0) {
                        if (arg_follow)
                                printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
                        else
                                printf("Logs begin at %s, end at %s.\n",
                                       format_timestamp(start_buf, sizeof(start_buf), start),
                                       format_timestamp(end_buf, sizeof(end_buf), end));
                }
        }

Lennart Poettering's avatar
Lennart Poettering committed
360
361
362
363
364
365
366
367
368
369
370
371
372
373
        if (arg_lines >= 0) {
                r = sd_journal_seek_tail(j);
                if (r < 0) {
                        log_error("Failed to seek to tail: %s", strerror(-r));
                        goto finish;
                }

                r = sd_journal_previous_skip(j, arg_lines);
        } else {
                r = sd_journal_seek_head(j);
                if (r < 0) {
                        log_error("Failed to seek to head: %s", strerror(-r));
                        goto finish;
                }
374
375
376
377
378
379
380

                r = sd_journal_next(j);
        }

        if (r < 0) {
                log_error("Failed to iterate through journal: %s", strerror(-r));
                goto finish;
381
        }
382

383
384
385
386
387
388
        if (!arg_no_pager && !arg_follow) {
                columns();
                pager_open();
        }

        if (arg_output == OUTPUT_JSON) {
389
                fputc('[', stdout);
390
391
                fflush(stdout);
        }
392

393
        for (;;) {
394
                for (;;) {
395
396
                        sd_id128_t boot_id;

397
398
399
400
401
402
                        if (need_seek) {
                                r = sd_journal_next(j);
                                if (r < 0) {
                                        log_error("Failed to iterate through journal: %s", strerror(-r));
                                        goto finish;
                                }
403
404
405
406
407
                        }

                        if (r == 0)
                                break;

408
409
410
411
412
413
414
415
416
417
                        r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
                        if (r >= 0) {
                                if (previous_boot_id_valid &&
                                    !sd_id128_equal(boot_id, previous_boot_id))
                                        printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n");

                                previous_boot_id = boot_id;
                                previous_boot_id_valid = true;
                        }

418
                        line ++;
419

420
                        r = output_journal(j, arg_output, line, 0, arg_show_all);
421
422
                        if (r < 0)
                                goto finish;
423
424

                        need_seek = true;
425
426
                }

427
428
429
                if (!arg_follow)
                        break;

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
434
                        goto finish;
                }
435
        }
436

437
438
439
        if (arg_output == OUTPUT_JSON)
                fputs("\n]\n", stdout);

440
finish:
441
442
        if (j)
                sd_journal_close(j);
443

444
445
        pager_close();

446
        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
447
}