chiark / gitweb /
coredumpctl: in case of error free pattern after print
[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_put(set, pattern);
130         if (r < 0) {
131                 log_error("Failed to add pattern '%s': %s",
132                           pattern, strerror(-r));
133                 free(pattern);
134                 goto fail;
135         }
136
137         return 0;
138 fail:
139         log_error("Failed to add match: %s", strerror(-r));
140         return r;
141 }
142
143 static int parse_argv(int argc, char *argv[], Set *matches) {
144         enum {
145                 ARG_VERSION = 0x100,
146                 ARG_NO_PAGER,
147                 ARG_NO_LEGEND,
148         };
149
150         int r, c;
151
152         static const struct option options[] = {
153                 { "help",         no_argument,       NULL, 'h'           },
154                 { "version" ,     no_argument,       NULL, ARG_VERSION   },
155                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER  },
156                 { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND },
157                 { "output",       required_argument, NULL, 'o'           },
158                 { "field",        required_argument, NULL, 'F'           },
159                 {}
160         };
161
162         assert(argc >= 0);
163         assert(argv);
164
165         while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
166                 switch(c) {
167
168                 case 'h':
169                         arg_action = ACTION_NONE;
170                         return help();
171
172                 case ARG_VERSION:
173                         arg_action = ACTION_NONE;
174                         puts(PACKAGE_STRING);
175                         puts(SYSTEMD_FEATURES);
176                         return 0;
177
178                 case ARG_NO_PAGER:
179                         arg_no_pager = true;
180                         break;
181
182                 case ARG_NO_LEGEND:
183                         arg_no_legend = true;
184                         break;
185
186                 case 'o':
187                         if (output) {
188                                 log_error("cannot set output more than once");
189                                 return -EINVAL;
190                         }
191
192                         output = fopen(optarg, "we");
193                         if (!output) {
194                                 log_error("writing to '%s': %m", optarg);
195                                 return -errno;
196                         }
197
198                         break;
199
200                 case 'F':
201                         if (field) {
202                                 log_error("cannot use --field/-F more than once");
203                                 return -EINVAL;
204                         }
205
206                         field = optarg;
207                         break;
208
209                 case '?':
210                         return -EINVAL;
211
212                 default:
213                         assert_not_reached("Unhandled option");
214                 }
215
216         if (optind < argc) {
217                 const char *cmd = argv[optind++];
218                 if (streq(cmd, "list"))
219                         arg_action = ACTION_LIST;
220                 else if (streq(cmd, "dump"))
221                         arg_action = ACTION_DUMP;
222                 else if (streq(cmd, "gdb"))
223                         arg_action = ACTION_GDB;
224                 else {
225                         log_error("Unknown action '%s'", cmd);
226                         return -EINVAL;
227                 }
228         }
229
230         if (field && arg_action != ACTION_LIST) {
231                 log_error("Option --field/-F only makes sense with list");
232                 return -EINVAL;
233         }
234
235         while (optind < argc) {
236                 r = add_match(matches, argv[optind]);
237                 if (r != 0)
238                         return r;
239                 optind++;
240         }
241
242         return 0;
243 }
244
245 static int retrieve(const void *data,
246                     size_t len,
247                     const char *name,
248                     const char **var) {
249
250         size_t ident;
251
252         ident = strlen(name) + 1; /* name + "=" */
253
254         if (len < ident)
255                 return 0;
256
257         if (memcmp(data, name, ident - 1) != 0)
258                 return 0;
259
260         if (((const char*) data)[ident - 1] != '=')
261                 return 0;
262
263         *var = strndup((const char*)data + ident, len - ident);
264         if (!*var)
265                 return log_oom();
266
267         return 0;
268 }
269
270 static void print_field(FILE* file, sd_journal *j) {
271         _cleanup_free_ const char *value = NULL;
272         const void *d;
273         size_t l;
274
275         assert(field);
276
277         SD_JOURNAL_FOREACH_DATA(j, d, l)
278                 retrieve(d, l, field, &value);
279         if (value)
280                 fprintf(file, "%s\n", value);
281 }
282
283 static int print_entry(FILE* file, sd_journal *j, int had_legend) {
284         _cleanup_free_ const char
285                 *pid = NULL, *uid = NULL, *gid = NULL,
286                 *sgnl = NULL, *exe = NULL;
287         const void *d;
288         size_t l;
289         usec_t t;
290         char buf[FORMAT_TIMESTAMP_MAX];
291         int r;
292
293         SD_JOURNAL_FOREACH_DATA(j, d, l) {
294                 retrieve(d, l, "COREDUMP_PID", &pid);
295                 retrieve(d, l, "COREDUMP_PID", &pid);
296                 retrieve(d, l, "COREDUMP_UID", &uid);
297                 retrieve(d, l, "COREDUMP_GID", &gid);
298                 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
299                 retrieve(d, l, "COREDUMP_EXE", &exe);
300                 if (!exe)
301                         retrieve(d, l, "COREDUMP_COMM", &exe);
302                 if (!exe)
303                         retrieve(d, l, "COREDUMP_CMDLINE", &exe);
304         }
305
306         if (!pid && !uid && !gid && !sgnl && !exe) {
307                 log_warning("Empty coredump log entry");
308                 return -EINVAL;
309         }
310
311         r = sd_journal_get_realtime_usec(j, &t);
312         if (r < 0) {
313                 log_error("Failed to get realtime timestamp: %s", strerror(-r));
314                 return r;
315         }
316
317         format_timestamp(buf, sizeof(buf), t);
318
319         if (!had_legend && !arg_no_legend)
320                 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
321                         FORMAT_TIMESTAMP_MAX-1, "TIME",
322                         6, "PID",
323                         5, "UID",
324                         5, "GID",
325                         3, "SIG",
326                            "EXE");
327
328         fprintf(file, "%*s %*s %*s %*s %*s %s\n",
329                 FORMAT_TIMESTAMP_MAX-1, buf,
330                 6, pid,
331                 5, uid,
332                 5, gid,
333                 3, sgnl,
334                 exe);
335
336         return 0;
337 }
338
339 static int dump_list(sd_journal *j) {
340         int found = 0;
341
342         assert(j);
343
344         /* The coredumps are likely to compressed, and for just
345          * listing them we don't need to decompress them, so let's
346          * pick a fairly low data threshold here */
347         sd_journal_set_data_threshold(j, 4096);
348
349         SD_JOURNAL_FOREACH(j) {
350                 if (field)
351                         print_field(stdout, j);
352                 else
353                         print_entry(stdout, j, found++);
354         }
355
356         if (!field && !found) {
357                 log_notice("No coredumps found");
358                 return -ESRCH;
359         }
360
361         return 0;
362 }
363
364 static int focus(sd_journal *j) {
365         int r;
366
367         r = sd_journal_seek_tail(j);
368         if (r == 0)
369                 r = sd_journal_previous(j);
370         if (r < 0) {
371                 log_error("Failed to search journal: %s", strerror(-r));
372                 return r;
373         }
374         if (r == 0) {
375                 log_error("No match found");
376                 return -ESRCH;
377         }
378         return r;
379 }
380
381 static int dump_core(sd_journal* j) {
382         const void *data;
383         size_t len, ret;
384         int r;
385
386         assert(j);
387
388         /* We want full data, nothing truncated. */
389         sd_journal_set_data_threshold(j, 0);
390
391         r = focus(j);
392         if (r < 0)
393                 return r;
394
395         print_entry(output ? stdout : stderr, j, false);
396
397         if (on_tty() && !output) {
398                 log_error("Refusing to dump core to tty");
399                 return -ENOTTY;
400         }
401
402         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
403         if (r < 0) {
404                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
405                 return r;
406         }
407
408         assert(len >= 9);
409         data = (const uint8_t*) data + 9;
410         len -= 9;
411
412         ret = fwrite(data, len, 1, output ? output : stdout);
413         if (ret != 1) {
414                 log_error("dumping coredump: %m (%zu)", ret);
415                 return -errno;
416         }
417
418         r = sd_journal_previous(j);
419         if (r >= 0)
420                 log_warning("More than one entry matches, ignoring rest.");
421
422         return 0;
423 }
424
425 static int run_gdb(sd_journal *j) {
426         char path[] = "/var/tmp/coredump-XXXXXX";
427         const void *data;
428         size_t len;
429         ssize_t sz;
430         pid_t pid;
431         _cleanup_free_ char *exe = NULL;
432         int r;
433         _cleanup_close_ int fd = -1;
434         siginfo_t st;
435
436         assert(j);
437
438         sd_journal_set_data_threshold(j, 0);
439
440         r = focus(j);
441         if (r < 0)
442                 return r;
443
444         print_entry(stdout, j, false);
445
446         r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
447         if (r < 0) {
448                 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
449                 return r;
450         }
451
452         assert(len >= 13);
453         data = (const uint8_t*) data + 13;
454         len -= 13;
455
456         exe = strndup(data, len);
457         if (!exe)
458                 return log_oom();
459
460         if (endswith(exe, " (deleted)")) {
461                 log_error("Binary already deleted.");
462                 return -ENOENT;
463         }
464
465         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
466         if (r < 0) {
467                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
468                 return r;
469         }
470
471         assert(len >= 9);
472         data = (const uint8_t*) data + 9;
473         len -= 9;
474
475         fd = mkostemp(path, O_WRONLY);
476         if (fd < 0) {
477                 log_error("Failed to create temporary file: %m");
478                 return -errno;
479         }
480
481         sz = write(fd, data, len);
482         if (sz < 0) {
483                 log_error("Failed to write temporary file: %m");
484                 r = -errno;
485                 goto finish;
486         }
487         if (sz != (ssize_t) len) {
488                 log_error("Short write to temporary file.");
489                 r = -EIO;
490                 goto finish;
491         }
492
493         close_nointr_nofail(fd);
494         fd = -1;
495
496         pid = fork();
497         if (pid < 0) {
498                 log_error("Failed to fork(): %m");
499                 r = -errno;
500                 goto finish;
501         }
502         if (pid == 0) {
503                 execlp("gdb", "gdb", exe, path, NULL);
504                 log_error("Failed to invoke gdb: %m");
505                 _exit(1);
506         }
507
508         r = wait_for_terminate(pid, &st);
509         if (r < 0) {
510                 log_error("Failed to wait for gdb: %m");
511                 goto finish;
512         }
513
514         r = st.si_code == CLD_EXITED ? st.si_status : 255;
515
516 finish:
517         unlink(path);
518         return r;
519 }
520
521 int main(int argc, char *argv[]) {
522         _cleanup_journal_close_ sd_journal*j = NULL;
523         const char* match;
524         Iterator it;
525         int r = 0;
526         _cleanup_set_free_free_ Set *matches = NULL;
527
528         setlocale(LC_ALL, "");
529         log_parse_environment();
530         log_open();
531
532         matches = new_matches();
533         if (!matches) {
534                 r = -ENOMEM;
535                 goto end;
536         }
537
538         r = parse_argv(argc, argv, matches);
539         if (r < 0)
540                 goto end;
541
542         if (arg_action == ACTION_NONE)
543                 goto end;
544
545         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
546         if (r < 0) {
547                 log_error("Failed to open journal: %s", strerror(-r));
548                 goto end;
549         }
550
551         SET_FOREACH(match, matches, it) {
552                 r = sd_journal_add_match(j, match, strlen(match));
553                 if (r != 0) {
554                         log_error("Failed to add match '%s': %s",
555                                   match, strerror(-r));
556                         goto end;
557                 }
558         }
559
560         if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
561                 _cleanup_free_ char *filter;
562
563                 filter = journal_make_match_string(j);
564                 log_debug("Journal filter: %s", filter);
565         }
566
567         switch(arg_action) {
568
569         case ACTION_LIST:
570                 if (!arg_no_pager)
571                         pager_open(false);
572
573                 r = dump_list(j);
574                 break;
575
576         case ACTION_DUMP:
577                 r = dump_core(j);
578                 break;
579
580         case  ACTION_GDB:
581                 r = run_gdb(j);
582                 break;
583
584         default:
585                 assert_not_reached("Shouldn't be here");
586         }
587
588 end:
589         pager_close();
590
591         if (output)
592                 fclose(output);
593
594         return r >= 0 ? r : EXIT_FAILURE;
595 }