]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
macro: rework how we define cleanup macros
[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_put(set, tmp);
72 if (r < 0) {
73 log_error("failed to add to set: %s", strerror(-r));
74 free(tmp);
75 set_free(set);
76 return NULL;
77 }
78
79 return set;
80 }
81
82 static int help(void) {
83 printf("%s [OPTIONS...] [MATCHES...]\n\n"
84 "List or retrieve coredumps from the journal.\n\n"
85 "Flags:\n"
86 " -o --output=FILE Write output to FILE\n"
87 " --no-pager Do not pipe output into a pager\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 char _cleanup_free_ *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 r = set_put(set, pattern);
129 if (r < 0) {
130 log_error("failed to add pattern '%s': %s",
131 pattern, strerror(-r));
132 goto fail;
133 }
134 log_debug("Added pattern: %s", pattern);
135
136 return 0;
137 fail:
138 free(pattern);
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 { NULL, 0, NULL, 0 }
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 case 'h':
168 help();
169 arg_action = ACTION_NONE;
170 return 0;
171
172 case ARG_VERSION:
173 puts(PACKAGE_STRING);
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 _cleanup_journal_close_ *j = NULL;
524 const char* match;
525 Iterator it;
526 int r = 0;
527 Set _cleanup_set_free_free_ *matches = NULL;
528
529 setlocale(LC_ALL, "");
530 log_parse_environment();
531 log_open();
532
533 matches = new_matches();
534 if (!matches) {
535 r = -ENOMEM;
536 goto end;
537 }
538
539 r = parse_argv(argc, argv, matches);
540 if (r < 0)
541 goto end;
542
543 if (arg_action == ACTION_NONE)
544 goto end;
545
546 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
547 if (r < 0) {
548 log_error("Failed to open journal: %s", strerror(-r));
549 goto end;
550 }
551
552 SET_FOREACH(match, matches, it) {
553 r = sd_journal_add_match(j, match, strlen(match));
554 if (r != 0) {
555 log_error("Failed to add match '%s': %s",
556 match, strerror(-r));
557 goto end;
558 }
559 }
560
561 switch(arg_action) {
562
563 case ACTION_LIST:
564 if (!arg_no_pager)
565 pager_open(false);
566
567 r = dump_list(j);
568 break;
569
570 case ACTION_DUMP:
571 r = dump_core(j);
572 break;
573
574 case ACTION_GDB:
575 r = run_gdb(j);
576 break;
577
578 default:
579 assert_not_reached("Shouldn't be here");
580 }
581
582 end:
583 pager_close();
584
585 if (output)
586 fclose(output);
587
588 return r >= 0 ? r : EXIT_FAILURE;
589 }