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