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