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