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