]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
coredumpctl: add guard to options table
[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
47 static int arg_no_pager = false;
48
49 static Set *new_matches(void) {
50 Set *set;
51 char *tmp;
52 int r;
53
54 set = set_new(trivial_hash_func, trivial_compare_func);
55 if (!set) {
56 log_oom();
57 return NULL;
58 }
59
60 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
61 if (!tmp) {
62 log_oom();
63 set_free(set);
64 return NULL;
65 }
66
67 r = set_put(set, tmp);
68 if (r < 0) {
69 log_error("failed to add to set: %s", strerror(-r));
70 free(tmp);
71 set_free(set);
72 return NULL;
73 }
74
75 return set;
76 }
77
78 static int help(void) {
79 printf("%s [OPTIONS...] [MATCHES...]\n\n"
80 "List or retrieve coredumps from the journal.\n\n"
81 "Flags:\n"
82 " -o --output=FILE Write output to FILE\n"
83 " --no-pager Do not pipe output into a pager\n"
84
85 "Commands:\n"
86 " -h --help Show this help\n"
87 " --version Print version string\n"
88 " list List available coredumps\n"
89 " dump PID Print coredump to stdout\n"
90 " dump PATH Print coredump to stdout\n"
91 , program_invocation_short_name);
92
93 return 0;
94 }
95
96 static int add_match(Set *set, const char *match) {
97 int r = -ENOMEM;
98 unsigned pid;
99 const char* prefix;
100 char *pattern = NULL;
101 char _cleanup_free_ *p = NULL;
102
103 if (strchr(match, '='))
104 prefix = "";
105 else if (strchr(match, '/')) {
106 p = path_make_absolute_cwd(match);
107 if (!p)
108 goto fail;
109
110 match = p;
111 prefix = "COREDUMP_EXE=";
112 }
113 else if (safe_atou(match, &pid) == 0)
114 prefix = "COREDUMP_PID=";
115 else
116 prefix = "COREDUMP_COMM=";
117
118 pattern = strjoin(prefix, match, NULL);
119 if (!pattern)
120 goto fail;
121
122 r = set_put(set, pattern);
123 if (r < 0) {
124 log_error("failed to add pattern '%s': %s",
125 pattern, strerror(-r));
126 goto fail;
127 }
128 log_debug("Added pattern: %s", pattern);
129
130 return 0;
131 fail:
132 free(pattern);
133 log_error("failed to add match: %s", strerror(-r));
134 return r;
135 }
136
137 static int parse_argv(int argc, char *argv[]) {
138 enum {
139 ARG_VERSION = 0x100,
140 ARG_NO_PAGER,
141 };
142
143 int r, c;
144
145 static const struct option options[] = {
146 { "help", no_argument, NULL, 'h' },
147 { "version" , no_argument, NULL, ARG_VERSION },
148 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
149 { "output", required_argument, NULL, 'o' },
150 { NULL, 0, NULL, 0 }
151 };
152
153 assert(argc >= 0);
154 assert(argv);
155
156 while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
157 switch(c) {
158 case 'h':
159 help();
160 arg_action = ACTION_NONE;
161 return 0;
162
163 case ARG_VERSION:
164 puts(PACKAGE_STRING);
165 puts(DISTRIBUTION);
166 puts(SYSTEMD_FEATURES);
167 arg_action = ACTION_NONE;
168 return 0;
169
170 case ARG_NO_PAGER:
171 arg_no_pager = true;
172 break;
173
174 case 'o':
175 if (output) {
176 log_error("cannot set output more than once");
177 return -EINVAL;
178 }
179
180 output = fopen(optarg, "we");
181 if (!output) {
182 log_error("writing to '%s': %m", optarg);
183 return -errno;
184 }
185
186 break;
187
188 case '?':
189 return -EINVAL;
190
191 default:
192 log_error("Unknown option code %c", c);
193 return -EINVAL;
194 }
195
196 if (optind < argc) {
197 const char *cmd = argv[optind++];
198 if(streq(cmd, "list"))
199 arg_action = ACTION_LIST;
200 else if (streq(cmd, "dump"))
201 arg_action = ACTION_DUMP;
202 else if (streq(cmd, "gdb"))
203 arg_action = ACTION_GDB;
204 else {
205 log_error("Unknown action '%s'", cmd);
206 return -EINVAL;
207 }
208 }
209
210 while (optind < argc) {
211 r = add_match(matches, argv[optind]);
212 if (r != 0)
213 return r;
214 optind++;
215 }
216
217 return 0;
218 }
219
220 static int retrieve(const void *data,
221 size_t len,
222 const char *name,
223 const char **var) {
224
225 size_t field;
226
227 field = strlen(name) + 1; /* name + "=" */
228
229 if (len < field)
230 return 0;
231
232 if (memcmp(data, name, field - 1) != 0)
233 return 0;
234
235 if (((const char*) data)[field - 1] != '=')
236 return 0;
237
238 *var = strndup((const char*)data + field, len - field);
239 if (!var)
240 return log_oom();
241
242 return 0;
243 }
244
245 static int print_entry(FILE* file, sd_journal *j, int had_header) {
246 const char _cleanup_free_
247 *pid = NULL, *uid = NULL, *gid = NULL,
248 *sgnl = NULL, *exe = NULL;
249 const void *d;
250 size_t l;
251 usec_t t;
252 char buf[FORMAT_TIMESTAMP_MAX];
253 int r;
254
255 SD_JOURNAL_FOREACH_DATA(j, d, l) {
256 retrieve(d, l, "COREDUMP_PID", &pid);
257 retrieve(d, l, "COREDUMP_PID", &pid);
258 retrieve(d, l, "COREDUMP_UID", &uid);
259 retrieve(d, l, "COREDUMP_GID", &gid);
260 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
261 retrieve(d, l, "COREDUMP_EXE", &exe);
262 if (!exe)
263 retrieve(d, l, "COREDUMP_COMM", &exe);
264 if (!exe)
265 retrieve(d, l, "COREDUMP_CMDLINE", &exe);
266 }
267
268 if (!pid && !uid && !gid && !sgnl && !exe) {
269 log_warning("Empty coredump log entry");
270 return -EINVAL;
271 }
272
273 r = sd_journal_get_realtime_usec(j, &t);
274 if (r < 0) {
275 log_error("Failed to get realtime timestamp: %s", strerror(-r));
276 return r;
277 }
278
279 format_timestamp(buf, sizeof(buf), t);
280
281 if (!had_header)
282 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
283 FORMAT_TIMESTAMP_MAX-1, "TIME",
284 6, "PID",
285 5, "UID",
286 5, "GID",
287 3, "SIG",
288 "EXE");
289
290 fprintf(file, "%*s %*s %*s %*s %*s %s\n",
291 FORMAT_TIMESTAMP_MAX-1, buf,
292 6, pid,
293 5, uid,
294 5, gid,
295 3, sgnl,
296 exe);
297
298 return 0;
299 }
300
301 static int dump_list(sd_journal *j) {
302 int found = 0;
303
304 assert(j);
305
306 SD_JOURNAL_FOREACH(j)
307 print_entry(stdout, j, found++);
308
309 if (!found) {
310 log_notice("No coredumps found");
311 return -ESRCH;
312 }
313
314 return 0;
315 }
316
317 static int focus(sd_journal *j) {
318 int r;
319
320 r = sd_journal_seek_tail(j);
321 if (r == 0)
322 r = sd_journal_previous(j);
323 if (r < 0) {
324 log_error("Failed to search journal: %s", strerror(-r));
325 return r;
326 }
327 if (r == 0) {
328 log_error("No match found");
329 return -ESRCH;
330 }
331 return r;
332 }
333
334 static int dump_core(sd_journal* j) {
335 const void *data;
336 size_t len, ret;
337 int r;
338
339 assert(j);
340
341 r = focus(j);
342 if (r < 0)
343 return r;
344
345 print_entry(output ? stdout : stderr, j, false);
346
347 if (on_tty() && !output) {
348 log_error("Refusing to dump core to tty");
349 return -ENOTTY;
350 }
351
352 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
353 if (r < 0) {
354 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
355 return r;
356 }
357
358 assert(len >= 9);
359 data = (const uint8_t*) data + 9;
360 len -= 9;
361
362 ret = fwrite(data, len, 1, output ? output : stdout);
363 if (ret != 1) {
364 log_error("dumping coredump: %m (%zu)", ret);
365 return -errno;
366 }
367
368 r = sd_journal_previous(j);
369 if (r >= 0)
370 log_warning("More than one entry matches, ignoring rest.\n");
371
372 return 0;
373 }
374
375 static int run_gdb(sd_journal *j) {
376 char path[] = "/var/tmp/coredump-XXXXXX";
377 const void *data;
378 size_t len;
379 ssize_t sz;
380 pid_t pid;
381 _cleanup_free_ char *exe = NULL;
382 int r;
383 _cleanup_close_ int fd = -1;
384 siginfo_t st;
385
386 assert(j);
387
388 r = focus(j);
389 if (r < 0)
390 return r;
391
392 print_entry(stdout, j, false);
393
394 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
395 if (r < 0) {
396 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
397 return r;
398 }
399
400 assert(len >= 13);
401 data = (const uint8_t*) data + 13;
402 len -= 13;
403
404 exe = strndup(data, len);
405 if (!exe)
406 return log_oom();
407
408 if (endswith(exe, " (deleted)")) {
409 log_error("Binary already deleted.");
410 return -ENOENT;
411 }
412
413 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
414 if (r < 0) {
415 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
416 return r;
417 }
418
419 assert(len >= 9);
420 data = (const uint8_t*) data + 9;
421 len -= 9;
422
423 fd = mkostemp(path, O_WRONLY);
424 if (fd < 0) {
425 log_error("Failed to create temporary file: %m");
426 return -errno;
427 }
428
429 sz = write(fd, data, len);
430 if (sz < 0) {
431 log_error("Failed to write temporary file: %s", strerror(errno));
432 r = -errno;
433 goto finish;
434 }
435 if (sz != (ssize_t) len) {
436 log_error("Short write to temporary file.");
437 r = -EIO;
438 goto finish;
439 }
440
441 close_nointr_nofail(fd);
442 fd = -1;
443
444 pid = fork();
445 if (pid < 0) {
446 log_error("Failed to fork(): %m");
447 r = -errno;
448 goto finish;
449 }
450 if (pid == 0) {
451 execlp("gdb", "gdb", exe, path, NULL);
452 log_error("Failed to invoke gdb: %m");
453 _exit(1);
454 }
455
456 r = wait_for_terminate(pid, &st);
457 if (r < 0) {
458 log_error("Failed to wait for gdb: %m");
459 goto finish;
460 }
461
462 r = st.si_code == CLD_EXITED ? st.si_status : 255;
463
464 finish:
465 unlink(path);
466 return r;
467 }
468
469 int main(int argc, char *argv[]) {
470 sd_journal *j = NULL;
471 const char* match;
472 Iterator it;
473 int r = 0;
474
475 log_parse_environment();
476 log_open();
477
478 matches = new_matches();
479 if (!matches)
480 goto end;
481
482 if (parse_argv(argc, argv))
483 goto end;
484
485 if (arg_action == ACTION_NONE)
486 goto end;
487
488 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
489 if (r < 0) {
490 log_error("Failed to open journal: %s", strerror(-r));
491 goto end;
492 }
493
494 SET_FOREACH(match, matches, it) {
495 r = sd_journal_add_match(j, match, strlen(match));
496 if (r != 0) {
497 log_error("Failed to add match '%s': %s",
498 match, strerror(-r));
499 goto end;
500 }
501 }
502
503 switch(arg_action) {
504
505 case ACTION_LIST:
506 if (!arg_no_pager)
507 pager_open();
508
509 r = dump_list(j);
510 break;
511
512 case ACTION_DUMP:
513 r = dump_core(j);
514 break;
515
516 case ACTION_GDB:
517 r = run_gdb(j);
518 break;
519
520 default:
521 assert_not_reached("Shouldn't be here");
522 }
523
524 end:
525 if (j)
526 sd_journal_close(j);
527
528 set_free_free(matches);
529
530 pager_close();
531
532 if (output)
533 fclose(output);
534
535 return r >= 0 ? r : EXIT_FAILURE;
536 }