]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
journal: by default do not decompress dat objects larger than 64K
[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
38 static enum {
39 ACTION_NONE,
40 ACTION_LIST,
41 ACTION_DUMP,
42 ACTION_GDB,
43 } arg_action = ACTION_LIST;
44
45 static Set *matches = NULL;
46 static FILE* output = NULL;
47 static char* field = NULL;
48
49 static int arg_no_pager = false;
50 static int arg_no_legend = false;
51
52 static Set *new_matches(void) {
53 Set *set;
54 char *tmp;
55 int r;
56
57 set = set_new(trivial_hash_func, trivial_compare_func);
58 if (!set) {
59 log_oom();
60 return NULL;
61 }
62
63 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
64 if (!tmp) {
65 log_oom();
66 set_free(set);
67 return NULL;
68 }
69
70 r = set_put(set, tmp);
71 if (r < 0) {
72 log_error("failed to add to set: %s", strerror(-r));
73 free(tmp);
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 char _cleanup_free_ *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 r = set_put(set, pattern);
128 if (r < 0) {
129 log_error("failed to add pattern '%s': %s",
130 pattern, strerror(-r));
131 goto fail;
132 }
133 log_debug("Added pattern: %s", pattern);
134
135 return 0;
136 fail:
137 free(pattern);
138 log_error("failed to add match: %s", strerror(-r));
139 return r;
140 }
141
142 static int parse_argv(int argc, char *argv[]) {
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 { NULL, 0, NULL, 0 }
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 case 'h':
167 help();
168 arg_action = ACTION_NONE;
169 return 0;
170
171 case ARG_VERSION:
172 puts(PACKAGE_STRING);
173 puts(DISTRIBUTION);
174 puts(SYSTEMD_FEATURES);
175 arg_action = ACTION_NONE;
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 log_error("Unknown option code %c", c);
214 return -EINVAL;
215 }
216
217 if (optind < argc) {
218 const char *cmd = argv[optind++];
219 if(streq(cmd, "list"))
220 arg_action = ACTION_LIST;
221 else if (streq(cmd, "dump"))
222 arg_action = ACTION_DUMP;
223 else if (streq(cmd, "gdb"))
224 arg_action = ACTION_GDB;
225 else {
226 log_error("Unknown action '%s'", cmd);
227 return -EINVAL;
228 }
229 }
230
231 if (field && arg_action != ACTION_LIST) {
232 log_error("Option --field/-F only makes sense with list");
233 return -EINVAL;
234 }
235
236 while (optind < argc) {
237 r = add_match(matches, argv[optind]);
238 if (r != 0)
239 return r;
240 optind++;
241 }
242
243 return 0;
244 }
245
246 static int retrieve(const void *data,
247 size_t len,
248 const char *name,
249 const char **var) {
250
251 size_t ident;
252
253 ident = strlen(name) + 1; /* name + "=" */
254
255 if (len < ident)
256 return 0;
257
258 if (memcmp(data, name, ident - 1) != 0)
259 return 0;
260
261 if (((const char*) data)[ident - 1] != '=')
262 return 0;
263
264 *var = strndup((const char*)data + ident, len - ident);
265 if (!var)
266 return log_oom();
267
268 return 0;
269 }
270
271 static void print_field(FILE* file, sd_journal *j) {
272 const char _cleanup_free_ *value = NULL;
273 const void *d;
274 size_t l;
275
276 assert(field);
277
278 SD_JOURNAL_FOREACH_DATA(j, d, l)
279 retrieve(d, l, field, &value);
280 if (value)
281 fprintf(file, "%s\n", value);
282 }
283
284 static int print_entry(FILE* file, sd_journal *j, int had_legend) {
285 const char _cleanup_free_
286 *pid = NULL, *uid = NULL, *gid = NULL,
287 *sgnl = NULL, *exe = NULL;
288 const void *d;
289 size_t l;
290 usec_t t;
291 char buf[FORMAT_TIMESTAMP_MAX];
292 int r;
293
294 SD_JOURNAL_FOREACH_DATA(j, d, l) {
295 retrieve(d, l, "COREDUMP_PID", &pid);
296 retrieve(d, l, "COREDUMP_PID", &pid);
297 retrieve(d, l, "COREDUMP_UID", &uid);
298 retrieve(d, l, "COREDUMP_GID", &gid);
299 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
300 retrieve(d, l, "COREDUMP_EXE", &exe);
301 if (!exe)
302 retrieve(d, l, "COREDUMP_COMM", &exe);
303 if (!exe)
304 retrieve(d, l, "COREDUMP_CMDLINE", &exe);
305 }
306
307 if (!pid && !uid && !gid && !sgnl && !exe) {
308 log_warning("Empty coredump log entry");
309 return -EINVAL;
310 }
311
312 r = sd_journal_get_realtime_usec(j, &t);
313 if (r < 0) {
314 log_error("Failed to get realtime timestamp: %s", strerror(-r));
315 return r;
316 }
317
318 format_timestamp(buf, sizeof(buf), t);
319
320 if (!had_legend && !arg_no_legend)
321 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
322 FORMAT_TIMESTAMP_MAX-1, "TIME",
323 6, "PID",
324 5, "UID",
325 5, "GID",
326 3, "SIG",
327 "EXE");
328
329 fprintf(file, "%*s %*s %*s %*s %*s %s\n",
330 FORMAT_TIMESTAMP_MAX-1, buf,
331 6, pid,
332 5, uid,
333 5, gid,
334 3, sgnl,
335 exe);
336
337 return 0;
338 }
339
340 static int dump_list(sd_journal *j) {
341 int found = 0;
342
343 assert(j);
344
345 /* The coredumps are likely to compressed, and for just
346 * listing them we don#t need to decompress them, so let's
347 * pick a fairly low data threshold here */
348 sd_journal_set_data_threshold(j, 4096);
349
350 SD_JOURNAL_FOREACH(j) {
351 if (field)
352 print_field(stdout, j);
353 else
354 print_entry(stdout, j, found++);
355 }
356
357 if (!field && !found) {
358 log_notice("No coredumps found");
359 return -ESRCH;
360 }
361
362 return 0;
363 }
364
365 static int focus(sd_journal *j) {
366 int r;
367
368 r = sd_journal_seek_tail(j);
369 if (r == 0)
370 r = sd_journal_previous(j);
371 if (r < 0) {
372 log_error("Failed to search journal: %s", strerror(-r));
373 return r;
374 }
375 if (r == 0) {
376 log_error("No match found");
377 return -ESRCH;
378 }
379 return r;
380 }
381
382 static int dump_core(sd_journal* j) {
383 const void *data;
384 size_t len, ret;
385 int r;
386
387 assert(j);
388
389 /* We want full data, nothing truncated. */
390 sd_journal_set_data_threshold(j, 0);
391
392 r = focus(j);
393 if (r < 0)
394 return r;
395
396 print_entry(output ? stdout : stderr, j, false);
397
398 if (on_tty() && !output) {
399 log_error("Refusing to dump core to tty");
400 return -ENOTTY;
401 }
402
403 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
404 if (r < 0) {
405 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
406 return r;
407 }
408
409 assert(len >= 9);
410 data = (const uint8_t*) data + 9;
411 len -= 9;
412
413 ret = fwrite(data, len, 1, output ? output : stdout);
414 if (ret != 1) {
415 log_error("dumping coredump: %m (%zu)", ret);
416 return -errno;
417 }
418
419 r = sd_journal_previous(j);
420 if (r >= 0)
421 log_warning("More than one entry matches, ignoring rest.\n");
422
423 return 0;
424 }
425
426 static int run_gdb(sd_journal *j) {
427 char path[] = "/var/tmp/coredump-XXXXXX";
428 const void *data;
429 size_t len;
430 ssize_t sz;
431 pid_t pid;
432 _cleanup_free_ char *exe = NULL;
433 int r;
434 _cleanup_close_ int fd = -1;
435 siginfo_t st;
436
437 assert(j);
438
439 sd_journal_set_data_threshold(j, 0);
440
441 r = focus(j);
442 if (r < 0)
443 return r;
444
445 print_entry(stdout, j, false);
446
447 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
448 if (r < 0) {
449 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
450 return r;
451 }
452
453 assert(len >= 13);
454 data = (const uint8_t*) data + 13;
455 len -= 13;
456
457 exe = strndup(data, len);
458 if (!exe)
459 return log_oom();
460
461 if (endswith(exe, " (deleted)")) {
462 log_error("Binary already deleted.");
463 return -ENOENT;
464 }
465
466 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
467 if (r < 0) {
468 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
469 return r;
470 }
471
472 assert(len >= 9);
473 data = (const uint8_t*) data + 9;
474 len -= 9;
475
476 fd = mkostemp(path, O_WRONLY);
477 if (fd < 0) {
478 log_error("Failed to create temporary file: %m");
479 return -errno;
480 }
481
482 sz = write(fd, data, len);
483 if (sz < 0) {
484 log_error("Failed to write temporary file: %s", strerror(errno));
485 r = -errno;
486 goto finish;
487 }
488 if (sz != (ssize_t) len) {
489 log_error("Short write to temporary file.");
490 r = -EIO;
491 goto finish;
492 }
493
494 close_nointr_nofail(fd);
495 fd = -1;
496
497 pid = fork();
498 if (pid < 0) {
499 log_error("Failed to fork(): %m");
500 r = -errno;
501 goto finish;
502 }
503 if (pid == 0) {
504 execlp("gdb", "gdb", exe, path, NULL);
505 log_error("Failed to invoke gdb: %m");
506 _exit(1);
507 }
508
509 r = wait_for_terminate(pid, &st);
510 if (r < 0) {
511 log_error("Failed to wait for gdb: %m");
512 goto finish;
513 }
514
515 r = st.si_code == CLD_EXITED ? st.si_status : 255;
516
517 finish:
518 unlink(path);
519 return r;
520 }
521
522 int main(int argc, char *argv[]) {
523 sd_journal *j = NULL;
524 const char* match;
525 Iterator it;
526 int r = 0;
527
528 setlocale(LC_ALL, "");
529 log_parse_environment();
530 log_open();
531
532 matches = new_matches();
533 if (!matches) {
534 r = -ENOMEM;
535 goto end;
536 }
537
538 r = parse_argv(argc, argv);
539 if (r < 0)
540 goto end;
541
542 if (arg_action == ACTION_NONE)
543 goto end;
544
545 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
546 if (r < 0) {
547 log_error("Failed to open journal: %s", strerror(-r));
548 goto end;
549 }
550
551 SET_FOREACH(match, matches, it) {
552 r = sd_journal_add_match(j, match, strlen(match));
553 if (r != 0) {
554 log_error("Failed to add match '%s': %s",
555 match, strerror(-r));
556 goto end;
557 }
558 }
559
560 switch(arg_action) {
561
562 case ACTION_LIST:
563 if (!arg_no_pager)
564 pager_open();
565
566 r = dump_list(j);
567 break;
568
569 case ACTION_DUMP:
570 r = dump_core(j);
571 break;
572
573 case ACTION_GDB:
574 r = run_gdb(j);
575 break;
576
577 default:
578 assert_not_reached("Shouldn't be here");
579 }
580
581 end:
582 if (j)
583 sd_journal_close(j);
584
585 set_free_free(matches);
586
587 pager_close();
588
589 if (output)
590 fclose(output);
591
592 return r >= 0 ? r : EXIT_FAILURE;
593 }