]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
util: replace close_nointr_nofail() by a more useful safe_close()
[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_put(set, pattern);
130 if (r < 0) {
131 log_error("Failed to add pattern '%s': %s",
132 pattern, strerror(-r));
133 free(pattern);
134 goto fail;
135 }
136
137 return 0;
138 fail:
139 log_error("Failed to add match: %s", strerror(-r));
140 return r;
141 }
142
143 static int parse_argv(int argc, char *argv[], Set *matches) {
144 enum {
145 ARG_VERSION = 0x100,
146 ARG_NO_PAGER,
147 ARG_NO_LEGEND,
148 };
149
150 int r, c;
151
152 static const struct option options[] = {
153 { "help", no_argument, NULL, 'h' },
154 { "version" , no_argument, NULL, ARG_VERSION },
155 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
156 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
157 { "output", required_argument, NULL, 'o' },
158 { "field", required_argument, NULL, 'F' },
159 {}
160 };
161
162 assert(argc >= 0);
163 assert(argv);
164
165 while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
166 switch(c) {
167
168 case 'h':
169 arg_action = ACTION_NONE;
170 return help();
171
172 case ARG_VERSION:
173 arg_action = ACTION_NONE;
174 puts(PACKAGE_STRING);
175 puts(SYSTEMD_FEATURES);
176 return 0;
177
178 case ARG_NO_PAGER:
179 arg_no_pager = true;
180 break;
181
182 case ARG_NO_LEGEND:
183 arg_no_legend = true;
184 break;
185
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;
199
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
209 case '?':
210 return -EINVAL;
211
212 default:
213 assert_not_reached("Unhandled option");
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.");
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_safe(path, O_WRONLY|O_CLOEXEC);
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: %m");
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 fd = safe_close(fd);
494
495 pid = fork();
496 if (pid < 0) {
497 log_error("Failed to fork(): %m");
498 r = -errno;
499 goto finish;
500 }
501 if (pid == 0) {
502 execlp("gdb", "gdb", exe, path, NULL);
503 log_error("Failed to invoke gdb: %m");
504 _exit(1);
505 }
506
507 r = wait_for_terminate(pid, &st);
508 if (r < 0) {
509 log_error("Failed to wait for gdb: %m");
510 goto finish;
511 }
512
513 r = st.si_code == CLD_EXITED ? st.si_status : 255;
514
515 finish:
516 unlink(path);
517 return r;
518 }
519
520 int main(int argc, char *argv[]) {
521 _cleanup_journal_close_ sd_journal*j = NULL;
522 const char* match;
523 Iterator it;
524 int r = 0;
525 _cleanup_set_free_free_ Set *matches = NULL;
526
527 setlocale(LC_ALL, "");
528 log_parse_environment();
529 log_open();
530
531 matches = new_matches();
532 if (!matches) {
533 r = -ENOMEM;
534 goto end;
535 }
536
537 r = parse_argv(argc, argv, matches);
538 if (r < 0)
539 goto end;
540
541 if (arg_action == ACTION_NONE)
542 goto end;
543
544 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
545 if (r < 0) {
546 log_error("Failed to open journal: %s", strerror(-r));
547 goto end;
548 }
549
550 SET_FOREACH(match, matches, it) {
551 r = sd_journal_add_match(j, match, strlen(match));
552 if (r != 0) {
553 log_error("Failed to add match '%s': %s",
554 match, strerror(-r));
555 goto end;
556 }
557 }
558
559 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
560 _cleanup_free_ char *filter;
561
562 filter = journal_make_match_string(j);
563 log_debug("Journal filter: %s", filter);
564 }
565
566 switch(arg_action) {
567
568 case ACTION_LIST:
569 if (!arg_no_pager)
570 pager_open(false);
571
572 r = dump_list(j);
573 break;
574
575 case ACTION_DUMP:
576 r = dump_core(j);
577 break;
578
579 case ACTION_GDB:
580 r = run_gdb(j);
581 break;
582
583 default:
584 assert_not_reached("Shouldn't be here");
585 }
586
587 end:
588 pager_close();
589
590 if (output)
591 fclose(output);
592
593 return r >= 0 ? r : EXIT_FAILURE;
594 }