chiark / gitweb /
4adc9236f1fd3428d6f98ea3725a21e927a8f84f
[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(DISTRIBUTION);
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         SD_JOURNAL_FOREACH(j) {
346                 if (field)
347                         print_field(stdout, j);
348                 else
349                         print_entry(stdout, j, found++);
350         }
351
352         if (!field && !found) {
353                 log_notice("No coredumps found");
354                 return -ESRCH;
355         }
356
357         return 0;
358 }
359
360 static int focus(sd_journal *j) {
361         int r;
362
363         r = sd_journal_seek_tail(j);
364         if (r == 0)
365                 r = sd_journal_previous(j);
366         if (r < 0) {
367                 log_error("Failed to search journal: %s", strerror(-r));
368                 return r;
369         }
370         if (r == 0) {
371                 log_error("No match found");
372                 return -ESRCH;
373         }
374         return r;
375 }
376
377 static int dump_core(sd_journal* j) {
378         const void *data;
379         size_t len, ret;
380         int r;
381
382         assert(j);
383
384         r = focus(j);
385         if (r < 0)
386                 return r;
387
388         print_entry(output ? stdout : stderr, j, false);
389
390         if (on_tty() && !output) {
391                 log_error("Refusing to dump core to tty");
392                 return -ENOTTY;
393         }
394
395         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
396         if (r < 0) {
397                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
398                 return r;
399         }
400
401         assert(len >= 9);
402         data = (const uint8_t*) data + 9;
403         len -= 9;
404
405         ret = fwrite(data, len, 1, output ? output : stdout);
406         if (ret != 1) {
407                 log_error("dumping coredump: %m (%zu)", ret);
408                 return -errno;
409         }
410
411         r = sd_journal_previous(j);
412         if (r >= 0)
413                 log_warning("More than one entry matches, ignoring rest.\n");
414
415         return 0;
416 }
417
418 static int run_gdb(sd_journal *j) {
419         char path[] = "/var/tmp/coredump-XXXXXX";
420         const void *data;
421         size_t len;
422         ssize_t sz;
423         pid_t pid;
424         _cleanup_free_ char *exe = NULL;
425         int r;
426         _cleanup_close_ int fd = -1;
427         siginfo_t st;
428
429         assert(j);
430
431         r = focus(j);
432         if (r < 0)
433                 return r;
434
435         print_entry(stdout, j, false);
436
437         r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
438         if (r < 0) {
439                 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
440                 return r;
441         }
442
443         assert(len >= 13);
444         data = (const uint8_t*) data + 13;
445         len -= 13;
446
447         exe = strndup(data, len);
448         if (!exe)
449                 return log_oom();
450
451         if (endswith(exe, " (deleted)")) {
452                 log_error("Binary already deleted.");
453                 return -ENOENT;
454         }
455
456         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
457         if (r < 0) {
458                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
459                 return r;
460         }
461
462         assert(len >= 9);
463         data = (const uint8_t*) data + 9;
464         len -= 9;
465
466         fd = mkostemp(path, O_WRONLY);
467         if (fd < 0) {
468                 log_error("Failed to create temporary file: %m");
469                 return -errno;
470         }
471
472         sz = write(fd, data, len);
473         if (sz < 0) {
474                 log_error("Failed to write temporary file: %s", strerror(errno));
475                 r = -errno;
476                 goto finish;
477         }
478         if (sz != (ssize_t) len) {
479                 log_error("Short write to temporary file.");
480                 r = -EIO;
481                 goto finish;
482         }
483
484         close_nointr_nofail(fd);
485         fd = -1;
486
487         pid = fork();
488         if (pid < 0) {
489                 log_error("Failed to fork(): %m");
490                 r = -errno;
491                 goto finish;
492         }
493         if (pid == 0) {
494                 execlp("gdb", "gdb", exe, path, NULL);
495                 log_error("Failed to invoke gdb: %m");
496                 _exit(1);
497         }
498
499         r = wait_for_terminate(pid, &st);
500         if (r < 0) {
501                 log_error("Failed to wait for gdb: %m");
502                 goto finish;
503         }
504
505         r = st.si_code == CLD_EXITED ? st.si_status : 255;
506
507 finish:
508         unlink(path);
509         return r;
510 }
511
512 int main(int argc, char *argv[]) {
513         sd_journal *j = NULL;
514         const char* match;
515         Iterator it;
516         int r = 0;
517
518         setlocale(LC_ALL, "");
519         log_parse_environment();
520         log_open();
521
522         matches = new_matches();
523         if (!matches) {
524                 r = -ENOMEM;
525                 goto end;
526         }
527
528         r = parse_argv(argc, argv);
529         if (r < 0)
530                 goto end;
531
532         if (arg_action == ACTION_NONE)
533                 goto end;
534
535         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
536         if (r < 0) {
537                 log_error("Failed to open journal: %s", strerror(-r));
538                 goto end;
539         }
540
541         SET_FOREACH(match, matches, it) {
542                 r = sd_journal_add_match(j, match, strlen(match));
543                 if (r != 0) {
544                         log_error("Failed to add match '%s': %s",
545                                   match, strerror(-r));
546                         goto end;
547                 }
548         }
549
550         switch(arg_action) {
551
552         case ACTION_LIST:
553                 if (!arg_no_pager)
554                         pager_open();
555
556                 r = dump_list(j);
557                 break;
558
559         case ACTION_DUMP:
560                 r = dump_core(j);
561                 break;
562
563         case  ACTION_GDB:
564                 r = run_gdb(j);
565                 break;
566
567         default:
568                 assert_not_reached("Shouldn't be here");
569         }
570
571 end:
572         if (j)
573                 sd_journal_close(j);
574
575         set_free_free(matches);
576
577         pager_close();
578
579         if (output)
580                 fclose(output);
581
582         return r >= 0 ? r : EXIT_FAILURE;
583 }