chiark / gitweb /
coredumpctl: add missing -F, --field option to help
[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 <stdio.h>
23 #include <string.h>
24 #include <getopt.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27
28 #include <systemd/sd-journal.h>
29
30 #include "build.h"
31 #include "set.h"
32 #include "util.h"
33 #include "log.h"
34 #include "path-util.h"
35 #include "pager.h"
36
37 static enum {
38         ACTION_NONE,
39         ACTION_LIST,
40         ACTION_DUMP,
41         ACTION_GDB,
42 } arg_action = ACTION_LIST;
43
44 static Set *matches = NULL;
45 static FILE* output = NULL;
46 static char* field = NULL;
47
48 static int arg_no_pager = false;
49 static int arg_no_legend = false;
50
51 static Set *new_matches(void) {
52         Set *set;
53         char *tmp;
54         int r;
55
56         set = set_new(trivial_hash_func, trivial_compare_func);
57         if (!set) {
58                 log_oom();
59                 return NULL;
60         }
61
62         tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
63         if (!tmp) {
64                 log_oom();
65                 set_free(set);
66                 return NULL;
67         }
68
69         r = set_put(set, tmp);
70         if (r < 0) {
71                 log_error("failed to add to set: %s", strerror(-r));
72                 free(tmp);
73                 set_free(set);
74                 return NULL;
75         }
76
77         return set;
78 }
79
80 static int help(void) {
81         printf("%s [OPTIONS...] [MATCHES...]\n\n"
82                "List or retrieve coredumps from the journal.\n\n"
83                "Flags:\n"
84                "  -o --output=FILE  Write output to FILE\n"
85                "     --no-pager     Do not pipe output into a pager\n"
86
87                "Commands:\n"
88                "  -h --help         Show this help\n"
89                "  --version         Print version string\n"
90                "  -F --field=FIELD  List all values a certain field takes\n"
91                "  gdb               Start gdb for the first matching coredump\n"
92                "  list              List available coredumps\n"
93                "  dump PID          Print coredump to stdout\n"
94                "  dump PATH         Print coredump to stdout\n"
95                , program_invocation_short_name);
96
97         return 0;
98 }
99
100 static int add_match(Set *set, const char *match) {
101         int r = -ENOMEM;
102         unsigned pid;
103         const char* prefix;
104         char *pattern = NULL;
105         char _cleanup_free_ *p = NULL;
106
107         if (strchr(match, '='))
108                 prefix = "";
109         else if (strchr(match, '/')) {
110                 p = path_make_absolute_cwd(match);
111                 if (!p)
112                         goto fail;
113
114                 match = p;
115                 prefix = "COREDUMP_EXE=";
116         }
117         else if (safe_atou(match, &pid) == 0)
118                 prefix = "COREDUMP_PID=";
119         else
120                 prefix = "COREDUMP_COMM=";
121
122         pattern = strjoin(prefix, match, NULL);
123         if (!pattern)
124                 goto fail;
125
126         r = set_put(set, pattern);
127         if (r < 0) {
128                 log_error("failed to add pattern '%s': %s",
129                           pattern, strerror(-r));
130                 goto fail;
131         }
132         log_debug("Added pattern: %s", pattern);
133
134         return 0;
135 fail:
136         free(pattern);
137         log_error("failed to add match: %s", strerror(-r));
138         return r;
139 }
140
141 static int parse_argv(int argc, char *argv[]) {
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(DISTRIBUTION);
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         SD_JOURNAL_FOREACH(j) {
345                 if (field)
346                         print_field(stdout, j);
347                 else
348                         print_entry(stdout, j, found++);
349         }
350
351         if (!field && !found) {
352                 log_notice("No coredumps found");
353                 return -ESRCH;
354         }
355
356         return 0;
357 }
358
359 static int focus(sd_journal *j) {
360         int r;
361
362         r = sd_journal_seek_tail(j);
363         if (r == 0)
364                 r = sd_journal_previous(j);
365         if (r < 0) {
366                 log_error("Failed to search journal: %s", strerror(-r));
367                 return r;
368         }
369         if (r == 0) {
370                 log_error("No match found");
371                 return -ESRCH;
372         }
373         return r;
374 }
375
376 static int dump_core(sd_journal* j) {
377         const void *data;
378         size_t len, ret;
379         int r;
380
381         assert(j);
382
383         r = focus(j);
384         if (r < 0)
385                 return r;
386
387         print_entry(output ? stdout : stderr, j, false);
388
389         if (on_tty() && !output) {
390                 log_error("Refusing to dump core to tty");
391                 return -ENOTTY;
392         }
393
394         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
395         if (r < 0) {
396                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
397                 return r;
398         }
399
400         assert(len >= 9);
401         data = (const uint8_t*) data + 9;
402         len -= 9;
403
404         ret = fwrite(data, len, 1, output ? output : stdout);
405         if (ret != 1) {
406                 log_error("dumping coredump: %m (%zu)", ret);
407                 return -errno;
408         }
409
410         r = sd_journal_previous(j);
411         if (r >= 0)
412                 log_warning("More than one entry matches, ignoring rest.\n");
413
414         return 0;
415 }
416
417 static int run_gdb(sd_journal *j) {
418         char path[] = "/var/tmp/coredump-XXXXXX";
419         const void *data;
420         size_t len;
421         ssize_t sz;
422         pid_t pid;
423         _cleanup_free_ char *exe = NULL;
424         int r;
425         _cleanup_close_ int fd = -1;
426         siginfo_t st;
427
428         assert(j);
429
430         r = focus(j);
431         if (r < 0)
432                 return r;
433
434         print_entry(stdout, j, false);
435
436         r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
437         if (r < 0) {
438                 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
439                 return r;
440         }
441
442         assert(len >= 13);
443         data = (const uint8_t*) data + 13;
444         len -= 13;
445
446         exe = strndup(data, len);
447         if (!exe)
448                 return log_oom();
449
450         if (endswith(exe, " (deleted)")) {
451                 log_error("Binary already deleted.");
452                 return -ENOENT;
453         }
454
455         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
456         if (r < 0) {
457                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
458                 return r;
459         }
460
461         assert(len >= 9);
462         data = (const uint8_t*) data + 9;
463         len -= 9;
464
465         fd = mkostemp(path, O_WRONLY);
466         if (fd < 0) {
467                 log_error("Failed to create temporary file: %m");
468                 return -errno;
469         }
470
471         sz = write(fd, data, len);
472         if (sz < 0) {
473                 log_error("Failed to write temporary file: %s", strerror(errno));
474                 r = -errno;
475                 goto finish;
476         }
477         if (sz != (ssize_t) len) {
478                 log_error("Short write to temporary file.");
479                 r = -EIO;
480                 goto finish;
481         }
482
483         close_nointr_nofail(fd);
484         fd = -1;
485
486         pid = fork();
487         if (pid < 0) {
488                 log_error("Failed to fork(): %m");
489                 r = -errno;
490                 goto finish;
491         }
492         if (pid == 0) {
493                 execlp("gdb", "gdb", exe, path, NULL);
494                 log_error("Failed to invoke gdb: %m");
495                 _exit(1);
496         }
497
498         r = wait_for_terminate(pid, &st);
499         if (r < 0) {
500                 log_error("Failed to wait for gdb: %m");
501                 goto finish;
502         }
503
504         r = st.si_code == CLD_EXITED ? st.si_status : 255;
505
506 finish:
507         unlink(path);
508         return r;
509 }
510
511 int main(int argc, char *argv[]) {
512         sd_journal *j = NULL;
513         const char* match;
514         Iterator it;
515         int r = 0;
516
517         log_parse_environment();
518         log_open();
519
520         matches = new_matches();
521         if (!matches) {
522                 r = -ENOMEM;
523                 goto end;
524         }
525
526         r = parse_argv(argc, argv);
527         if (r < 0)
528                 goto end;
529
530         if (arg_action == ACTION_NONE)
531                 goto end;
532
533         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
534         if (r < 0) {
535                 log_error("Failed to open journal: %s", strerror(-r));
536                 goto end;
537         }
538
539         SET_FOREACH(match, matches, it) {
540                 r = sd_journal_add_match(j, match, strlen(match));
541                 if (r != 0) {
542                         log_error("Failed to add match '%s': %s",
543                                   match, strerror(-r));
544                         goto end;
545                 }
546         }
547
548         switch(arg_action) {
549
550         case ACTION_LIST:
551                 if (!arg_no_pager)
552                         pager_open();
553
554                 r = dump_list(j);
555                 break;
556
557         case ACTION_DUMP:
558                 r = dump_core(j);
559                 break;
560
561         case  ACTION_GDB:
562                 r = run_gdb(j);
563                 break;
564
565         default:
566                 assert_not_reached("Shouldn't be here");
567         }
568
569 end:
570         if (j)
571                 sd_journal_close(j);
572
573         set_free_free(matches);
574
575         pager_close();
576
577         if (output)
578                 fclose(output);
579
580         return r >= 0 ? r : EXIT_FAILURE;
581 }