chiark / gitweb /
coredump: make sure coredumpctl can handle externally stored coredumps
[elogind.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 #include "copy.h"
40
41 static enum {
42         ACTION_NONE,
43         ACTION_LIST,
44         ACTION_DUMP,
45         ACTION_GDB,
46 } arg_action = ACTION_LIST;
47
48 static FILE* output = NULL;
49 static char* arg_field = NULL;
50
51 static int arg_no_pager = false;
52 static int arg_no_legend = false;
53
54 static Set *new_matches(void) {
55         Set *set;
56         char *tmp;
57         int r;
58
59         set = set_new(trivial_hash_func, trivial_compare_func);
60         if (!set) {
61                 log_oom();
62                 return NULL;
63         }
64
65         tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
66         if (!tmp) {
67                 log_oom();
68                 set_free(set);
69                 return NULL;
70         }
71
72         r = set_consume(set, tmp);
73         if (r < 0) {
74                 log_error("failed to add to set: %s", strerror(-r));
75                 set_free(set);
76                 return NULL;
77         }
78
79         return set;
80 }
81
82 static int help(void) {
83
84         printf("%s [OPTIONS...]\n\n"
85                "List or retrieve coredumps from the journal.\n\n"
86                "Flags:\n"
87                "  -o --output=FILE   Write output to FILE\n"
88                "     --no-pager      Do not pipe output into a pager\n"
89                "     --no-legend     Do not print the column headers.\n\n"
90
91                "Commands:\n"
92                "  -h --help          Show this help\n"
93                "  --version          Print version string\n"
94                "  -F --field=FIELD   List all values a certain field takes\n"
95                "  list [MATCHES...]  List available coredumps\n"
96                "  dump [MATCHES...]  Print first matching coredump to stdout\n"
97                "  gdb [MATCHES...]   Start gdb for the first matching coredump\n"
98                , program_invocation_short_name);
99
100         return 0;
101 }
102
103 static int add_match(Set *set, const char *match) {
104         int r = -ENOMEM;
105         unsigned pid;
106         const char* prefix;
107         char *pattern = NULL;
108         _cleanup_free_ char *p = NULL;
109
110         if (strchr(match, '='))
111                 prefix = "";
112         else if (strchr(match, '/')) {
113                 p = path_make_absolute_cwd(match);
114                 if (!p)
115                         goto fail;
116
117                 match = p;
118                 prefix = "COREDUMP_EXE=";
119         }
120         else if (safe_atou(match, &pid) == 0)
121                 prefix = "COREDUMP_PID=";
122         else
123                 prefix = "COREDUMP_COMM=";
124
125         pattern = strjoin(prefix, match, NULL);
126         if (!pattern)
127                 goto fail;
128
129         log_debug("Adding pattern: %s", pattern);
130         r = set_put(set, pattern);
131         if (r < 0) {
132                 log_error("Failed to add pattern '%s': %s",
133                           pattern, strerror(-r));
134                 free(pattern);
135                 goto fail;
136         }
137
138         return 0;
139 fail:
140         log_error("Failed to add match: %s", strerror(-r));
141         return r;
142 }
143
144 static int parse_argv(int argc, char *argv[], Set *matches) {
145         enum {
146                 ARG_VERSION = 0x100,
147                 ARG_NO_PAGER,
148                 ARG_NO_LEGEND,
149         };
150
151         int r, c;
152
153         static const struct option options[] = {
154                 { "help",         no_argument,       NULL, 'h'           },
155                 { "version" ,     no_argument,       NULL, ARG_VERSION   },
156                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER  },
157                 { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND },
158                 { "output",       required_argument, NULL, 'o'           },
159                 { "field",        required_argument, NULL, 'F'           },
160                 {}
161         };
162
163         assert(argc >= 0);
164         assert(argv);
165
166         while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
167                 switch(c) {
168
169                 case 'h':
170                         arg_action = ACTION_NONE;
171                         return help();
172
173                 case ARG_VERSION:
174                         arg_action = ACTION_NONE;
175                         puts(PACKAGE_STRING);
176                         puts(SYSTEMD_FEATURES);
177                         return 0;
178
179                 case ARG_NO_PAGER:
180                         arg_no_pager = true;
181                         break;
182
183                 case ARG_NO_LEGEND:
184                         arg_no_legend = true;
185                         break;
186
187                 case 'o':
188                         if (output) {
189                                 log_error("cannot set output more than once");
190                                 return -EINVAL;
191                         }
192
193                         output = fopen(optarg, "we");
194                         if (!output) {
195                                 log_error("writing to '%s': %m", optarg);
196                                 return -errno;
197                         }
198
199                         break;
200
201                 case 'F':
202                         if (arg_field) {
203                                 log_error("cannot use --field/-F more than once");
204                                 return -EINVAL;
205                         }
206
207                         arg_field = optarg;
208                         break;
209
210                 case '?':
211                         return -EINVAL;
212
213                 default:
214                         assert_not_reached("Unhandled option");
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 (arg_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                     char **var) {
250
251         size_t ident;
252         char *v;
253
254         ident = strlen(name) + 1; /* name + "=" */
255
256         if (len < ident)
257                 return 0;
258
259         if (memcmp(data, name, ident - 1) != 0)
260                 return 0;
261
262         if (((const char*) data)[ident - 1] != '=')
263                 return 0;
264
265         v = strndup((const char*)data + ident, len - ident);
266         if (!v)
267                 return log_oom();
268
269         free(*var);
270         *var = v;
271
272         return 0;
273 }
274
275 static void print_field(FILE* file, sd_journal *j) {
276         _cleanup_free_ char *value = NULL;
277         const void *d;
278         size_t l;
279
280         assert(arg_field);
281
282         SD_JOURNAL_FOREACH_DATA(j, d, l)
283                 retrieve(d, l, arg_field, &value);
284
285         if (value)
286                 fprintf(file, "%s\n", value);
287 }
288
289 static int print_entry(FILE* file, sd_journal *j, int had_legend) {
290         _cleanup_free_ char
291                 *pid = NULL, *uid = NULL, *gid = NULL,
292                 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL;
293         const void *d;
294         size_t l;
295         usec_t t;
296         char buf[FORMAT_TIMESTAMP_MAX];
297         int r;
298
299         SD_JOURNAL_FOREACH_DATA(j, d, l) {
300                 retrieve(d, l, "COREDUMP_PID", &pid);
301                 retrieve(d, l, "COREDUMP_UID", &uid);
302                 retrieve(d, l, "COREDUMP_GID", &gid);
303                 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
304                 retrieve(d, l, "COREDUMP_EXE", &exe);
305                 retrieve(d, l, "COREDUMP_COMM", &comm);
306                 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
307         }
308
309         if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline) {
310                 log_warning("Empty coredump log entry");
311                 return -EINVAL;
312         }
313
314         r = sd_journal_get_realtime_usec(j, &t);
315         if (r < 0) {
316                 log_error("Failed to get realtime timestamp: %s", strerror(-r));
317                 return r;
318         }
319
320         format_timestamp(buf, sizeof(buf), t);
321
322         if (!had_legend && !arg_no_legend)
323                 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
324                         FORMAT_TIMESTAMP_MAX-1, "TIME",
325                         6, "PID",
326                         5, "UID",
327                         5, "GID",
328                         3, "SIG",
329                            "EXE");
330
331         fprintf(file, "%*s %*s %*s %*s %*s %s\n",
332                 FORMAT_TIMESTAMP_MAX-1, buf,
333                 6, strna(pid),
334                 5, strna(uid),
335                 5, strna(gid),
336                 3, strna(sgnl),
337                 strna(exe ?: (comm ?: cmdline)));
338
339         return 0;
340 }
341
342 static int dump_list(sd_journal *j) {
343         int found = 0;
344
345         assert(j);
346
347         /* The coredumps are likely to compressed, and for just
348          * listing them we don't need to decompress them, so let's
349          * pick a fairly low data threshold here */
350         sd_journal_set_data_threshold(j, 4096);
351
352         SD_JOURNAL_FOREACH(j) {
353                 if (arg_field)
354                         print_field(stdout, j);
355                 else
356                         print_entry(stdout, j, found++);
357         }
358
359         if (!arg_field && !found) {
360                 log_notice("No coredumps found");
361                 return -ESRCH;
362         }
363
364         return 0;
365 }
366
367 static int focus(sd_journal *j) {
368         int r;
369
370         r = sd_journal_seek_tail(j);
371         if (r == 0)
372                 r = sd_journal_previous(j);
373         if (r < 0) {
374                 log_error("Failed to search journal: %s", strerror(-r));
375                 return r;
376         }
377         if (r == 0) {
378                 log_error("No match found");
379                 return -ESRCH;
380         }
381         return r;
382 }
383
384 #define filename_escape(s) xescape((s), "./")
385
386 static int make_coredump_path(sd_journal *j, char **ret) {
387         _cleanup_free_ char
388                 *pid = NULL, *boot_id = NULL, *tstamp = NULL, *comm = NULL,
389                 *p = NULL, *b = NULL, *t = NULL, *c = NULL;
390         const void *d;
391         size_t l;
392         char *fn;
393
394         assert(j);
395         assert(ret);
396
397         SD_JOURNAL_FOREACH_DATA(j, d, l) {
398                 retrieve(d, l, "COREDUMP_COMM", &comm);
399                 retrieve(d, l, "COREDUMP_PID", &pid);
400                 retrieve(d, l, "COREDUMP_TIMESTAMP", &tstamp);
401                 retrieve(d, l, "_BOOT_ID", &boot_id);
402         }
403
404         if (!pid || !comm || !tstamp || !boot_id) {
405                 log_error("Failed to retrieve necessary fields to find coredump on disk.");
406                 return -ENOENT;
407         }
408
409         p = filename_escape(pid);
410         if (!p)
411                 return log_oom();
412
413         t = filename_escape(tstamp);
414         if (!t)
415                 return log_oom();
416
417         c = filename_escape(comm);
418         if (!t)
419                 return log_oom();
420
421         b = filename_escape(boot_id);
422         if (!b)
423                 return log_oom();
424
425         fn = strjoin("/var/lib/systemd/coredump/core.", c, ".", b, ".", p, ".", t, NULL);
426         if (!fn)
427                 return log_oom();
428
429         *ret = fn;
430         return 0;
431 }
432
433 static int dump_core(sd_journal* j) {
434         const void *data;
435         size_t len, ret;
436         int r;
437
438         assert(j);
439
440         /* We want full data, nothing truncated. */
441         sd_journal_set_data_threshold(j, 0);
442
443         r = focus(j);
444         if (r < 0)
445                 return r;
446
447         print_entry(output ? stdout : stderr, j, false);
448
449         if (on_tty() && !output) {
450                 log_error("Refusing to dump core to tty");
451                 return -ENOTTY;
452         }
453
454         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
455         if (r == ENOENT) {
456                 _cleanup_free_ char *fn = NULL;
457                 _cleanup_close_ int fd = -1;
458
459                 r = make_coredump_path(j, &fn);
460                 if (r < 0)
461                         return r;
462
463                 fd = open(fn, O_RDONLY|O_CLOEXEC|O_NOCTTY);
464                 if (fd < 0) {
465                         if (errno == ENOENT)
466                                 log_error("Coredump neither in journal file nor stored externally on disk.");
467                         else
468                                 log_error("Failed to open coredump file: %s", strerror(-r));
469
470                         return -errno;
471                 }
472
473                 r = copy_bytes(fd, output ? fileno(output) : STDOUT_FILENO);
474                 if (r < 0) {
475                         log_error("Failed to stream coredump: %s", strerror(-r));
476                         return r;
477                 }
478
479         } else if (r < 0) {
480                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
481                 return r;
482
483         } else {
484                 assert(len >= 9);
485                 data = (const uint8_t*) data + 9;
486                 len -= 9;
487
488                 ret = fwrite(data, len, 1, output ?: stdout);
489                 if (ret != 1) {
490                         log_error("Dumping coredump failed: %m (%zu)", ret);
491                         return -errno;
492                 }
493         }
494
495         r = sd_journal_previous(j);
496         if (r >= 0)
497                 log_warning("More than one entry matches, ignoring rest.");
498
499         return 0;
500 }
501
502 static int run_gdb(sd_journal *j) {
503
504         _cleanup_free_ char *exe = NULL, *coredump = NULL;
505         char temp[] = "/var/tmp/coredump-XXXXXX";
506         bool unlink_temp = false;
507         const char *path;
508         const void *data;
509         siginfo_t st;
510         size_t len;
511         pid_t pid;
512         int r;
513
514         assert(j);
515
516         sd_journal_set_data_threshold(j, 0);
517
518         r = focus(j);
519         if (r < 0)
520                 return r;
521
522         print_entry(stdout, j, false);
523
524         r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
525         if (r < 0) {
526                 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
527                 return r;
528         }
529
530         assert(len >= 13);
531         data = (const uint8_t*) data + 13;
532         len -= 13;
533
534         exe = strndup(data, len);
535         if (!exe)
536                 return log_oom();
537
538         if (endswith(exe, " (deleted)")) {
539                 log_error("Binary already deleted.");
540                 return -ENOENT;
541         }
542
543         if (!path_is_absolute(exe)) {
544                 log_error("Binary is not an absolute path.");
545                 return -ENOENT;
546         }
547
548         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
549         if (r == -ENOENT) {
550
551                 r = make_coredump_path(j, &coredump);
552                 if (r < 0)
553                         return r;
554
555                 if (access(coredump, R_OK) < 0) {
556                         if (errno == ENOENT)
557                                 log_error("Coredump neither in journal file nor stored externally on disk.");
558                         else
559                                 log_error("Failed to access coredump fiile: %s", strerror(-r));
560
561                         return -errno;
562                 }
563
564                 path = coredump;
565
566         } else if (r < 0) {
567                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
568                 return r;
569
570         } else {
571                 _cleanup_close_ int fd = -1;
572                 ssize_t sz;
573
574                 assert(len >= 9);
575                 data = (const uint8_t*) data + 9;
576                 len -= 9;
577
578                 fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
579                 if (fd < 0) {
580                         log_error("Failed to create temporary file: %m");
581                         return -errno;
582                 }
583
584                 unlink_temp = true;
585
586                 sz = write(fd, data, len);
587                 if (sz < 0) {
588                         log_error("Failed to write temporary file: %m");
589                         r = -errno;
590                         goto finish;
591                 }
592                 if (sz != (ssize_t) len) {
593                         log_error("Short write to temporary file.");
594                         r = -EIO;
595                         goto finish;
596                 }
597
598                 path = temp;
599         }
600
601         pid = fork();
602         if (pid < 0) {
603                 log_error("Failed to fork(): %m");
604                 r = -errno;
605                 goto finish;
606         }
607         if (pid == 0) {
608                 execlp("gdb", "gdb", exe, path, NULL);
609
610                 log_error("Failed to invoke gdb: %m");
611                 _exit(1);
612         }
613
614         r = wait_for_terminate(pid, &st);
615         if (r < 0) {
616                 log_error("Failed to wait for gdb: %m");
617                 goto finish;
618         }
619
620         r = st.si_code == CLD_EXITED ? st.si_status : 255;
621
622 finish:
623         if (unlink_temp)
624                 unlink(temp);
625
626         return r;
627 }
628
629 int main(int argc, char *argv[]) {
630         _cleanup_journal_close_ sd_journal*j = NULL;
631         const char* match;
632         Iterator it;
633         int r = 0;
634         _cleanup_set_free_free_ Set *matches = NULL;
635
636         setlocale(LC_ALL, "");
637         log_parse_environment();
638         log_open();
639
640         matches = new_matches();
641         if (!matches) {
642                 r = -ENOMEM;
643                 goto end;
644         }
645
646         r = parse_argv(argc, argv, matches);
647         if (r < 0)
648                 goto end;
649
650         if (arg_action == ACTION_NONE)
651                 goto end;
652
653         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
654         if (r < 0) {
655                 log_error("Failed to open journal: %s", strerror(-r));
656                 goto end;
657         }
658
659         SET_FOREACH(match, matches, it) {
660                 r = sd_journal_add_match(j, match, strlen(match));
661                 if (r != 0) {
662                         log_error("Failed to add match '%s': %s",
663                                   match, strerror(-r));
664                         goto end;
665                 }
666         }
667
668         if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
669                 _cleanup_free_ char *filter;
670
671                 filter = journal_make_match_string(j);
672                 log_debug("Journal filter: %s", filter);
673         }
674
675         switch(arg_action) {
676
677         case ACTION_LIST:
678                 if (!arg_no_pager)
679                         pager_open(false);
680
681                 r = dump_list(j);
682                 break;
683
684         case ACTION_DUMP:
685                 r = dump_core(j);
686                 break;
687
688         case  ACTION_GDB:
689                 r = run_gdb(j);
690                 break;
691
692         default:
693                 assert_not_reached("Shouldn't be here");
694         }
695
696 end:
697         pager_close();
698
699         if (output)
700                 fclose(output);
701
702         return r >= 0 ? r : EXIT_FAILURE;
703 }