]>
Commit | Line | Data |
---|---|---|
86aa7ba4 LP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2012 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 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 | General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <time.h> | |
23 | #include <assert.h> | |
24 | #include <errno.h> | |
df50185b | 25 | #include <sys/poll.h> |
55d7bfc1 | 26 | #include <string.h> |
86aa7ba4 LP |
27 | |
28 | #include "logs-show.h" | |
29 | #include "log.h" | |
30 | #include "util.h" | |
31 | ||
32 | #define PRINT_THRESHOLD 128 | |
33 | ||
34 | static bool contains_unprintable(const void *p, size_t l) { | |
35 | const char *j; | |
36 | ||
37 | for (j = p; j < (const char *) p + l; j++) | |
38 | if (*j < ' ' || *j >= 127) | |
39 | return true; | |
40 | ||
41 | return false; | |
42 | } | |
43 | ||
55d7bfc1 LP |
44 | static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { |
45 | size_t fl, nl; | |
46 | void *buf; | |
47 | ||
48 | assert(data); | |
49 | assert(field); | |
50 | assert(target); | |
51 | assert(target_size); | |
52 | ||
53 | fl = strlen(field); | |
54 | if (length < fl) | |
55 | return 0; | |
56 | ||
57 | if (memcmp(data, field, fl)) | |
58 | return 0; | |
59 | ||
60 | nl = length - fl; | |
61 | buf = memdup((const char*) data + fl, nl); | |
62 | if (!buf) { | |
63 | log_error("Out of memory"); | |
64 | return -ENOMEM; | |
65 | } | |
66 | ||
6c1e6b98 | 67 | free(*target); |
55d7bfc1 LP |
68 | *target = buf; |
69 | *target_size = nl; | |
70 | ||
71 | return 1; | |
72 | } | |
73 | ||
74 | static bool shall_print(bool show_all, char *p, size_t l) { | |
75 | if (show_all) | |
76 | return true; | |
77 | ||
78 | if (l > PRINT_THRESHOLD) | |
79 | return false; | |
80 | ||
81 | if (contains_unprintable(p, l)) | |
82 | return false; | |
83 | ||
84 | return true; | |
85 | } | |
86 | ||
86aa7ba4 LP |
87 | static int output_short(sd_journal *j, unsigned line, bool show_all) { |
88 | int r; | |
89 | uint64_t realtime; | |
90 | time_t t; | |
91 | struct tm tm; | |
92 | char buf[64]; | |
93 | const void *data; | |
94 | size_t length; | |
95 | size_t n = 0; | |
6c1e6b98 LP |
96 | char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL; |
97 | size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0; | |
86aa7ba4 LP |
98 | |
99 | assert(j); | |
100 | ||
55d7bfc1 LP |
101 | SD_JOURNAL_FOREACH_DATA(j, data, length) { |
102 | ||
103 | r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len); | |
104 | if (r < 0) | |
105 | goto finish; | |
106 | else if (r > 0) | |
107 | continue; | |
108 | ||
4cd9a9d9 | 109 | r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len); |
55d7bfc1 LP |
110 | if (r < 0) |
111 | goto finish; | |
112 | else if (r > 0) | |
113 | continue; | |
114 | ||
115 | r = parse_field(data, length, "_COMM=", &comm, &comm_len); | |
116 | if (r < 0) | |
117 | goto finish; | |
118 | else if (r > 0) | |
119 | continue; | |
120 | ||
121 | r = parse_field(data, length, "_PID=", &pid, &pid_len); | |
122 | if (r < 0) | |
123 | goto finish; | |
124 | else if (r > 0) | |
125 | continue; | |
126 | ||
6c1e6b98 LP |
127 | r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len); |
128 | if (r < 0) | |
129 | goto finish; | |
130 | else if (r > 0) | |
131 | continue; | |
132 | ||
55d7bfc1 LP |
133 | r = parse_field(data, length, "MESSAGE=", &message, &message_len); |
134 | if (r < 0) | |
135 | goto finish; | |
136 | } | |
137 | ||
138 | if (!message) { | |
139 | r = 0; | |
140 | goto finish; | |
141 | } | |
142 | ||
86aa7ba4 LP |
143 | r = sd_journal_get_realtime_usec(j, &realtime); |
144 | if (r < 0) { | |
145 | log_error("Failed to get realtime: %s", strerror(-r)); | |
55d7bfc1 | 146 | goto finish; |
86aa7ba4 LP |
147 | } |
148 | ||
149 | t = (time_t) (realtime / USEC_PER_SEC); | |
150 | if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) { | |
151 | log_error("Failed to format time."); | |
55d7bfc1 | 152 | goto finish; |
86aa7ba4 LP |
153 | } |
154 | ||
155 | fputs(buf, stdout); | |
156 | n += strlen(buf); | |
157 | ||
55d7bfc1 LP |
158 | if (hostname && shall_print(show_all, hostname, hostname_len)) { |
159 | printf(" %.*s", (int) hostname_len, hostname); | |
160 | n += hostname_len + 1; | |
161 | } | |
162 | ||
4cd9a9d9 LP |
163 | if (identifier && shall_print(show_all, identifier, identifier_len)) { |
164 | printf(" %.*s", (int) identifier_len, identifier); | |
165 | n += identifier_len + 1; | |
55d7bfc1 LP |
166 | } else if (comm && shall_print(show_all, comm, comm_len)) { |
167 | printf(" %.*s", (int) comm_len, comm); | |
168 | n += comm_len + 1; | |
86aa7ba4 LP |
169 | } |
170 | ||
55d7bfc1 LP |
171 | if (pid && shall_print(show_all, pid, pid_len)) { |
172 | printf("[%.*s]", (int) pid_len, pid); | |
173 | n += pid_len + 2; | |
6c1e6b98 LP |
174 | } else if (fake_pid && shall_print(show_all, fake_pid, fake_pid_len)) { |
175 | printf("[%.*s]", (int) fake_pid_len, fake_pid); | |
176 | n += fake_pid_len + 2; | |
86aa7ba4 LP |
177 | } |
178 | ||
55d7bfc1 LP |
179 | if (show_all) |
180 | printf(": %.*s\n", (int) message_len, message); | |
181 | else if (contains_unprintable(message, message_len)) | |
182 | fputs(": [blob data]\n", stdout); | |
183 | else if (message_len + n < columns()) | |
184 | printf(": %.*s\n", (int) message_len, message); | |
185 | else if (n < columns()) { | |
186 | char *e; | |
187 | ||
188 | e = ellipsize_mem(message, message_len, columns() - n - 2, 90); | |
189 | ||
190 | if (!e) | |
191 | printf(": %.*s\n", (int) message_len, message); | |
192 | else | |
193 | printf(": %s", e); | |
194 | ||
195 | free(e); | |
196 | } else | |
197 | fputs("\n", stdout); | |
198 | ||
199 | r = 0; | |
200 | ||
201 | finish: | |
202 | free(hostname); | |
4cd9a9d9 | 203 | free(identifier); |
55d7bfc1 LP |
204 | free(comm); |
205 | free(pid); | |
6c1e6b98 | 206 | free(fake_pid); |
55d7bfc1 | 207 | free(message); |
86aa7ba4 | 208 | |
55d7bfc1 | 209 | return r; |
86aa7ba4 LP |
210 | } |
211 | ||
212 | static int output_verbose(sd_journal *j, unsigned line, bool show_all) { | |
213 | const void *data; | |
214 | size_t length; | |
215 | char *cursor; | |
216 | uint64_t realtime; | |
217 | char ts[FORMAT_TIMESTAMP_MAX]; | |
218 | int r; | |
219 | ||
220 | assert(j); | |
221 | ||
222 | r = sd_journal_get_realtime_usec(j, &realtime); | |
223 | if (r < 0) { | |
224 | log_error("Failed to get realtime timestamp: %s", strerror(-r)); | |
225 | return r; | |
226 | } | |
227 | ||
228 | r = sd_journal_get_cursor(j, &cursor); | |
229 | if (r < 0) { | |
230 | log_error("Failed to get cursor: %s", strerror(-r)); | |
231 | return r; | |
232 | } | |
233 | ||
234 | printf("%s [%s]\n", | |
235 | format_timestamp(ts, sizeof(ts), realtime), | |
236 | cursor); | |
237 | ||
238 | free(cursor); | |
239 | ||
240 | SD_JOURNAL_FOREACH_DATA(j, data, length) { | |
241 | if (!show_all && (length > PRINT_THRESHOLD || | |
242 | contains_unprintable(data, length))) { | |
243 | const char *c; | |
244 | ||
245 | c = memchr(data, '=', length); | |
246 | if (!c) { | |
247 | log_error("Invalid field."); | |
248 | return -EINVAL; | |
249 | } | |
250 | ||
251 | printf("\t%.*s=[blob data]\n", | |
252 | (int) (c - (const char*) data), | |
253 | (const char*) data); | |
254 | } else | |
255 | printf("\t%.*s\n", (int) length, (const char*) data); | |
256 | } | |
257 | ||
258 | return 0; | |
259 | } | |
260 | ||
261 | static int output_export(sd_journal *j, unsigned line, bool show_all) { | |
262 | sd_id128_t boot_id; | |
263 | char sid[33]; | |
264 | int r; | |
265 | usec_t realtime, monotonic; | |
266 | char *cursor; | |
267 | const void *data; | |
268 | size_t length; | |
269 | ||
270 | assert(j); | |
271 | ||
272 | r = sd_journal_get_realtime_usec(j, &realtime); | |
273 | if (r < 0) { | |
274 | log_error("Failed to get realtime timestamp: %s", strerror(-r)); | |
275 | return r; | |
276 | } | |
277 | ||
278 | r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); | |
279 | if (r < 0) { | |
280 | log_error("Failed to get monotonic timestamp: %s", strerror(-r)); | |
281 | return r; | |
282 | } | |
283 | ||
284 | r = sd_journal_get_cursor(j, &cursor); | |
285 | if (r < 0) { | |
286 | log_error("Failed to get cursor: %s", strerror(-r)); | |
287 | return r; | |
288 | } | |
289 | ||
290 | printf(".cursor=%s\n" | |
291 | ".realtime=%llu\n" | |
292 | ".monotonic=%llu\n" | |
293 | ".boot_id=%s\n", | |
294 | cursor, | |
295 | (unsigned long long) realtime, | |
296 | (unsigned long long) monotonic, | |
297 | sd_id128_to_string(boot_id, sid)); | |
298 | ||
299 | free(cursor); | |
300 | ||
301 | SD_JOURNAL_FOREACH_DATA(j, data, length) { | |
302 | ||
303 | if (contains_unprintable(data, length)) { | |
304 | const char *c; | |
305 | uint64_t le64; | |
306 | ||
307 | c = memchr(data, '=', length); | |
308 | if (!c) { | |
309 | log_error("Invalid field."); | |
310 | return -EINVAL; | |
311 | } | |
312 | ||
313 | fwrite(data, c - (const char*) data, 1, stdout); | |
314 | fputc('\n', stdout); | |
315 | le64 = htole64(length - (c - (const char*) data) - 1); | |
316 | fwrite(&le64, sizeof(le64), 1, stdout); | |
317 | fwrite(c + 1, length - (c - (const char*) data) - 1, 1, stdout); | |
318 | } else | |
319 | fwrite(data, length, 1, stdout); | |
320 | ||
321 | fputc('\n', stdout); | |
322 | } | |
323 | ||
324 | fputc('\n', stdout); | |
325 | ||
326 | return 0; | |
327 | } | |
328 | ||
329 | static void json_escape(const char* p, size_t l) { | |
330 | ||
331 | if (contains_unprintable(p, l)) { | |
332 | bool not_first = false; | |
333 | ||
334 | fputs("[ ", stdout); | |
335 | ||
336 | while (l > 0) { | |
337 | if (not_first) | |
338 | printf(", %u", (uint8_t) *p); | |
339 | else { | |
340 | not_first = true; | |
341 | printf("%u", (uint8_t) *p); | |
342 | } | |
343 | ||
344 | p++; | |
345 | l--; | |
346 | } | |
347 | ||
348 | fputs(" ]", stdout); | |
349 | } else { | |
350 | fputc('\"', stdout); | |
351 | ||
352 | while (l > 0) { | |
353 | if (*p == '"' || *p == '\\') { | |
354 | fputc('\\', stdout); | |
355 | fputc(*p, stdout); | |
356 | } else | |
357 | fputc(*p, stdout); | |
358 | ||
359 | p++; | |
360 | l--; | |
361 | } | |
362 | ||
363 | fputc('\"', stdout); | |
364 | } | |
365 | } | |
366 | ||
367 | static int output_json(sd_journal *j, unsigned line, bool show_all) { | |
368 | uint64_t realtime, monotonic; | |
369 | char *cursor; | |
370 | const void *data; | |
371 | size_t length; | |
372 | sd_id128_t boot_id; | |
373 | char sid[33]; | |
374 | int r; | |
375 | ||
376 | assert(j); | |
377 | ||
378 | r = sd_journal_get_realtime_usec(j, &realtime); | |
379 | if (r < 0) { | |
380 | log_error("Failed to get realtime timestamp: %s", strerror(-r)); | |
381 | return r; | |
382 | } | |
383 | ||
384 | r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); | |
385 | if (r < 0) { | |
386 | log_error("Failed to get monotonic timestamp: %s", strerror(-r)); | |
387 | return r; | |
388 | } | |
389 | ||
390 | r = sd_journal_get_cursor(j, &cursor); | |
391 | if (r < 0) { | |
392 | log_error("Failed to get cursor: %s", strerror(-r)); | |
393 | return r; | |
394 | } | |
395 | ||
396 | if (line == 1) | |
397 | fputc('\n', stdout); | |
398 | else | |
399 | fputs(",\n", stdout); | |
400 | ||
401 | printf("{\n" | |
402 | "\t\".cursor\" : \"%s\",\n" | |
403 | "\t\".realtime\" : %llu,\n" | |
404 | "\t\".monotonic\" : %llu,\n" | |
405 | "\t\".boot_id\" : \"%s\"", | |
406 | cursor, | |
407 | (unsigned long long) realtime, | |
408 | (unsigned long long) monotonic, | |
409 | sd_id128_to_string(boot_id, sid)); | |
410 | ||
411 | free(cursor); | |
412 | ||
413 | SD_JOURNAL_FOREACH_DATA(j, data, length) { | |
414 | const char *c; | |
415 | ||
416 | c = memchr(data, '=', length); | |
417 | if (!c) { | |
418 | log_error("Invalid field."); | |
419 | return -EINVAL; | |
420 | } | |
421 | ||
422 | fputs(",\n\t", stdout); | |
423 | json_escape(data, c - (const char*) data); | |
424 | fputs(" : ", stdout); | |
425 | json_escape(c + 1, length - (c - (const char*) data) - 1); | |
426 | } | |
427 | ||
428 | fputs("\n}", stdout); | |
429 | fflush(stdout); | |
430 | ||
431 | return 0; | |
432 | } | |
433 | ||
434 | static int (*output_funcs[_OUTPUT_MODE_MAX])(sd_journal*j, unsigned line, bool show_all) = { | |
435 | [OUTPUT_SHORT] = output_short, | |
436 | [OUTPUT_VERBOSE] = output_verbose, | |
437 | [OUTPUT_EXPORT] = output_export, | |
438 | [OUTPUT_JSON] = output_json | |
439 | }; | |
440 | ||
df50185b LP |
441 | int output_journal(sd_journal *j, OutputMode mode, unsigned line, bool show_all) { |
442 | assert(mode >= 0); | |
86aa7ba4 LP |
443 | assert(mode < _OUTPUT_MODE_MAX); |
444 | ||
445 | return output_funcs[mode](j, line, show_all); | |
446 | } | |
447 | ||
df50185b LP |
448 | int show_journal_by_unit( |
449 | const char *unit, | |
450 | OutputMode mode, | |
86aa7ba4 LP |
451 | const char *prefix, |
452 | unsigned n_columns, | |
453 | usec_t not_before, | |
454 | unsigned how_many, | |
df50185b LP |
455 | bool show_all, |
456 | bool follow) { | |
86aa7ba4 LP |
457 | |
458 | char *m = NULL; | |
459 | sd_journal *j; | |
460 | int r; | |
df50185b LP |
461 | int fd; |
462 | unsigned line = 0; | |
463 | bool need_seek = false; | |
86aa7ba4 | 464 | |
df50185b LP |
465 | assert(mode >= 0); |
466 | assert(mode < _OUTPUT_MODE_MAX); | |
467 | assert(unit); | |
468 | ||
469 | if (!endswith(unit, ".service") && | |
470 | !endswith(unit, ".socket") && | |
471 | !endswith(unit, ".mount") && | |
472 | !endswith(unit, ".swap")) | |
473 | return 0; | |
86aa7ba4 | 474 | |
df50185b | 475 | if (how_many <= 0) |
f4fb21c1 LP |
476 | return 0; |
477 | ||
86aa7ba4 LP |
478 | if (n_columns <= 0) |
479 | n_columns = columns(); | |
480 | ||
86aa7ba4 LP |
481 | if (!prefix) |
482 | prefix = ""; | |
483 | ||
df50185b | 484 | if (asprintf(&m, "_SYSTEMD_UNIT=%s", unit) < 0) { |
86aa7ba4 LP |
485 | r = -ENOMEM; |
486 | goto finish; | |
487 | } | |
488 | ||
489 | r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY); | |
490 | if (r < 0) | |
491 | goto finish; | |
492 | ||
df50185b LP |
493 | fd = sd_journal_get_fd(j); |
494 | if (fd < 0) | |
495 | goto finish; | |
496 | ||
86aa7ba4 LP |
497 | r = sd_journal_add_match(j, m, strlen(m)); |
498 | if (r < 0) | |
499 | goto finish; | |
500 | ||
501 | r = sd_journal_seek_tail(j); | |
502 | if (r < 0) | |
503 | goto finish; | |
504 | ||
df50185b LP |
505 | r = sd_journal_previous_skip(j, how_many); |
506 | if (r < 0) | |
507 | goto finish; | |
86aa7ba4 | 508 | |
df50185b LP |
509 | if (mode == OUTPUT_JSON) { |
510 | fputc('[', stdout); | |
511 | fflush(stdout); | |
512 | } | |
86aa7ba4 | 513 | |
df50185b LP |
514 | for (;;) { |
515 | for (;;) { | |
516 | usec_t usec; | |
517 | ||
518 | if (need_seek) { | |
519 | r = sd_journal_next(j); | |
520 | if (r < 0) | |
521 | goto finish; | |
522 | } | |
523 | ||
524 | if (r == 0) | |
525 | break; | |
86aa7ba4 | 526 | |
df50185b LP |
527 | need_seek = true; |
528 | ||
529 | if (not_before > 0) { | |
530 | r = sd_journal_get_monotonic_usec(j, &usec, NULL); | |
531 | ||
532 | /* -ESTALE is returned if the | |
533 | timestamp is not from this boot */ | |
534 | if (r == -ESTALE) | |
535 | continue; | |
536 | else if (r < 0) | |
537 | goto finish; | |
538 | ||
539 | if (usec < not_before) | |
540 | continue; | |
541 | } | |
542 | ||
543 | line ++; | |
544 | ||
545 | r = output_journal(j, mode, line, show_all); | |
546 | if (r < 0) | |
547 | goto finish; | |
548 | } | |
549 | ||
550 | if (!follow) | |
86aa7ba4 LP |
551 | break; |
552 | ||
df50185b | 553 | r = fd_wait_for_event(fd, POLLIN); |
86aa7ba4 LP |
554 | if (r < 0) |
555 | goto finish; | |
df50185b LP |
556 | |
557 | r = sd_journal_process(j); | |
558 | if (r < 0) | |
559 | goto finish; | |
560 | ||
86aa7ba4 LP |
561 | } |
562 | ||
df50185b LP |
563 | if (mode == OUTPUT_JSON) |
564 | fputs("\n]\n", stdout); | |
565 | ||
86aa7ba4 LP |
566 | finish: |
567 | if (m) | |
568 | free(m); | |
569 | ||
570 | if (j) | |
571 | sd_journal_close(j); | |
572 | ||
573 | return r; | |
574 | } | |
df50185b LP |
575 | |
576 | static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { | |
577 | [OUTPUT_SHORT] = "short", | |
578 | [OUTPUT_VERBOSE] = "verbose", | |
579 | [OUTPUT_EXPORT] = "export", | |
580 | [OUTPUT_JSON] = "json" | |
581 | }; | |
582 | ||
583 | DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode); |