]>
Commit | Line | Data |
---|---|---|
5de0409e ZJS |
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 | ||
a9cdc94f | 22 | #include <locale.h> |
5de0409e ZJS |
23 | #include <stdio.h> |
24 | #include <string.h> | |
25 | #include <getopt.h> | |
ada45c78 LP |
26 | #include <fcntl.h> |
27 | #include <unistd.h> | |
5de0409e ZJS |
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, | |
ada45c78 | 42 | ACTION_GDB, |
5de0409e ZJS |
43 | } arg_action = ACTION_LIST; |
44 | ||
ccc40358 LP |
45 | static Set *matches = NULL; |
46 | static FILE* output = NULL; | |
4f76ae1b | 47 | static char* field = NULL; |
5de0409e | 48 | |
ccc40358 | 49 | static int arg_no_pager = false; |
9a340880 | 50 | static int arg_no_legend = false; |
5de0409e ZJS |
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(); | |
4a207bb2 | 66 | set_free(set); |
5de0409e ZJS |
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); | |
4a207bb2 | 74 | set_free(set); |
5de0409e ZJS |
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" | |
ef216ca3 | 91 | " -F --field=FIELD List all values a certain field takes\n" |
584f5872 | 92 | " gdb Start gdb for the first matching coredump\n" |
5de0409e ZJS |
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, | |
9a340880 | 146 | ARG_NO_LEGEND, |
5de0409e ZJS |
147 | }; |
148 | ||
149 | int r, c; | |
150 | ||
151 | static const struct option options[] = { | |
57ce4bd4 ZJS |
152 | { "help", no_argument, NULL, 'h' }, |
153 | { "version" , no_argument, NULL, ARG_VERSION }, | |
154 | { "no-pager", no_argument, NULL, ARG_NO_PAGER }, | |
9a340880 | 155 | { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, |
57ce4bd4 | 156 | { "output", required_argument, NULL, 'o' }, |
4f76ae1b | 157 | { "field", required_argument, NULL, 'F' }, |
57ce4bd4 | 158 | { NULL, 0, NULL, 0 } |
5de0409e ZJS |
159 | }; |
160 | ||
161 | assert(argc >= 0); | |
162 | assert(argv); | |
163 | ||
4f76ae1b | 164 | while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0) |
5de0409e ZJS |
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 | ||
9a340880 ZJS |
182 | case ARG_NO_LEGEND: |
183 | arg_no_legend = true; | |
184 | break; | |
185 | ||
5de0409e ZJS |
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; | |
57ce4bd4 | 199 | |
4f76ae1b ZJS |
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 | ||
57ce4bd4 ZJS |
209 | case '?': |
210 | return -EINVAL; | |
211 | ||
5de0409e ZJS |
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; | |
ada45c78 LP |
223 | else if (streq(cmd, "gdb")) |
224 | arg_action = ACTION_GDB; | |
5de0409e ZJS |
225 | else { |
226 | log_error("Unknown action '%s'", cmd); | |
227 | return -EINVAL; | |
228 | } | |
229 | } | |
230 | ||
4f76ae1b ZJS |
231 | if (field && arg_action != ACTION_LIST) { |
232 | log_error("Option --field/-F only makes sense with list"); | |
233 | return -EINVAL; | |
234 | } | |
235 | ||
5de0409e ZJS |
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 | ||
8bc8ab83 LP |
246 | static int retrieve(const void *data, |
247 | size_t len, | |
248 | const char *name, | |
249 | const char **var) { | |
5de0409e | 250 | |
4f76ae1b | 251 | size_t ident; |
8bc8ab83 | 252 | |
4f76ae1b | 253 | ident = strlen(name) + 1; /* name + "=" */ |
8bc8ab83 | 254 | |
4f76ae1b | 255 | if (len < ident) |
8bc8ab83 | 256 | return 0; |
5de0409e | 257 | |
4f76ae1b | 258 | if (memcmp(data, name, ident - 1) != 0) |
8bc8ab83 LP |
259 | return 0; |
260 | ||
4f76ae1b | 261 | if (((const char*) data)[ident - 1] != '=') |
8bc8ab83 | 262 | return 0; |
5de0409e | 263 | |
4f76ae1b | 264 | *var = strndup((const char*)data + ident, len - ident); |
5de0409e ZJS |
265 | if (!var) |
266 | return log_oom(); | |
267 | ||
268 | return 0; | |
269 | } | |
270 | ||
4f76ae1b ZJS |
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 | ||
9a340880 | 284 | static int print_entry(FILE* file, sd_journal *j, int had_legend) { |
5de0409e ZJS |
285 | const char _cleanup_free_ |
286 | *pid = NULL, *uid = NULL, *gid = NULL, | |
287 | *sgnl = NULL, *exe = NULL; | |
8bc8ab83 LP |
288 | const void *d; |
289 | size_t l; | |
684341b0 LP |
290 | usec_t t; |
291 | char buf[FORMAT_TIMESTAMP_MAX]; | |
292 | int r; | |
8bc8ab83 LP |
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 | } | |
5de0409e ZJS |
306 | |
307 | if (!pid && !uid && !gid && !sgnl && !exe) { | |
684341b0 LP |
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; | |
5de0409e ZJS |
316 | } |
317 | ||
684341b0 LP |
318 | format_timestamp(buf, sizeof(buf), t); |
319 | ||
9a340880 | 320 | if (!had_legend && !arg_no_legend) |
684341b0 LP |
321 | fprintf(file, "%-*s %*s %*s %*s %*s %s\n", |
322 | FORMAT_TIMESTAMP_MAX-1, "TIME", | |
5de0409e ZJS |
323 | 6, "PID", |
324 | 5, "UID", | |
325 | 5, "GID", | |
684341b0 LP |
326 | 3, "SIG", |
327 | "EXE"); | |
5de0409e | 328 | |
684341b0 LP |
329 | fprintf(file, "%*s %*s %*s %*s %*s %s\n", |
330 | FORMAT_TIMESTAMP_MAX-1, buf, | |
5de0409e ZJS |
331 | 6, pid, |
332 | 5, uid, | |
333 | 5, gid, | |
334 | 3, sgnl, | |
335 | exe); | |
684341b0 LP |
336 | |
337 | return 0; | |
5de0409e ZJS |
338 | } |
339 | ||
340 | static int dump_list(sd_journal *j) { | |
341 | int found = 0; | |
342 | ||
343 | assert(j); | |
344 | ||
4f76ae1b ZJS |
345 | SD_JOURNAL_FOREACH(j) { |
346 | if (field) | |
347 | print_field(stdout, j); | |
348 | else | |
349 | print_entry(stdout, j, found++); | |
350 | } | |
5de0409e | 351 | |
4f76ae1b | 352 | if (!field && !found) { |
684341b0 | 353 | log_notice("No coredumps found"); |
5de0409e ZJS |
354 | return -ESRCH; |
355 | } | |
356 | ||
357 | return 0; | |
358 | } | |
359 | ||
ada45c78 | 360 | static int focus(sd_journal *j) { |
5de0409e ZJS |
361 | int r; |
362 | ||
5de0409e ZJS |
363 | r = sd_journal_seek_tail(j); |
364 | if (r == 0) | |
365 | r = sd_journal_previous(j); | |
366 | if (r < 0) { | |
367 | log_error("Failed to search journal: %s", strerror(-r)); | |
368 | return r; | |
369 | } | |
8bc8ab83 LP |
370 | if (r == 0) { |
371 | log_error("No match found"); | |
372 | return -ESRCH; | |
373 | } | |
ada45c78 LP |
374 | return r; |
375 | } | |
5de0409e | 376 | |
ada45c78 LP |
377 | static int dump_core(sd_journal* j) { |
378 | const void *data; | |
379 | size_t len, ret; | |
380 | int r; | |
381 | ||
382 | assert(j); | |
383 | ||
384 | r = focus(j); | |
385 | if (r < 0) | |
5de0409e | 386 | return r; |
5de0409e ZJS |
387 | |
388 | print_entry(output ? stdout : stderr, j, false); | |
389 | ||
390 | if (on_tty() && !output) { | |
391 | log_error("Refusing to dump core to tty"); | |
392 | return -ENOTTY; | |
393 | } | |
394 | ||
ada45c78 LP |
395 | r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len); |
396 | if (r < 0) { | |
397 | log_error("Failed to retrieve COREDUMP field: %s", strerror(-r)); | |
398 | return r; | |
399 | } | |
400 | ||
5de0409e | 401 | assert(len >= 9); |
ada45c78 LP |
402 | data = (const uint8_t*) data + 9; |
403 | len -= 9; | |
5de0409e | 404 | |
ada45c78 | 405 | ret = fwrite(data, len, 1, output ? output : stdout); |
5de0409e ZJS |
406 | if (ret != 1) { |
407 | log_error("dumping coredump: %m (%zu)", ret); | |
408 | return -errno; | |
409 | } | |
410 | ||
411 | r = sd_journal_previous(j); | |
412 | if (r >= 0) | |
413 | log_warning("More than one entry matches, ignoring rest.\n"); | |
414 | ||
415 | return 0; | |
416 | } | |
417 | ||
ada45c78 LP |
418 | static int run_gdb(sd_journal *j) { |
419 | char path[] = "/var/tmp/coredump-XXXXXX"; | |
420 | const void *data; | |
421 | size_t len; | |
422 | ssize_t sz; | |
423 | pid_t pid; | |
424 | _cleanup_free_ char *exe = NULL; | |
425 | int r; | |
426 | _cleanup_close_ int fd = -1; | |
427 | siginfo_t st; | |
428 | ||
429 | assert(j); | |
430 | ||
431 | r = focus(j); | |
432 | if (r < 0) | |
433 | return r; | |
434 | ||
435 | print_entry(stdout, j, false); | |
436 | ||
437 | r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len); | |
438 | if (r < 0) { | |
439 | log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r)); | |
440 | return r; | |
441 | } | |
442 | ||
443 | assert(len >= 13); | |
444 | data = (const uint8_t*) data + 13; | |
445 | len -= 13; | |
446 | ||
447 | exe = strndup(data, len); | |
448 | if (!exe) | |
449 | return log_oom(); | |
450 | ||
451 | if (endswith(exe, " (deleted)")) { | |
452 | log_error("Binary already deleted."); | |
453 | return -ENOENT; | |
454 | } | |
455 | ||
456 | r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len); | |
457 | if (r < 0) { | |
458 | log_error("Failed to retrieve COREDUMP field: %s", strerror(-r)); | |
459 | return r; | |
460 | } | |
461 | ||
462 | assert(len >= 9); | |
463 | data = (const uint8_t*) data + 9; | |
464 | len -= 9; | |
465 | ||
466 | fd = mkostemp(path, O_WRONLY); | |
467 | if (fd < 0) { | |
468 | log_error("Failed to create temporary file: %m"); | |
469 | return -errno; | |
470 | } | |
471 | ||
472 | sz = write(fd, data, len); | |
473 | if (sz < 0) { | |
474 | log_error("Failed to write temporary file: %s", strerror(errno)); | |
475 | r = -errno; | |
476 | goto finish; | |
477 | } | |
478 | if (sz != (ssize_t) len) { | |
479 | log_error("Short write to temporary file."); | |
480 | r = -EIO; | |
481 | goto finish; | |
482 | } | |
483 | ||
484 | close_nointr_nofail(fd); | |
485 | fd = -1; | |
486 | ||
487 | pid = fork(); | |
488 | if (pid < 0) { | |
489 | log_error("Failed to fork(): %m"); | |
490 | r = -errno; | |
491 | goto finish; | |
492 | } | |
493 | if (pid == 0) { | |
494 | execlp("gdb", "gdb", exe, path, NULL); | |
495 | log_error("Failed to invoke gdb: %m"); | |
496 | _exit(1); | |
497 | } | |
498 | ||
499 | r = wait_for_terminate(pid, &st); | |
500 | if (r < 0) { | |
501 | log_error("Failed to wait for gdb: %m"); | |
502 | goto finish; | |
503 | } | |
504 | ||
505 | r = st.si_code == CLD_EXITED ? st.si_status : 255; | |
506 | ||
507 | finish: | |
508 | unlink(path); | |
509 | return r; | |
510 | } | |
511 | ||
5de0409e ZJS |
512 | int main(int argc, char *argv[]) { |
513 | sd_journal *j = NULL; | |
514 | const char* match; | |
515 | Iterator it; | |
516 | int r = 0; | |
517 | ||
a9cdc94f | 518 | setlocale(LC_ALL, ""); |
5de0409e ZJS |
519 | log_parse_environment(); |
520 | log_open(); | |
521 | ||
522 | matches = new_matches(); | |
2fb7a5ce ZJS |
523 | if (!matches) { |
524 | r = -ENOMEM; | |
5de0409e | 525 | goto end; |
2fb7a5ce | 526 | } |
5de0409e | 527 | |
2fb7a5ce ZJS |
528 | r = parse_argv(argc, argv); |
529 | if (r < 0) | |
5de0409e ZJS |
530 | goto end; |
531 | ||
532 | if (arg_action == ACTION_NONE) | |
533 | goto end; | |
534 | ||
535 | r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); | |
536 | if (r < 0) { | |
537 | log_error("Failed to open journal: %s", strerror(-r)); | |
538 | goto end; | |
539 | } | |
540 | ||
541 | SET_FOREACH(match, matches, it) { | |
5de0409e ZJS |
542 | r = sd_journal_add_match(j, match, strlen(match)); |
543 | if (r != 0) { | |
544 | log_error("Failed to add match '%s': %s", | |
545 | match, strerror(-r)); | |
546 | goto end; | |
547 | } | |
548 | } | |
549 | ||
550 | switch(arg_action) { | |
ada45c78 | 551 | |
5de0409e ZJS |
552 | case ACTION_LIST: |
553 | if (!arg_no_pager) | |
554 | pager_open(); | |
555 | ||
556 | r = dump_list(j); | |
557 | break; | |
ada45c78 | 558 | |
5de0409e ZJS |
559 | case ACTION_DUMP: |
560 | r = dump_core(j); | |
561 | break; | |
ada45c78 LP |
562 | |
563 | case ACTION_GDB: | |
564 | r = run_gdb(j); | |
565 | break; | |
566 | ||
567 | default: | |
5de0409e ZJS |
568 | assert_not_reached("Shouldn't be here"); |
569 | } | |
570 | ||
571 | end: | |
572 | if (j) | |
573 | sd_journal_close(j); | |
574 | ||
575 | set_free_free(matches); | |
576 | ||
577 | pager_close(); | |
578 | ||
579 | if (output) | |
580 | fclose(output); | |
581 | ||
ada45c78 | 582 | return r >= 0 ? r : EXIT_FAILURE; |
5de0409e | 583 | } |