]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
Revert "coredumpctl: in case of error free pattern after print"
[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_consume(set, pattern);
130 if (r < 0) {
131 log_error("Failed to add pattern '%s': %s",
132 pattern, strerror(-r));
133 goto fail;
134 }
135
136 return 0;
137 fail:
138 log_error("Failed to add match: %s", strerror(-r));
139 return r;
140 }
141
142 static int parse_argv(int argc, char *argv[], Set *matches) {
143 enum {
144 ARG_VERSION = 0x100,
145 ARG_NO_PAGER,
146 ARG_NO_LEGEND,
147 };
148
149 int r, c;
150
151 static const struct option options[] = {
152 { "help", no_argument, NULL, 'h' },
153 { "version" , no_argument, NULL, ARG_VERSION },
154 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
155 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
156 { "output", required_argument, NULL, 'o' },
157 { "field", required_argument, NULL, 'F' },
158 {}
159 };
160
161 assert(argc >= 0);
162 assert(argv);
163
164 while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
165 switch(c) {
166
167 case 'h':
168 arg_action = ACTION_NONE;
169 return help();
170
171 case ARG_VERSION:
172 arg_action = ACTION_NONE;
173 puts(PACKAGE_STRING);
174 puts(SYSTEMD_FEATURES);
175 return 0;
176
177 case ARG_NO_PAGER:
178 arg_no_pager = true;
179 break;
180
181 case ARG_NO_LEGEND:
182 arg_no_legend = true;
183 break;
184
185 case 'o':
186 if (output) {
187 log_error("cannot set output more than once");
188 return -EINVAL;
189 }
190
191 output = fopen(optarg, "we");
192 if (!output) {
193 log_error("writing to '%s': %m", optarg);
194 return -errno;
195 }
196
197 break;
198
199 case 'F':
200 if (field) {
201 log_error("cannot use --field/-F more than once");
202 return -EINVAL;
203 }
204
205 field = optarg;
206 break;
207
208 case '?':
209 return -EINVAL;
210
211 default:
212 assert_not_reached("Unhandled option");
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.");
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: %m");
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 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 }