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