]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
Add set_consume which always takes ownership
[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
88 "Commands:\n"
89 " -h --help Show this help\n"
90 " --version Print version string\n"
91 " -F --field=FIELD List all values a certain field takes\n"
92 " gdb Start gdb for the first matching coredump\n"
93 " list List available coredumps\n"
94 " dump PID Print coredump to stdout\n"
95 " dump PATH Print coredump to stdout\n"
96 , program_invocation_short_name);
97
98 return 0;
99 }
100
101 static int add_match(Set *set, const char *match) {
102 int r = -ENOMEM;
103 unsigned pid;
104 const char* prefix;
105 char *pattern = NULL;
106 _cleanup_free_ char *p = NULL;
107
108 if (strchr(match, '='))
109 prefix = "";
110 else if (strchr(match, '/')) {
111 p = path_make_absolute_cwd(match);
112 if (!p)
113 goto fail;
114
115 match = p;
116 prefix = "COREDUMP_EXE=";
117 }
118 else if (safe_atou(match, &pid) == 0)
119 prefix = "COREDUMP_PID=";
120 else
121 prefix = "COREDUMP_COMM=";
122
123 pattern = strjoin(prefix, match, NULL);
124 if (!pattern)
125 goto fail;
126
127 log_debug("Adding pattern: %s", pattern);
128 r = set_consume(set, pattern);
129 if (r < 0) {
130 log_error("Failed to add pattern '%s': %s",
131 pattern, strerror(-r));
132 goto fail;
133 }
134
135 return 0;
136 fail:
137 log_error("Failed to add match: %s", strerror(-r));
138 return r;
139 }
140
141 static int parse_argv(int argc, char *argv[], Set *matches) {
142 enum {
143 ARG_VERSION = 0x100,
144 ARG_NO_PAGER,
145 ARG_NO_LEGEND,
146 };
147
148 int r, c;
149
150 static const struct option options[] = {
151 { "help", no_argument, NULL, 'h' },
152 { "version" , no_argument, NULL, ARG_VERSION },
153 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
154 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
155 { "output", required_argument, NULL, 'o' },
156 { "field", required_argument, NULL, 'F' },
157 { NULL, 0, NULL, 0 }
158 };
159
160 assert(argc >= 0);
161 assert(argv);
162
163 while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
164 switch(c) {
165 case 'h':
166 help();
167 arg_action = ACTION_NONE;
168 return 0;
169
170 case ARG_VERSION:
171 puts(PACKAGE_STRING);
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 _cleanup_free_ const char *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 _cleanup_free_ const char
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 /* The coredumps are likely to compressed, and for just
344 * listing them we don#t need to decompress them, so let's
345 * pick a fairly low data threshold here */
346 sd_journal_set_data_threshold(j, 4096);
347
348 SD_JOURNAL_FOREACH(j) {
349 if (field)
350 print_field(stdout, j);
351 else
352 print_entry(stdout, j, found++);
353 }
354
355 if (!field && !found) {
356 log_notice("No coredumps found");
357 return -ESRCH;
358 }
359
360 return 0;
361 }
362
363 static int focus(sd_journal *j) {
364 int r;
365
366 r = sd_journal_seek_tail(j);
367 if (r == 0)
368 r = sd_journal_previous(j);
369 if (r < 0) {
370 log_error("Failed to search journal: %s", strerror(-r));
371 return r;
372 }
373 if (r == 0) {
374 log_error("No match found");
375 return -ESRCH;
376 }
377 return r;
378 }
379
380 static int dump_core(sd_journal* j) {
381 const void *data;
382 size_t len, ret;
383 int r;
384
385 assert(j);
386
387 /* We want full data, nothing truncated. */
388 sd_journal_set_data_threshold(j, 0);
389
390 r = focus(j);
391 if (r < 0)
392 return r;
393
394 print_entry(output ? stdout : stderr, j, false);
395
396 if (on_tty() && !output) {
397 log_error("Refusing to dump core to tty");
398 return -ENOTTY;
399 }
400
401 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
402 if (r < 0) {
403 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
404 return r;
405 }
406
407 assert(len >= 9);
408 data = (const uint8_t*) data + 9;
409 len -= 9;
410
411 ret = fwrite(data, len, 1, output ? output : stdout);
412 if (ret != 1) {
413 log_error("dumping coredump: %m (%zu)", ret);
414 return -errno;
415 }
416
417 r = sd_journal_previous(j);
418 if (r >= 0)
419 log_warning("More than one entry matches, ignoring rest.\n");
420
421 return 0;
422 }
423
424 static int run_gdb(sd_journal *j) {
425 char path[] = "/var/tmp/coredump-XXXXXX";
426 const void *data;
427 size_t len;
428 ssize_t sz;
429 pid_t pid;
430 _cleanup_free_ char *exe = NULL;
431 int r;
432 _cleanup_close_ int fd = -1;
433 siginfo_t st;
434
435 assert(j);
436
437 sd_journal_set_data_threshold(j, 0);
438
439 r = focus(j);
440 if (r < 0)
441 return r;
442
443 print_entry(stdout, j, false);
444
445 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
446 if (r < 0) {
447 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
448 return r;
449 }
450
451 assert(len >= 13);
452 data = (const uint8_t*) data + 13;
453 len -= 13;
454
455 exe = strndup(data, len);
456 if (!exe)
457 return log_oom();
458
459 if (endswith(exe, " (deleted)")) {
460 log_error("Binary already deleted.");
461 return -ENOENT;
462 }
463
464 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
465 if (r < 0) {
466 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
467 return r;
468 }
469
470 assert(len >= 9);
471 data = (const uint8_t*) data + 9;
472 len -= 9;
473
474 fd = mkostemp(path, O_WRONLY);
475 if (fd < 0) {
476 log_error("Failed to create temporary file: %m");
477 return -errno;
478 }
479
480 sz = write(fd, data, len);
481 if (sz < 0) {
482 log_error("Failed to write temporary file: %s", strerror(errno));
483 r = -errno;
484 goto finish;
485 }
486 if (sz != (ssize_t) len) {
487 log_error("Short write to temporary file.");
488 r = -EIO;
489 goto finish;
490 }
491
492 close_nointr_nofail(fd);
493 fd = -1;
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 switch(arg_action) {
560
561 case ACTION_LIST:
562 if (!arg_no_pager)
563 pager_open(false);
564
565 r = dump_list(j);
566 break;
567
568 case ACTION_DUMP:
569 r = dump_core(j);
570 break;
571
572 case ACTION_GDB:
573 r = run_gdb(j);
574 break;
575
576 default:
577 assert_not_reached("Shouldn't be here");
578 }
579
580 end:
581 pager_close();
582
583 if (output)
584 fclose(output);
585
586 return r >= 0 ? r : EXIT_FAILURE;
587 }