]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/coredumpctl.c
journal: document new catalog APIs
[thirdparty/systemd.git] / src / journal / coredumpctl.c
CommitLineData
5de0409e
ZJS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2012 Zbigniew Jędrzejewski-Szmek
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
a9cdc94f 22#include <locale.h>
5de0409e
ZJS
23#include <stdio.h>
24#include <string.h>
25#include <getopt.h>
ada45c78
LP
26#include <fcntl.h>
27#include <unistd.h>
5de0409e
ZJS
28
29#include <systemd/sd-journal.h>
30
31#include "build.h"
32#include "set.h"
33#include "util.h"
34#include "log.h"
35#include "path-util.h"
36#include "pager.h"
37
38static enum {
39 ACTION_NONE,
40 ACTION_LIST,
41 ACTION_DUMP,
ada45c78 42 ACTION_GDB,
5de0409e
ZJS
43} arg_action = ACTION_LIST;
44
ccc40358
LP
45static Set *matches = NULL;
46static FILE* output = NULL;
4f76ae1b 47static char* field = NULL;
5de0409e 48
ccc40358 49static int arg_no_pager = false;
9a340880 50static int arg_no_legend = false;
5de0409e
ZJS
51
52static Set *new_matches(void) {
53 Set *set;
54 char *tmp;
55 int r;
56
57 set = set_new(trivial_hash_func, trivial_compare_func);
58 if (!set) {
59 log_oom();
60 return NULL;
61 }
62
63 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
64 if (!tmp) {
65 log_oom();
4a207bb2 66 set_free(set);
5de0409e
ZJS
67 return NULL;
68 }
69
70 r = set_put(set, tmp);
71 if (r < 0) {
72 log_error("failed to add to set: %s", strerror(-r));
73 free(tmp);
4a207bb2 74 set_free(set);
5de0409e
ZJS
75 return NULL;
76 }
77
78 return set;
79}
80
81static int help(void) {
82 printf("%s [OPTIONS...] [MATCHES...]\n\n"
83 "List or retrieve coredumps from the journal.\n\n"
84 "Flags:\n"
85 " -o --output=FILE Write output to FILE\n"
86 " --no-pager Do not pipe output into a pager\n"
87
88 "Commands:\n"
89 " -h --help Show this help\n"
90 " --version Print version string\n"
ef216ca3 91 " -F --field=FIELD List all values a certain field takes\n"
584f5872 92 " gdb Start gdb for the first matching coredump\n"
5de0409e
ZJS
93 " list List available coredumps\n"
94 " dump PID Print coredump to stdout\n"
95 " dump PATH Print coredump to stdout\n"
96 , program_invocation_short_name);
97
98 return 0;
99}
100
101static int add_match(Set *set, const char *match) {
102 int r = -ENOMEM;
103 unsigned pid;
104 const char* prefix;
105 char *pattern = NULL;
106 char _cleanup_free_ *p = NULL;
107
108 if (strchr(match, '='))
109 prefix = "";
110 else if (strchr(match, '/')) {
111 p = path_make_absolute_cwd(match);
112 if (!p)
113 goto fail;
114
115 match = p;
116 prefix = "COREDUMP_EXE=";
117 }
118 else if (safe_atou(match, &pid) == 0)
119 prefix = "COREDUMP_PID=";
120 else
121 prefix = "COREDUMP_COMM=";
122
123 pattern = strjoin(prefix, match, NULL);
124 if (!pattern)
125 goto fail;
126
127 r = set_put(set, pattern);
128 if (r < 0) {
129 log_error("failed to add pattern '%s': %s",
130 pattern, strerror(-r));
131 goto fail;
132 }
133 log_debug("Added pattern: %s", pattern);
134
135 return 0;
136fail:
137 free(pattern);
138 log_error("failed to add match: %s", strerror(-r));
139 return r;
140}
141
142static int parse_argv(int argc, char *argv[]) {
143 enum {
144 ARG_VERSION = 0x100,
145 ARG_NO_PAGER,
9a340880 146 ARG_NO_LEGEND,
5de0409e
ZJS
147 };
148
149 int r, c;
150
151 static const struct option options[] = {
57ce4bd4
ZJS
152 { "help", no_argument, NULL, 'h' },
153 { "version" , no_argument, NULL, ARG_VERSION },
154 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
9a340880 155 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
57ce4bd4 156 { "output", required_argument, NULL, 'o' },
4f76ae1b 157 { "field", required_argument, NULL, 'F' },
57ce4bd4 158 { NULL, 0, NULL, 0 }
5de0409e
ZJS
159 };
160
161 assert(argc >= 0);
162 assert(argv);
163
4f76ae1b 164 while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
5de0409e
ZJS
165 switch(c) {
166 case 'h':
167 help();
168 arg_action = ACTION_NONE;
169 return 0;
170
171 case ARG_VERSION:
172 puts(PACKAGE_STRING);
173 puts(DISTRIBUTION);
174 puts(SYSTEMD_FEATURES);
175 arg_action = ACTION_NONE;
176 return 0;
177
178 case ARG_NO_PAGER:
179 arg_no_pager = true;
180 break;
181
9a340880
ZJS
182 case ARG_NO_LEGEND:
183 arg_no_legend = true;
184 break;
185
5de0409e
ZJS
186 case 'o':
187 if (output) {
188 log_error("cannot set output more than once");
189 return -EINVAL;
190 }
191
192 output = fopen(optarg, "we");
193 if (!output) {
194 log_error("writing to '%s': %m", optarg);
195 return -errno;
196 }
197
198 break;
57ce4bd4 199
4f76ae1b
ZJS
200 case 'F':
201 if (field) {
202 log_error("cannot use --field/-F more than once");
203 return -EINVAL;
204 }
205
206 field = optarg;
207 break;
208
57ce4bd4
ZJS
209 case '?':
210 return -EINVAL;
211
5de0409e
ZJS
212 default:
213 log_error("Unknown option code %c", c);
214 return -EINVAL;
215 }
216
217 if (optind < argc) {
218 const char *cmd = argv[optind++];
219 if(streq(cmd, "list"))
220 arg_action = ACTION_LIST;
221 else if (streq(cmd, "dump"))
222 arg_action = ACTION_DUMP;
ada45c78
LP
223 else if (streq(cmd, "gdb"))
224 arg_action = ACTION_GDB;
5de0409e
ZJS
225 else {
226 log_error("Unknown action '%s'", cmd);
227 return -EINVAL;
228 }
229 }
230
4f76ae1b
ZJS
231 if (field && arg_action != ACTION_LIST) {
232 log_error("Option --field/-F only makes sense with list");
233 return -EINVAL;
234 }
235
5de0409e
ZJS
236 while (optind < argc) {
237 r = add_match(matches, argv[optind]);
238 if (r != 0)
239 return r;
240 optind++;
241 }
242
243 return 0;
244}
245
8bc8ab83
LP
246static int retrieve(const void *data,
247 size_t len,
248 const char *name,
249 const char **var) {
5de0409e 250
4f76ae1b 251 size_t ident;
8bc8ab83 252
4f76ae1b 253 ident = strlen(name) + 1; /* name + "=" */
8bc8ab83 254
4f76ae1b 255 if (len < ident)
8bc8ab83 256 return 0;
5de0409e 257
4f76ae1b 258 if (memcmp(data, name, ident - 1) != 0)
8bc8ab83
LP
259 return 0;
260
4f76ae1b 261 if (((const char*) data)[ident - 1] != '=')
8bc8ab83 262 return 0;
5de0409e 263
4f76ae1b 264 *var = strndup((const char*)data + ident, len - ident);
5de0409e
ZJS
265 if (!var)
266 return log_oom();
267
268 return 0;
269}
270
4f76ae1b
ZJS
271static void print_field(FILE* file, sd_journal *j) {
272 const char _cleanup_free_ *value = NULL;
273 const void *d;
274 size_t l;
275
276 assert(field);
277
278 SD_JOURNAL_FOREACH_DATA(j, d, l)
279 retrieve(d, l, field, &value);
280 if (value)
281 fprintf(file, "%s\n", value);
282}
283
9a340880 284static int print_entry(FILE* file, sd_journal *j, int had_legend) {
5de0409e
ZJS
285 const char _cleanup_free_
286 *pid = NULL, *uid = NULL, *gid = NULL,
287 *sgnl = NULL, *exe = NULL;
8bc8ab83
LP
288 const void *d;
289 size_t l;
684341b0
LP
290 usec_t t;
291 char buf[FORMAT_TIMESTAMP_MAX];
292 int r;
8bc8ab83
LP
293
294 SD_JOURNAL_FOREACH_DATA(j, d, l) {
295 retrieve(d, l, "COREDUMP_PID", &pid);
296 retrieve(d, l, "COREDUMP_PID", &pid);
297 retrieve(d, l, "COREDUMP_UID", &uid);
298 retrieve(d, l, "COREDUMP_GID", &gid);
299 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
300 retrieve(d, l, "COREDUMP_EXE", &exe);
301 if (!exe)
302 retrieve(d, l, "COREDUMP_COMM", &exe);
303 if (!exe)
304 retrieve(d, l, "COREDUMP_CMDLINE", &exe);
305 }
5de0409e
ZJS
306
307 if (!pid && !uid && !gid && !sgnl && !exe) {
684341b0
LP
308 log_warning("Empty coredump log entry");
309 return -EINVAL;
310 }
311
312 r = sd_journal_get_realtime_usec(j, &t);
313 if (r < 0) {
314 log_error("Failed to get realtime timestamp: %s", strerror(-r));
315 return r;
5de0409e
ZJS
316 }
317
684341b0
LP
318 format_timestamp(buf, sizeof(buf), t);
319
9a340880 320 if (!had_legend && !arg_no_legend)
684341b0
LP
321 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
322 FORMAT_TIMESTAMP_MAX-1, "TIME",
5de0409e
ZJS
323 6, "PID",
324 5, "UID",
325 5, "GID",
684341b0
LP
326 3, "SIG",
327 "EXE");
5de0409e 328
684341b0
LP
329 fprintf(file, "%*s %*s %*s %*s %*s %s\n",
330 FORMAT_TIMESTAMP_MAX-1, buf,
5de0409e
ZJS
331 6, pid,
332 5, uid,
333 5, gid,
334 3, sgnl,
335 exe);
684341b0
LP
336
337 return 0;
5de0409e
ZJS
338}
339
340static int dump_list(sd_journal *j) {
341 int found = 0;
342
343 assert(j);
344
4f76ae1b
ZJS
345 SD_JOURNAL_FOREACH(j) {
346 if (field)
347 print_field(stdout, j);
348 else
349 print_entry(stdout, j, found++);
350 }
5de0409e 351
4f76ae1b 352 if (!field && !found) {
684341b0 353 log_notice("No coredumps found");
5de0409e
ZJS
354 return -ESRCH;
355 }
356
357 return 0;
358}
359
ada45c78 360static int focus(sd_journal *j) {
5de0409e
ZJS
361 int r;
362
5de0409e
ZJS
363 r = sd_journal_seek_tail(j);
364 if (r == 0)
365 r = sd_journal_previous(j);
366 if (r < 0) {
367 log_error("Failed to search journal: %s", strerror(-r));
368 return r;
369 }
8bc8ab83
LP
370 if (r == 0) {
371 log_error("No match found");
372 return -ESRCH;
373 }
ada45c78
LP
374 return r;
375}
5de0409e 376
ada45c78
LP
377static int dump_core(sd_journal* j) {
378 const void *data;
379 size_t len, ret;
380 int r;
381
382 assert(j);
383
384 r = focus(j);
385 if (r < 0)
5de0409e 386 return r;
5de0409e
ZJS
387
388 print_entry(output ? stdout : stderr, j, false);
389
390 if (on_tty() && !output) {
391 log_error("Refusing to dump core to tty");
392 return -ENOTTY;
393 }
394
ada45c78
LP
395 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
396 if (r < 0) {
397 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
398 return r;
399 }
400
5de0409e 401 assert(len >= 9);
ada45c78
LP
402 data = (const uint8_t*) data + 9;
403 len -= 9;
5de0409e 404
ada45c78 405 ret = fwrite(data, len, 1, output ? output : stdout);
5de0409e
ZJS
406 if (ret != 1) {
407 log_error("dumping coredump: %m (%zu)", ret);
408 return -errno;
409 }
410
411 r = sd_journal_previous(j);
412 if (r >= 0)
413 log_warning("More than one entry matches, ignoring rest.\n");
414
415 return 0;
416}
417
ada45c78
LP
418static int run_gdb(sd_journal *j) {
419 char path[] = "/var/tmp/coredump-XXXXXX";
420 const void *data;
421 size_t len;
422 ssize_t sz;
423 pid_t pid;
424 _cleanup_free_ char *exe = NULL;
425 int r;
426 _cleanup_close_ int fd = -1;
427 siginfo_t st;
428
429 assert(j);
430
431 r = focus(j);
432 if (r < 0)
433 return r;
434
435 print_entry(stdout, j, false);
436
437 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
438 if (r < 0) {
439 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
440 return r;
441 }
442
443 assert(len >= 13);
444 data = (const uint8_t*) data + 13;
445 len -= 13;
446
447 exe = strndup(data, len);
448 if (!exe)
449 return log_oom();
450
451 if (endswith(exe, " (deleted)")) {
452 log_error("Binary already deleted.");
453 return -ENOENT;
454 }
455
456 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
457 if (r < 0) {
458 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
459 return r;
460 }
461
462 assert(len >= 9);
463 data = (const uint8_t*) data + 9;
464 len -= 9;
465
466 fd = mkostemp(path, O_WRONLY);
467 if (fd < 0) {
468 log_error("Failed to create temporary file: %m");
469 return -errno;
470 }
471
472 sz = write(fd, data, len);
473 if (sz < 0) {
474 log_error("Failed to write temporary file: %s", strerror(errno));
475 r = -errno;
476 goto finish;
477 }
478 if (sz != (ssize_t) len) {
479 log_error("Short write to temporary file.");
480 r = -EIO;
481 goto finish;
482 }
483
484 close_nointr_nofail(fd);
485 fd = -1;
486
487 pid = fork();
488 if (pid < 0) {
489 log_error("Failed to fork(): %m");
490 r = -errno;
491 goto finish;
492 }
493 if (pid == 0) {
494 execlp("gdb", "gdb", exe, path, NULL);
495 log_error("Failed to invoke gdb: %m");
496 _exit(1);
497 }
498
499 r = wait_for_terminate(pid, &st);
500 if (r < 0) {
501 log_error("Failed to wait for gdb: %m");
502 goto finish;
503 }
504
505 r = st.si_code == CLD_EXITED ? st.si_status : 255;
506
507finish:
508 unlink(path);
509 return r;
510}
511
5de0409e
ZJS
512int main(int argc, char *argv[]) {
513 sd_journal *j = NULL;
514 const char* match;
515 Iterator it;
516 int r = 0;
517
a9cdc94f 518 setlocale(LC_ALL, "");
5de0409e
ZJS
519 log_parse_environment();
520 log_open();
521
522 matches = new_matches();
2fb7a5ce
ZJS
523 if (!matches) {
524 r = -ENOMEM;
5de0409e 525 goto end;
2fb7a5ce 526 }
5de0409e 527
2fb7a5ce
ZJS
528 r = parse_argv(argc, argv);
529 if (r < 0)
5de0409e
ZJS
530 goto end;
531
532 if (arg_action == ACTION_NONE)
533 goto end;
534
535 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
536 if (r < 0) {
537 log_error("Failed to open journal: %s", strerror(-r));
538 goto end;
539 }
540
541 SET_FOREACH(match, matches, it) {
5de0409e
ZJS
542 r = sd_journal_add_match(j, match, strlen(match));
543 if (r != 0) {
544 log_error("Failed to add match '%s': %s",
545 match, strerror(-r));
546 goto end;
547 }
548 }
549
550 switch(arg_action) {
ada45c78 551
5de0409e
ZJS
552 case ACTION_LIST:
553 if (!arg_no_pager)
554 pager_open();
555
556 r = dump_list(j);
557 break;
ada45c78 558
5de0409e
ZJS
559 case ACTION_DUMP:
560 r = dump_core(j);
561 break;
ada45c78
LP
562
563 case ACTION_GDB:
564 r = run_gdb(j);
565 break;
566
567 default:
5de0409e
ZJS
568 assert_not_reached("Shouldn't be here");
569 }
570
571end:
572 if (j)
573 sd_journal_close(j);
574
575 set_free_free(matches);
576
577 pager_close();
578
579 if (output)
580 fclose(output);
581
ada45c78 582 return r >= 0 ? r : EXIT_FAILURE;
5de0409e 583}