chiark / gitweb /
663e3cdd10391d3e66c45ccce2e86569f5822ea5
[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                "  gdb               Start gdb for the first matching coredump\n"
91                "  list              List available coredumps\n"
92                "  dump PID          Print coredump to stdout\n"
93                "  dump PATH         Print coredump to stdout\n"
94                , program_invocation_short_name);
95
96         return 0;
97 }
98
99 static int add_match(Set *set, const char *match) {
100         int r = -ENOMEM;
101         unsigned pid;
102         const char* prefix;
103         char *pattern = NULL;
104         char _cleanup_free_ *p = NULL;
105
106         if (strchr(match, '='))
107                 prefix = "";
108         else if (strchr(match, '/')) {
109                 p = path_make_absolute_cwd(match);
110                 if (!p)
111                         goto fail;
112
113                 match = p;
114                 prefix = "COREDUMP_EXE=";
115         }
116         else if (safe_atou(match, &pid) == 0)
117                 prefix = "COREDUMP_PID=";
118         else
119                 prefix = "COREDUMP_COMM=";
120
121         pattern = strjoin(prefix, match, NULL);
122         if (!pattern)
123                 goto fail;
124
125         r = set_put(set, pattern);
126         if (r < 0) {
127                 log_error("failed to add pattern '%s': %s",
128                           pattern, strerror(-r));
129                 goto fail;
130         }
131         log_debug("Added pattern: %s", pattern);
132
133         return 0;
134 fail:
135         free(pattern);
136         log_error("failed to add match: %s", strerror(-r));
137         return r;
138 }
139
140 static int parse_argv(int argc, char *argv[]) {
141         enum {
142                 ARG_VERSION = 0x100,
143                 ARG_NO_PAGER,
144                 ARG_NO_LEGEND,
145         };
146
147         int r, c;
148
149         static const struct option options[] = {
150                 { "help",         no_argument,       NULL, 'h'           },
151                 { "version" ,     no_argument,       NULL, ARG_VERSION   },
152                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER  },
153                 { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND },
154                 { "output",       required_argument, NULL, 'o'           },
155                 { "field",        required_argument, NULL, 'F'           },
156                 { NULL,           0,                 NULL, 0             }
157         };
158
159         assert(argc >= 0);
160         assert(argv);
161
162         while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
163                 switch(c) {
164                 case 'h':
165                         help();
166                         arg_action = ACTION_NONE;
167                         return 0;
168
169                 case ARG_VERSION:
170                         puts(PACKAGE_STRING);
171                         puts(DISTRIBUTION);
172                         puts(SYSTEMD_FEATURES);
173                         arg_action = ACTION_NONE;
174                         return 0;
175
176                 case ARG_NO_PAGER:
177                         arg_no_pager = true;
178                         break;
179
180                 case ARG_NO_LEGEND:
181                         arg_no_legend = true;
182                         break;
183
184                 case 'o':
185                         if (output) {
186                                 log_error("cannot set output more than once");
187                                 return -EINVAL;
188                         }
189
190                         output = fopen(optarg, "we");
191                         if (!output) {
192                                 log_error("writing to '%s': %m", optarg);
193                                 return -errno;
194                         }
195
196                         break;
197
198                 case 'F':
199                         if (field) {
200                                 log_error("cannot use --field/-F more than once");
201                                 return -EINVAL;
202                         }
203
204                         field = optarg;
205                         break;
206
207                 case '?':
208                         return -EINVAL;
209
210                 default:
211                         log_error("Unknown option code %c", c);
212                         return -EINVAL;
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         const char _cleanup_free_ *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         const char _cleanup_free_
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         SD_JOURNAL_FOREACH(j) {
344                 if (field)
345                         print_field(stdout, j);
346                 else
347                         print_entry(stdout, j, found++);
348         }
349
350         if (!field && !found) {
351                 log_notice("No coredumps found");
352                 return -ESRCH;
353         }
354
355         return 0;
356 }
357
358 static int focus(sd_journal *j) {
359         int r;
360
361         r = sd_journal_seek_tail(j);
362         if (r == 0)
363                 r = sd_journal_previous(j);
364         if (r < 0) {
365                 log_error("Failed to search journal: %s", strerror(-r));
366                 return r;
367         }
368         if (r == 0) {
369                 log_error("No match found");
370                 return -ESRCH;
371         }
372         return r;
373 }
374
375 static int dump_core(sd_journal* j) {
376         const void *data;
377         size_t len, ret;
378         int r;
379
380         assert(j);
381
382         r = focus(j);
383         if (r < 0)
384                 return r;
385
386         print_entry(output ? stdout : stderr, j, false);
387
388         if (on_tty() && !output) {
389                 log_error("Refusing to dump core to tty");
390                 return -ENOTTY;
391         }
392
393         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
394         if (r < 0) {
395                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
396                 return r;
397         }
398
399         assert(len >= 9);
400         data = (const uint8_t*) data + 9;
401         len -= 9;
402
403         ret = fwrite(data, len, 1, output ? output : stdout);
404         if (ret != 1) {
405                 log_error("dumping coredump: %m (%zu)", ret);
406                 return -errno;
407         }
408
409         r = sd_journal_previous(j);
410         if (r >= 0)
411                 log_warning("More than one entry matches, ignoring rest.\n");
412
413         return 0;
414 }
415
416 static int run_gdb(sd_journal *j) {
417         char path[] = "/var/tmp/coredump-XXXXXX";
418         const void *data;
419         size_t len;
420         ssize_t sz;
421         pid_t pid;
422         _cleanup_free_ char *exe = NULL;
423         int r;
424         _cleanup_close_ int fd = -1;
425         siginfo_t st;
426
427         assert(j);
428
429         r = focus(j);
430         if (r < 0)
431                 return r;
432
433         print_entry(stdout, j, false);
434
435         r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
436         if (r < 0) {
437                 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
438                 return r;
439         }
440
441         assert(len >= 13);
442         data = (const uint8_t*) data + 13;
443         len -= 13;
444
445         exe = strndup(data, len);
446         if (!exe)
447                 return log_oom();
448
449         if (endswith(exe, " (deleted)")) {
450                 log_error("Binary already deleted.");
451                 return -ENOENT;
452         }
453
454         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
455         if (r < 0) {
456                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
457                 return r;
458         }
459
460         assert(len >= 9);
461         data = (const uint8_t*) data + 9;
462         len -= 9;
463
464         fd = mkostemp(path, O_WRONLY);
465         if (fd < 0) {
466                 log_error("Failed to create temporary file: %m");
467                 return -errno;
468         }
469
470         sz = write(fd, data, len);
471         if (sz < 0) {
472                 log_error("Failed to write temporary file: %s", strerror(errno));
473                 r = -errno;
474                 goto finish;
475         }
476         if (sz != (ssize_t) len) {
477                 log_error("Short write to temporary file.");
478                 r = -EIO;
479                 goto finish;
480         }
481
482         close_nointr_nofail(fd);
483         fd = -1;
484
485         pid = fork();
486         if (pid < 0) {
487                 log_error("Failed to fork(): %m");
488                 r = -errno;
489                 goto finish;
490         }
491         if (pid == 0) {
492                 execlp("gdb", "gdb", exe, path, NULL);
493                 log_error("Failed to invoke gdb: %m");
494                 _exit(1);
495         }
496
497         r = wait_for_terminate(pid, &st);
498         if (r < 0) {
499                 log_error("Failed to wait for gdb: %m");
500                 goto finish;
501         }
502
503         r = st.si_code == CLD_EXITED ? st.si_status : 255;
504
505 finish:
506         unlink(path);
507         return r;
508 }
509
510 int main(int argc, char *argv[]) {
511         sd_journal *j = NULL;
512         const char* match;
513         Iterator it;
514         int r = 0;
515
516         log_parse_environment();
517         log_open();
518
519         matches = new_matches();
520         if (!matches) {
521                 r = -ENOMEM;
522                 goto end;
523         }
524
525         r = parse_argv(argc, argv);
526         if (r < 0)
527                 goto end;
528
529         if (arg_action == ACTION_NONE)
530                 goto end;
531
532         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
533         if (r < 0) {
534                 log_error("Failed to open journal: %s", strerror(-r));
535                 goto end;
536         }
537
538         SET_FOREACH(match, matches, it) {
539                 r = sd_journal_add_match(j, match, strlen(match));
540                 if (r != 0) {
541                         log_error("Failed to add match '%s': %s",
542                                   match, strerror(-r));
543                         goto end;
544                 }
545         }
546
547         switch(arg_action) {
548
549         case ACTION_LIST:
550                 if (!arg_no_pager)
551                         pager_open();
552
553                 r = dump_list(j);
554                 break;
555
556         case ACTION_DUMP:
557                 r = dump_core(j);
558                 break;
559
560         case  ACTION_GDB:
561                 r = run_gdb(j);
562                 break;
563
564         default:
565                 assert_not_reached("Shouldn't be here");
566         }
567
568 end:
569         if (j)
570                 sd_journal_close(j);
571
572         set_free_free(matches);
573
574         pager_close();
575
576         if (output)
577                 fclose(output);
578
579         return r >= 0 ? r : EXIT_FAILURE;
580 }