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