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