chiark / gitweb /
coredumpctl: check return of strndup
[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         /* 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 *j = NULL;
524         const char* match;
525         Iterator it;
526         int r = 0;
527
528         setlocale(LC_ALL, "");
529         log_parse_environment();
530         log_open();
531
532         matches = new_matches();
533         if (!matches) {
534                 r = -ENOMEM;
535                 goto end;
536         }
537
538         r = parse_argv(argc, argv);
539         if (r < 0)
540                 goto end;
541
542         if (arg_action == ACTION_NONE)
543                 goto end;
544
545         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
546         if (r < 0) {
547                 log_error("Failed to open journal: %s", strerror(-r));
548                 goto end;
549         }
550
551         SET_FOREACH(match, matches, it) {
552                 r = sd_journal_add_match(j, match, strlen(match));
553                 if (r != 0) {
554                         log_error("Failed to add match '%s': %s",
555                                   match, strerror(-r));
556                         goto end;
557                 }
558         }
559
560         switch(arg_action) {
561
562         case ACTION_LIST:
563                 if (!arg_no_pager)
564                         pager_open();
565
566                 r = dump_list(j);
567                 break;
568
569         case ACTION_DUMP:
570                 r = dump_core(j);
571                 break;
572
573         case  ACTION_GDB:
574                 r = run_gdb(j);
575                 break;
576
577         default:
578                 assert_not_reached("Shouldn't be here");
579         }
580
581 end:
582         if (j)
583                 sd_journal_close(j);
584
585         set_free_free(matches);
586
587         pager_close();
588
589         if (output)
590                 fclose(output);
591
592         return r >= 0 ? r : EXIT_FAILURE;
593 }