]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
7cf6b4590be44110caf732db202a3724df4ba2c3
[thirdparty/systemd.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 }