chiark / gitweb /
coredumpctl: show timestamps in list
[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 int 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         usec_t t;
242         char buf[FORMAT_TIMESTAMP_MAX];
243         int r;
244
245         SD_JOURNAL_FOREACH_DATA(j, d, l) {
246                 retrieve(d, l, "COREDUMP_PID", &pid);
247                 retrieve(d, l, "COREDUMP_PID", &pid);
248                 retrieve(d, l, "COREDUMP_UID", &uid);
249                 retrieve(d, l, "COREDUMP_GID", &gid);
250                 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
251                 retrieve(d, l, "COREDUMP_EXE", &exe);
252                 if (!exe)
253                         retrieve(d, l, "COREDUMP_COMM", &exe);
254                 if (!exe)
255                         retrieve(d, l, "COREDUMP_CMDLINE", &exe);
256         }
257
258         if (!pid && !uid && !gid && !sgnl && !exe) {
259                 log_warning("Empty coredump log entry");
260                 return -EINVAL;
261         }
262
263         r = sd_journal_get_realtime_usec(j, &t);
264         if (r < 0) {
265                 log_error("Failed to get realtime timestamp: %s", strerror(-r));
266                 return r;
267         }
268
269         format_timestamp(buf, sizeof(buf), t);
270
271         if (!had_header)
272                 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
273                         FORMAT_TIMESTAMP_MAX-1, "TIME",
274                         6, "PID",
275                         5, "UID",
276                         5, "GID",
277                         3, "SIG",
278                            "EXE");
279
280         fprintf(file, "%*s %*s %*s %*s %*s %s\n",
281                 FORMAT_TIMESTAMP_MAX-1, buf,
282                 6, pid,
283                 5, uid,
284                 5, gid,
285                 3, sgnl,
286                 exe);
287
288         return 0;
289 }
290
291 static int dump_list(sd_journal *j) {
292         int found = 0;
293
294         assert(j);
295
296         SD_JOURNAL_FOREACH(j)
297                 print_entry(stdout, j, found++);
298
299         if (!found) {
300                 log_notice("No coredumps found");
301                 return -ESRCH;
302         }
303
304         return 0;
305 }
306
307 static int dump_core(sd_journal* j) {
308         const char *data;
309         size_t len, ret;
310         int r;
311
312         assert(j);
313
314         r = sd_journal_seek_tail(j);
315         if (r == 0)
316                 r = sd_journal_previous(j);
317         if (r < 0) {
318                 log_error("Failed to search journal: %s", strerror(-r));
319                 return r;
320         }
321
322         if (r == 0) {
323                 log_error("No match found");
324                 return -ESRCH;
325         }
326
327         r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
328         if (r != 0) {
329                 log_error("retrieve COREDUMP field: %s", strerror(-r));
330                 return r;
331         }
332
333         print_entry(output ? stdout : stderr, j, false);
334
335         if (on_tty() && !output) {
336                 log_error("Refusing to dump core to tty");
337                 return -ENOTTY;
338         }
339
340         assert(len >= 9);
341
342         ret = fwrite(data+9, len-9, 1, output ? output : stdout);
343         if (ret != 1) {
344                 log_error("dumping coredump: %m (%zu)", ret);
345                 return -errno;
346         }
347
348         r = sd_journal_previous(j);
349         if (r >= 0)
350                 log_warning("More than one entry matches, ignoring rest.\n");
351
352         return 0;
353 }
354
355 int main(int argc, char *argv[]) {
356         sd_journal *j = NULL;
357         const char* match;
358         Iterator it;
359         int r = 0;
360
361         log_parse_environment();
362         log_open();
363
364         matches = new_matches();
365         if (!matches)
366                 goto end;
367
368         if (parse_argv(argc, argv))
369                 goto end;
370
371         if (arg_action == ACTION_NONE)
372                 goto end;
373
374         r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
375         if (r < 0) {
376                 log_error("Failed to open journal: %s", strerror(-r));
377                 goto end;
378         }
379
380         SET_FOREACH(match, matches, it) {
381                 r = sd_journal_add_match(j, match, strlen(match));
382                 if (r != 0) {
383                         log_error("Failed to add match '%s': %s",
384                                   match, strerror(-r));
385                         goto end;
386                 }
387         }
388
389         switch(arg_action) {
390         case ACTION_LIST:
391                 if (!arg_no_pager)
392                         pager_open();
393
394                 r = dump_list(j);
395                 break;
396         case ACTION_DUMP:
397                 r = dump_core(j);
398                 break;
399         case ACTION_NONE:
400                 assert_not_reached("Shouldn't be here");
401         }
402
403 end:
404         if (j)
405                 sd_journal_close(j);
406
407         set_free_free(matches);
408
409         pager_close();
410
411         if (output)
412                 fclose(output);
413
414         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
415 }