chiark / gitweb /
9dac169cc51cb402fbcb48ad477ed56c514111b0
[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
26 #include <systemd/sd-journal.h>
27
28 #include "build.h"
29 #include "set.h"
30 #include "util.h"
31 #include "log.h"
32 #include "path-util.h"
33 #include "pager.h"
34
35 static enum {
36         ACTION_NONE,
37         ACTION_LIST,
38         ACTION_DUMP,
39 } arg_action = ACTION_LIST;
40
41 static Set *matches = NULL;
42 static FILE* output = NULL;
43
44 static int arg_no_pager = false;
45
46 static Set *new_matches(void) {
47         Set *set;
48         char *tmp;
49         int r;
50
51         set = set_new(trivial_hash_func, trivial_compare_func);
52         if (!set) {
53                 log_oom();
54                 return NULL;
55         }
56
57         tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
58         if (!tmp) {
59                 log_oom();
60                 set_clear_free(set);
61                 return NULL;
62         }
63
64         r = set_put(set, tmp);
65         if (r < 0) {
66                 log_error("failed to add to set: %s", strerror(-r));
67                 free(tmp);
68                 set_clear_free(set);
69                 return NULL;
70         }
71
72         return set;
73 }
74
75 static int help(void) {
76         printf("%s [OPTIONS...] [MATCHES...]\n\n"
77                "List or retrieve coredumps from the journal.\n\n"
78                "Flags:\n"
79                "  -o --output=FILE  Write output to FILE\n"
80                "     --no-pager     Do not pipe output into a pager\n"
81
82                "Commands:\n"
83                "  -h --help         Show this help\n"
84                "  --version         Print version string\n"
85                "  list              List available coredumps\n"
86                "  dump PID          Print coredump to stdout\n"
87                "  dump PATH         Print coredump to stdout\n"
88                , program_invocation_short_name);
89
90         return 0;
91 }
92
93 static int add_match(Set *set, const char *match) {
94         int r = -ENOMEM;
95         unsigned pid;
96         const char* prefix;
97         char *pattern = NULL;
98         char _cleanup_free_ *p = NULL;
99
100         if (strchr(match, '='))
101                 prefix = "";
102         else if (strchr(match, '/')) {
103                 p = path_make_absolute_cwd(match);
104                 if (!p)
105                         goto fail;
106
107                 match = p;
108                 prefix = "COREDUMP_EXE=";
109         }
110         else if (safe_atou(match, &pid) == 0)
111                 prefix = "COREDUMP_PID=";
112         else
113                 prefix = "COREDUMP_COMM=";
114
115         pattern = strjoin(prefix, match, NULL);
116         if (!pattern)
117                 goto fail;
118
119         r = set_put(set, pattern);
120         if (r < 0) {
121                 log_error("failed to add pattern '%s': %s",
122                           pattern, strerror(-r));
123                 goto fail;
124         }
125         log_debug("Added pattern: %s", pattern);
126
127         return 0;
128 fail:
129         free(pattern);
130         log_error("failed to add match: %s", strerror(-r));
131         return r;
132 }
133
134 static int parse_argv(int argc, char *argv[]) {
135         enum {
136                 ARG_VERSION = 0x100,
137                 ARG_NO_PAGER,
138         };
139
140         int r, c;
141
142         static const struct option options[] = {
143                 { "help",         no_argument,       NULL, 'h'              },
144                 { "version" ,     no_argument,       NULL, ARG_VERSION      },
145                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER     },
146                 { "output",       required_argument, NULL, 'o'              },
147         };
148
149         assert(argc >= 0);
150         assert(argv);
151
152         while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
153                 switch(c) {
154                 case 'h':
155                         help();
156                         arg_action = ACTION_NONE;
157                         return 0;
158
159                 case ARG_VERSION:
160                         puts(PACKAGE_STRING);
161                         puts(DISTRIBUTION);
162                         puts(SYSTEMD_FEATURES);
163                         arg_action = ACTION_NONE;
164                         return 0;
165
166                 case ARG_NO_PAGER:
167                         arg_no_pager = true;
168                         break;
169
170                 case 'o':
171                         if (output) {
172                                 log_error("cannot set output more than once");
173                                 return -EINVAL;
174                         }
175
176                         output = fopen(optarg, "we");
177                         if (!output) {
178                                 log_error("writing to '%s': %m", optarg);
179                                 return -errno;
180                         }
181
182                         break;
183                 default:
184                         log_error("Unknown option code %c", c);
185                         return -EINVAL;
186                 }
187
188         if (optind < argc) {
189                 const char *cmd = argv[optind++];
190                 if(streq(cmd, "list"))
191                         arg_action = ACTION_LIST;
192                 else if (streq(cmd, "dump"))
193                         arg_action = ACTION_DUMP;
194                 else {
195                         log_error("Unknown action '%s'", cmd);
196                         return -EINVAL;
197                 }
198         }
199
200         while (optind < argc) {
201                 r = add_match(matches, argv[optind]);
202                 if (r != 0)
203                         return r;
204                 optind++;
205         }
206
207         return 0;
208 }
209
210 static int retrieve(const void *data,
211                     size_t len,
212                     const char *name,
213                     const char **var) {
214
215         size_t field;
216
217         field = strlen(name) + 1; /* name + "=" */
218
219         if (len < field)
220                 return 0;
221
222         if (memcmp(data, name, field - 1) != 0)
223                 return 0;
224
225         if (((const char*) data)[field - 1] != '=')
226                 return 0;
227
228         *var = strndup((const char*)data + field, len - field);
229         if (!var)
230                 return log_oom();
231
232         return 0;
233 }
234
235 static void print_entry(FILE* file, sd_journal *j, int had_header) {
236         const char _cleanup_free_
237                 *pid = NULL, *uid = NULL, *gid = NULL,
238                 *sgnl = NULL, *exe = NULL;
239         const void *d;
240         size_t l;
241
242         SD_JOURNAL_FOREACH_DATA(j, d, l) {
243                 retrieve(d, l, "COREDUMP_PID", &pid);
244                 retrieve(d, l, "COREDUMP_PID", &pid);
245                 retrieve(d, l, "COREDUMP_UID", &uid);
246                 retrieve(d, l, "COREDUMP_GID", &gid);
247                 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
248                 retrieve(d, l, "COREDUMP_EXE", &exe);
249                 if (!exe)
250                         retrieve(d, l, "COREDUMP_COMM", &exe);
251                 if (!exe)
252                         retrieve(d, l, "COREDUMP_CMDLINE", &exe);
253         }
254
255         if (!pid && !uid && !gid && !sgnl && !exe) {
256                 log_warning("empty coredump log entry");
257                 return;
258         }
259
260         if (!had_header)
261                 fprintf(file, "%*s %*s %*s %*s %s\n",
262                         6, "PID",
263                         5, "UID",
264                         5, "GID",
265                         3, "sig",
266                         "exe");
267
268         fprintf(file, "%*s %*s %*s %*s %s\n",
269                 6, pid,
270                 5, uid,
271                 5, gid,
272                 3, sgnl,
273                 exe);
274 }
275
276 static int dump_list(sd_journal *j) {
277         int found = 0;
278
279         assert(j);
280
281         SD_JOURNAL_FOREACH(j)
282                 print_entry(stdout, j, found++);
283
284         if (!found) {
285                 log_error("no coredumps found");
286                 return -ESRCH;
287         }
288
289         return 0;
290 }
291
292 static int dump_core(sd_journal* j) {
293         const char *data;
294         size_t len, ret;
295         int r;
296
297         assert(j);
298
299         r = sd_journal_seek_tail(j);
300         if (r == 0)
301                 r = sd_journal_previous(j);
302         if (r < 0) {
303                 log_error("Failed to search journal: %s", strerror(-r));
304                 return r;
305         }
306
307         if (r == 0) {
308                 log_error("No match found");
309                 return -ESRCH;
310         }
311
312         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
313         if (r != 0) {
314                 log_error("retrieve COREDUMP field: %s", strerror(-r));
315                 return r;
316         }
317
318         print_entry(output ? stdout : stderr, j, false);
319
320         if (on_tty() && !output) {
321                 log_error("Refusing to dump core to tty");
322                 return -ENOTTY;
323         }
324
325         assert(len >= 9);
326
327         ret = fwrite(data+9, len-9, 1, output ? output : stdout);
328         if (ret != 1) {
329                 log_error("dumping coredump: %m (%zu)", ret);
330                 return -errno;
331         }
332
333         r = sd_journal_previous(j);
334         if (r >= 0)
335                 log_warning("More than one entry matches, ignoring rest.\n");
336
337         return 0;
338 }
339
340 int main(int argc, char *argv[]) {
341         sd_journal *j = NULL;
342         const char* match;
343         Iterator it;
344         int r = 0;
345
346         log_parse_environment();
347         log_open();
348
349         matches = new_matches();
350         if (!matches)
351                 goto end;
352
353         if (parse_argv(argc, argv))
354                 goto end;
355
356         if (arg_action == ACTION_NONE)
357                 goto end;
358
359         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
360         if (r < 0) {
361                 log_error("Failed to open journal: %s", strerror(-r));
362                 goto end;
363         }
364
365         SET_FOREACH(match, matches, it) {
366                 r = sd_journal_add_match(j, match, strlen(match));
367                 if (r != 0) {
368                         log_error("Failed to add match '%s': %s",
369                                   match, strerror(-r));
370                         goto end;
371                 }
372         }
373
374         switch(arg_action) {
375         case ACTION_LIST:
376                 if (!arg_no_pager)
377                         pager_open();
378
379                 r = dump_list(j);
380                 break;
381         case ACTION_DUMP:
382                 r = dump_core(j);
383                 break;
384         case ACTION_NONE:
385                 assert_not_reached("Shouldn't be here");
386         }
387
388 end:
389         if (j)
390                 sd_journal_close(j);
391
392         set_free_free(matches);
393
394         pager_close();
395
396         if (output)
397                 fclose(output);
398
399         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
400 }