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