chiark / gitweb /
coredump: make use of the cleanup macros
[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
38 static enum {
39         ACTION_NONE,
40         ACTION_LIST,
41         ACTION_DUMP,
42         ACTION_GDB,
43 } arg_action = ACTION_LIST;
44
45 static Set *matches = NULL;
46 static FILE* output = NULL;
47 static char* field = NULL;
48
49 static int arg_no_pager = false;
50 static int arg_no_legend = false;
51
52 static Set *new_matches(void) {
53         Set *set;
54         char *tmp;
55         int r;
56
57         set = set_new(trivial_hash_func, trivial_compare_func);
58         if (!set) {
59                 log_oom();
60                 return NULL;
61         }
62
63         tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
64         if (!tmp) {
65                 log_oom();
66                 set_free(set);
67                 return NULL;
68         }
69
70         r = set_put(set, tmp);
71         if (r < 0) {
72                 log_error("failed to add to set: %s", strerror(-r));
73                 free(tmp);
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
88                "Commands:\n"
89                "  -h --help         Show this help\n"
90                "  --version         Print version string\n"
91                "  -F --field=FIELD  List all values a certain field takes\n"
92                "  gdb               Start gdb for the first matching coredump\n"
93                "  list              List available coredumps\n"
94                "  dump PID          Print coredump to stdout\n"
95                "  dump PATH         Print coredump to stdout\n"
96                , program_invocation_short_name);
97
98         return 0;
99 }
100
101 static int add_match(Set *set, const char *match) {
102         int r = -ENOMEM;
103         unsigned pid;
104         const char* prefix;
105         char *pattern = NULL;
106         char _cleanup_free_ *p = NULL;
107
108         if (strchr(match, '='))
109                 prefix = "";
110         else if (strchr(match, '/')) {
111                 p = path_make_absolute_cwd(match);
112                 if (!p)
113                         goto fail;
114
115                 match = p;
116                 prefix = "COREDUMP_EXE=";
117         }
118         else if (safe_atou(match, &pid) == 0)
119                 prefix = "COREDUMP_PID=";
120         else
121                 prefix = "COREDUMP_COMM=";
122
123         pattern = strjoin(prefix, match, NULL);
124         if (!pattern)
125                 goto fail;
126
127         r = set_put(set, pattern);
128         if (r < 0) {
129                 log_error("failed to add pattern '%s': %s",
130                           pattern, strerror(-r));
131                 goto fail;
132         }
133         log_debug("Added pattern: %s", pattern);
134
135         return 0;
136 fail:
137         free(pattern);
138         log_error("failed to add match: %s", strerror(-r));
139         return r;
140 }
141
142 static int parse_argv(int argc, char *argv[]) {
143         enum {
144                 ARG_VERSION = 0x100,
145                 ARG_NO_PAGER,
146                 ARG_NO_LEGEND,
147         };
148
149         int r, c;
150
151         static const struct option options[] = {
152                 { "help",         no_argument,       NULL, 'h'           },
153                 { "version" ,     no_argument,       NULL, ARG_VERSION   },
154                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER  },
155                 { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND },
156                 { "output",       required_argument, NULL, 'o'           },
157                 { "field",        required_argument, NULL, 'F'           },
158                 { NULL,           0,                 NULL, 0             }
159         };
160
161         assert(argc >= 0);
162         assert(argv);
163
164         while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
165                 switch(c) {
166                 case 'h':
167                         help();
168                         arg_action = ACTION_NONE;
169                         return 0;
170
171                 case ARG_VERSION:
172                         puts(PACKAGE_STRING);
173                         puts(SYSTEMD_FEATURES);
174                         arg_action = ACTION_NONE;
175                         return 0;
176
177                 case ARG_NO_PAGER:
178                         arg_no_pager = true;
179                         break;
180
181                 case ARG_NO_LEGEND:
182                         arg_no_legend = true;
183                         break;
184
185                 case 'o':
186                         if (output) {
187                                 log_error("cannot set output more than once");
188                                 return -EINVAL;
189                         }
190
191                         output = fopen(optarg, "we");
192                         if (!output) {
193                                 log_error("writing to '%s': %m", optarg);
194                                 return -errno;
195                         }
196
197                         break;
198
199                 case 'F':
200                         if (field) {
201                                 log_error("cannot use --field/-F more than once");
202                                 return -EINVAL;
203                         }
204
205                         field = optarg;
206                         break;
207
208                 case '?':
209                         return -EINVAL;
210
211                 default:
212                         log_error("Unknown option code %c", c);
213                         return -EINVAL;
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         const char _cleanup_free_ *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         const char _cleanup_free_
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.\n");
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: %s", strerror(errno));
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         sd_journal *j = NULL;
523         const char* match;
524         Iterator it;
525         int r = 0;
526
527         setlocale(LC_ALL, "");
528         log_parse_environment();
529         log_open();
530
531         matches = new_matches();
532         if (!matches) {
533                 r = -ENOMEM;
534                 goto end;
535         }
536
537         r = parse_argv(argc, argv);
538         if (r < 0)
539                 goto end;
540
541         if (arg_action == ACTION_NONE)
542                 goto end;
543
544         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
545         if (r < 0) {
546                 log_error("Failed to open journal: %s", strerror(-r));
547                 goto end;
548         }
549
550         SET_FOREACH(match, matches, it) {
551                 r = sd_journal_add_match(j, match, strlen(match));
552                 if (r != 0) {
553                         log_error("Failed to add match '%s': %s",
554                                   match, strerror(-r));
555                         goto end;
556                 }
557         }
558
559         switch(arg_action) {
560
561         case ACTION_LIST:
562                 if (!arg_no_pager)
563                         pager_open();
564
565                 r = dump_list(j);
566                 break;
567
568         case ACTION_DUMP:
569                 r = dump_core(j);
570                 break;
571
572         case  ACTION_GDB:
573                 r = run_gdb(j);
574                 break;
575
576         default:
577                 assert_not_reached("Shouldn't be here");
578         }
579
580 end:
581         if (j)
582                 sd_journal_close(j);
583
584         set_free_free(matches);
585
586         pager_close();
587
588         if (output)
589                 fclose(output);
590
591         return r >= 0 ? r : EXIT_FAILURE;
592 }