chiark / gitweb /
coredumpctl: fix program return code
[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
47 static int arg_no_pager = false;
48
49 static Set *new_matches(void) {
50         Set *set;
51         char *tmp;
52         int r;
53
54         set = set_new(trivial_hash_func, trivial_compare_func);
55         if (!set) {
56                 log_oom();
57                 return NULL;
58         }
59
60         tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
61         if (!tmp) {
62                 log_oom();
63                 set_free(set);
64                 return NULL;
65         }
66
67         r = set_put(set, tmp);
68         if (r < 0) {
69                 log_error("failed to add to set: %s", strerror(-r));
70                 free(tmp);
71                 set_free(set);
72                 return NULL;
73         }
74
75         return set;
76 }
77
78 static int help(void) {
79         printf("%s [OPTIONS...] [MATCHES...]\n\n"
80                "List or retrieve coredumps from the journal.\n\n"
81                "Flags:\n"
82                "  -o --output=FILE  Write output to FILE\n"
83                "     --no-pager     Do not pipe output into a pager\n"
84
85                "Commands:\n"
86                "  -h --help         Show this help\n"
87                "  --version         Print version string\n"
88                "  list              List available coredumps\n"
89                "  dump PID          Print coredump to stdout\n"
90                "  dump PATH         Print coredump to stdout\n"
91                , program_invocation_short_name);
92
93         return 0;
94 }
95
96 static int add_match(Set *set, const char *match) {
97         int r = -ENOMEM;
98         unsigned pid;
99         const char* prefix;
100         char *pattern = NULL;
101         char _cleanup_free_ *p = NULL;
102
103         if (strchr(match, '='))
104                 prefix = "";
105         else if (strchr(match, '/')) {
106                 p = path_make_absolute_cwd(match);
107                 if (!p)
108                         goto fail;
109
110                 match = p;
111                 prefix = "COREDUMP_EXE=";
112         }
113         else if (safe_atou(match, &pid) == 0)
114                 prefix = "COREDUMP_PID=";
115         else
116                 prefix = "COREDUMP_COMM=";
117
118         pattern = strjoin(prefix, match, NULL);
119         if (!pattern)
120                 goto fail;
121
122         r = set_put(set, pattern);
123         if (r < 0) {
124                 log_error("failed to add pattern '%s': %s",
125                           pattern, strerror(-r));
126                 goto fail;
127         }
128         log_debug("Added pattern: %s", pattern);
129
130         return 0;
131 fail:
132         free(pattern);
133         log_error("failed to add match: %s", strerror(-r));
134         return r;
135 }
136
137 static int parse_argv(int argc, char *argv[]) {
138         enum {
139                 ARG_VERSION = 0x100,
140                 ARG_NO_PAGER,
141         };
142
143         int r, c;
144
145         static const struct option options[] = {
146                 { "help",         no_argument,       NULL, 'h'           },
147                 { "version" ,     no_argument,       NULL, ARG_VERSION   },
148                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER  },
149                 { "output",       required_argument, NULL, 'o'           },
150                 { NULL,           0,                 NULL, 0             }
151         };
152
153         assert(argc >= 0);
154         assert(argv);
155
156         while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
157                 switch(c) {
158                 case 'h':
159                         help();
160                         arg_action = ACTION_NONE;
161                         return 0;
162
163                 case ARG_VERSION:
164                         puts(PACKAGE_STRING);
165                         puts(DISTRIBUTION);
166                         puts(SYSTEMD_FEATURES);
167                         arg_action = ACTION_NONE;
168                         return 0;
169
170                 case ARG_NO_PAGER:
171                         arg_no_pager = true;
172                         break;
173
174                 case 'o':
175                         if (output) {
176                                 log_error("cannot set output more than once");
177                                 return -EINVAL;
178                         }
179
180                         output = fopen(optarg, "we");
181                         if (!output) {
182                                 log_error("writing to '%s': %m", optarg);
183                                 return -errno;
184                         }
185
186                         break;
187
188                 case '?':
189                         return -EINVAL;
190
191                 default:
192                         log_error("Unknown option code %c", c);
193                         return -EINVAL;
194                 }
195
196         if (optind < argc) {
197                 const char *cmd = argv[optind++];
198                 if(streq(cmd, "list"))
199                         arg_action = ACTION_LIST;
200                 else if (streq(cmd, "dump"))
201                         arg_action = ACTION_DUMP;
202                 else if (streq(cmd, "gdb"))
203                         arg_action = ACTION_GDB;
204                 else {
205                         log_error("Unknown action '%s'", cmd);
206                         return -EINVAL;
207                 }
208         }
209
210         while (optind < argc) {
211                 r = add_match(matches, argv[optind]);
212                 if (r != 0)
213                         return r;
214                 optind++;
215         }
216
217         return 0;
218 }
219
220 static int retrieve(const void *data,
221                     size_t len,
222                     const char *name,
223                     const char **var) {
224
225         size_t field;
226
227         field = strlen(name) + 1; /* name + "=" */
228
229         if (len < field)
230                 return 0;
231
232         if (memcmp(data, name, field - 1) != 0)
233                 return 0;
234
235         if (((const char*) data)[field - 1] != '=')
236                 return 0;
237
238         *var = strndup((const char*)data + field, len - field);
239         if (!var)
240                 return log_oom();
241
242         return 0;
243 }
244
245 static int print_entry(FILE* file, sd_journal *j, int had_header) {
246         const char _cleanup_free_
247                 *pid = NULL, *uid = NULL, *gid = NULL,
248                 *sgnl = NULL, *exe = NULL;
249         const void *d;
250         size_t l;
251         usec_t t;
252         char buf[FORMAT_TIMESTAMP_MAX];
253         int r;
254
255         SD_JOURNAL_FOREACH_DATA(j, d, l) {
256                 retrieve(d, l, "COREDUMP_PID", &pid);
257                 retrieve(d, l, "COREDUMP_PID", &pid);
258                 retrieve(d, l, "COREDUMP_UID", &uid);
259                 retrieve(d, l, "COREDUMP_GID", &gid);
260                 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
261                 retrieve(d, l, "COREDUMP_EXE", &exe);
262                 if (!exe)
263                         retrieve(d, l, "COREDUMP_COMM", &exe);
264                 if (!exe)
265                         retrieve(d, l, "COREDUMP_CMDLINE", &exe);
266         }
267
268         if (!pid && !uid && !gid && !sgnl && !exe) {
269                 log_warning("Empty coredump log entry");
270                 return -EINVAL;
271         }
272
273         r = sd_journal_get_realtime_usec(j, &t);
274         if (r < 0) {
275                 log_error("Failed to get realtime timestamp: %s", strerror(-r));
276                 return r;
277         }
278
279         format_timestamp(buf, sizeof(buf), t);
280
281         if (!had_header)
282                 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
283                         FORMAT_TIMESTAMP_MAX-1, "TIME",
284                         6, "PID",
285                         5, "UID",
286                         5, "GID",
287                         3, "SIG",
288                            "EXE");
289
290         fprintf(file, "%*s %*s %*s %*s %*s %s\n",
291                 FORMAT_TIMESTAMP_MAX-1, buf,
292                 6, pid,
293                 5, uid,
294                 5, gid,
295                 3, sgnl,
296                 exe);
297
298         return 0;
299 }
300
301 static int dump_list(sd_journal *j) {
302         int found = 0;
303
304         assert(j);
305
306         SD_JOURNAL_FOREACH(j)
307                 print_entry(stdout, j, found++);
308
309         if (!found) {
310                 log_notice("No coredumps found");
311                 return -ESRCH;
312         }
313
314         return 0;
315 }
316
317 static int focus(sd_journal *j) {
318         int r;
319
320         r = sd_journal_seek_tail(j);
321         if (r == 0)
322                 r = sd_journal_previous(j);
323         if (r < 0) {
324                 log_error("Failed to search journal: %s", strerror(-r));
325                 return r;
326         }
327         if (r == 0) {
328                 log_error("No match found");
329                 return -ESRCH;
330         }
331         return r;
332 }
333
334 static int dump_core(sd_journal* j) {
335         const void *data;
336         size_t len, ret;
337         int r;
338
339         assert(j);
340
341         r = focus(j);
342         if (r < 0)
343                 return r;
344
345         print_entry(output ? stdout : stderr, j, false);
346
347         if (on_tty() && !output) {
348                 log_error("Refusing to dump core to tty");
349                 return -ENOTTY;
350         }
351
352         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
353         if (r < 0) {
354                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
355                 return r;
356         }
357
358         assert(len >= 9);
359         data = (const uint8_t*) data + 9;
360         len -= 9;
361
362         ret = fwrite(data, len, 1, output ? output : stdout);
363         if (ret != 1) {
364                 log_error("dumping coredump: %m (%zu)", ret);
365                 return -errno;
366         }
367
368         r = sd_journal_previous(j);
369         if (r >= 0)
370                 log_warning("More than one entry matches, ignoring rest.\n");
371
372         return 0;
373 }
374
375 static int run_gdb(sd_journal *j) {
376         char path[] = "/var/tmp/coredump-XXXXXX";
377         const void *data;
378         size_t len;
379         ssize_t sz;
380         pid_t pid;
381         _cleanup_free_ char *exe = NULL;
382         int r;
383         _cleanup_close_ int fd = -1;
384         siginfo_t st;
385
386         assert(j);
387
388         r = focus(j);
389         if (r < 0)
390                 return r;
391
392         print_entry(stdout, j, false);
393
394         r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
395         if (r < 0) {
396                 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
397                 return r;
398         }
399
400         assert(len >= 13);
401         data = (const uint8_t*) data + 13;
402         len -= 13;
403
404         exe = strndup(data, len);
405         if (!exe)
406                 return log_oom();
407
408         if (endswith(exe, " (deleted)")) {
409                 log_error("Binary already deleted.");
410                 return -ENOENT;
411         }
412
413         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
414         if (r < 0) {
415                 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
416                 return r;
417         }
418
419         assert(len >= 9);
420         data = (const uint8_t*) data + 9;
421         len -= 9;
422
423         fd = mkostemp(path, O_WRONLY);
424         if (fd < 0) {
425                 log_error("Failed to create temporary file: %m");
426                 return -errno;
427         }
428
429         sz = write(fd, data, len);
430         if (sz < 0) {
431                 log_error("Failed to write temporary file: %s", strerror(errno));
432                 r = -errno;
433                 goto finish;
434         }
435         if (sz != (ssize_t) len) {
436                 log_error("Short write to temporary file.");
437                 r = -EIO;
438                 goto finish;
439         }
440
441         close_nointr_nofail(fd);
442         fd = -1;
443
444         pid = fork();
445         if (pid < 0) {
446                 log_error("Failed to fork(): %m");
447                 r = -errno;
448                 goto finish;
449         }
450         if (pid == 0) {
451                 execlp("gdb", "gdb", exe, path, NULL);
452                 log_error("Failed to invoke gdb: %m");
453                 _exit(1);
454         }
455
456         r = wait_for_terminate(pid, &st);
457         if (r < 0) {
458                 log_error("Failed to wait for gdb: %m");
459                 goto finish;
460         }
461
462         r = st.si_code == CLD_EXITED ? st.si_status : 255;
463
464 finish:
465         unlink(path);
466         return r;
467 }
468
469 int main(int argc, char *argv[]) {
470         sd_journal *j = NULL;
471         const char* match;
472         Iterator it;
473         int r = 0;
474
475         log_parse_environment();
476         log_open();
477
478         matches = new_matches();
479         if (!matches) {
480                 r = -ENOMEM;
481                 goto end;
482         }
483
484         r = parse_argv(argc, argv);
485         if (r < 0)
486                 goto end;
487
488         if (arg_action == ACTION_NONE)
489                 goto end;
490
491         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
492         if (r < 0) {
493                 log_error("Failed to open journal: %s", strerror(-r));
494                 goto end;
495         }
496
497         SET_FOREACH(match, matches, it) {
498                 r = sd_journal_add_match(j, match, strlen(match));
499                 if (r != 0) {
500                         log_error("Failed to add match '%s': %s",
501                                   match, strerror(-r));
502                         goto end;
503                 }
504         }
505
506         switch(arg_action) {
507
508         case ACTION_LIST:
509                 if (!arg_no_pager)
510                         pager_open();
511
512                 r = dump_list(j);
513                 break;
514
515         case ACTION_DUMP:
516                 r = dump_core(j);
517                 break;
518
519         case  ACTION_GDB:
520                 r = run_gdb(j);
521                 break;
522
523         default:
524                 assert_not_reached("Shouldn't be here");
525         }
526
527 end:
528         if (j)
529                 sd_journal_close(j);
530
531         set_free_free(matches);
532
533         pager_close();
534
535         if (output)
536                 fclose(output);
537
538         return r >= 0 ? r : EXIT_FAILURE;
539 }