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