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