chiark / gitweb /
Add set_consume which always takes ownership
[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
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         _cleanup_free_ char *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         log_debug("Adding pattern: %s", pattern);
128         r = set_consume(set, pattern);
129         if (r < 0) {
130                 log_error("Failed to add pattern '%s': %s",
131                           pattern, strerror(-r));
132                 goto fail;
133         }
134
135         return 0;
136 fail:
137         log_error("Failed to add match: %s", strerror(-r));
138         return r;
139 }
140
141 static int parse_argv(int argc, char *argv[], Set *matches) {
142         enum {
143                 ARG_VERSION = 0x100,
144                 ARG_NO_PAGER,
145                 ARG_NO_LEGEND,
146         };
147
148         int r, c;
149
150         static const struct option options[] = {
151                 { "help",         no_argument,       NULL, 'h'           },
152                 { "version" ,     no_argument,       NULL, ARG_VERSION   },
153                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER  },
154                 { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND },
155                 { "output",       required_argument, NULL, 'o'           },
156                 { "field",        required_argument, NULL, 'F'           },
157                 { NULL,           0,                 NULL, 0             }
158         };
159
160         assert(argc >= 0);
161         assert(argv);
162
163         while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
164                 switch(c) {
165                 case 'h':
166                         help();
167                         arg_action = ACTION_NONE;
168                         return 0;
169
170                 case ARG_VERSION:
171                         puts(PACKAGE_STRING);
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         _cleanup_free_ const char *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         _cleanup_free_ const char
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         /* The coredumps are likely to compressed, and for just
344          * listing them we don#t need to decompress them, so let's
345          * pick a fairly low data threshold here */
346         sd_journal_set_data_threshold(j, 4096);
347
348         SD_JOURNAL_FOREACH(j) {
349                 if (field)
350                         print_field(stdout, j);
351                 else
352                         print_entry(stdout, j, found++);
353         }
354
355         if (!field && !found) {
356                 log_notice("No coredumps found");
357                 return -ESRCH;
358         }
359
360         return 0;
361 }
362
363 static int focus(sd_journal *j) {
364         int r;
365
366         r = sd_journal_seek_tail(j);
367         if (r == 0)
368                 r = sd_journal_previous(j);
369         if (r < 0) {
370                 log_error("Failed to search journal: %s", strerror(-r));
371                 return r;
372         }
373         if (r == 0) {
374                 log_error("No match found");
375                 return -ESRCH;
376         }
377         return r;
378 }
379
380 static int dump_core(sd_journal* j) {
381         const void *data;
382         size_t len, ret;
383         int r;
384
385         assert(j);
386
387         /* We want full data, nothing truncated. */
388         sd_journal_set_data_threshold(j, 0);
389
390         r = focus(j);
391         if (r < 0)
392                 return r;
393
394         print_entry(output ? stdout : stderr, j, false);
395
396         if (on_tty() && !output) {
397                 log_error("Refusing to dump core to tty");
398                 return -ENOTTY;
399         }
400
401         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
402         if (r < 0) {
403                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
404                 return r;
405         }
406
407         assert(len >= 9);
408         data = (const uint8_t*) data + 9;
409         len -= 9;
410
411         ret = fwrite(data, len, 1, output ? output : stdout);
412         if (ret != 1) {
413                 log_error("dumping coredump: %m (%zu)", ret);
414                 return -errno;
415         }
416
417         r = sd_journal_previous(j);
418         if (r >= 0)
419                 log_warning("More than one entry matches, ignoring rest.\n");
420
421         return 0;
422 }
423
424 static int run_gdb(sd_journal *j) {
425         char path[] = "/var/tmp/coredump-XXXXXX";
426         const void *data;
427         size_t len;
428         ssize_t sz;
429         pid_t pid;
430         _cleanup_free_ char *exe = NULL;
431         int r;
432         _cleanup_close_ int fd = -1;
433         siginfo_t st;
434
435         assert(j);
436
437         sd_journal_set_data_threshold(j, 0);
438
439         r = focus(j);
440         if (r < 0)
441                 return r;
442
443         print_entry(stdout, j, false);
444
445         r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
446         if (r < 0) {
447                 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
448                 return r;
449         }
450
451         assert(len >= 13);
452         data = (const uint8_t*) data + 13;
453         len -= 13;
454
455         exe = strndup(data, len);
456         if (!exe)
457                 return log_oom();
458
459         if (endswith(exe, " (deleted)")) {
460                 log_error("Binary already deleted.");
461                 return -ENOENT;
462         }
463
464         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
465         if (r < 0) {
466                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
467                 return r;
468         }
469
470         assert(len >= 9);
471         data = (const uint8_t*) data + 9;
472         len -= 9;
473
474         fd = mkostemp(path, O_WRONLY);
475         if (fd < 0) {
476                 log_error("Failed to create temporary file: %m");
477                 return -errno;
478         }
479
480         sz = write(fd, data, len);
481         if (sz < 0) {
482                 log_error("Failed to write temporary file: %s", strerror(errno));
483                 r = -errno;
484                 goto finish;
485         }
486         if (sz != (ssize_t) len) {
487                 log_error("Short write to temporary file.");
488                 r = -EIO;
489                 goto finish;
490         }
491
492         close_nointr_nofail(fd);
493         fd = -1;
494
495         pid = fork();
496         if (pid < 0) {
497                 log_error("Failed to fork(): %m");
498                 r = -errno;
499                 goto finish;
500         }
501         if (pid == 0) {
502                 execlp("gdb", "gdb", exe, path, NULL);
503                 log_error("Failed to invoke gdb: %m");
504                 _exit(1);
505         }
506
507         r = wait_for_terminate(pid, &st);
508         if (r < 0) {
509                 log_error("Failed to wait for gdb: %m");
510                 goto finish;
511         }
512
513         r = st.si_code == CLD_EXITED ? st.si_status : 255;
514
515 finish:
516         unlink(path);
517         return r;
518 }
519
520 int main(int argc, char *argv[]) {
521         _cleanup_journal_close_ sd_journal*j = NULL;
522         const char* match;
523         Iterator it;
524         int r = 0;
525         _cleanup_set_free_free_ Set *matches = NULL;
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, matches);
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(false);
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         pager_close();
582
583         if (output)
584                 fclose(output);
585
586         return r >= 0 ? r : EXIT_FAILURE;
587 }