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