]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
enable localization for common *ctl commands
[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 SD_JOURNAL_FOREACH(j) {
346 if (field)
347 print_field(stdout, j);
348 else
349 print_entry(stdout, j, found++);
350 }
351
352 if (!field && !found) {
353 log_notice("No coredumps found");
354 return -ESRCH;
355 }
356
357 return 0;
358 }
359
360 static int focus(sd_journal *j) {
361 int r;
362
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 }
370 if (r == 0) {
371 log_error("No match found");
372 return -ESRCH;
373 }
374 return r;
375 }
376
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)
386 return r;
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
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
401 assert(len >= 9);
402 data = (const uint8_t*) data + 9;
403 len -= 9;
404
405 ret = fwrite(data, len, 1, output ? output : stdout);
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
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
512 int main(int argc, char *argv[]) {
513 sd_journal *j = NULL;
514 const char* match;
515 Iterator it;
516 int r = 0;
517
518 setlocale(LC_ALL, "");
519 log_parse_environment();
520 log_open();
521
522 matches = new_matches();
523 if (!matches) {
524 r = -ENOMEM;
525 goto end;
526 }
527
528 r = parse_argv(argc, argv);
529 if (r < 0)
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) {
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) {
551
552 case ACTION_LIST:
553 if (!arg_no_pager)
554 pager_open();
555
556 r = dump_list(j);
557 break;
558
559 case ACTION_DUMP:
560 r = dump_core(j);
561 break;
562
563 case ACTION_GDB:
564 r = run_gdb(j);
565 break;
566
567 default:
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
582 return r >= 0 ? r : EXIT_FAILURE;
583 }