]>
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 | |
5430f7f2 LP |
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 | |
86aa7ba4 LP |
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 | |
5430f7f2 | 16 | Lesser General Public License for more details. |
86aa7ba4 | 17 | |
5430f7f2 | 18 | You should have received a copy of the GNU Lesser General Public License |
86aa7ba4 LP |
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" | |
ba961854 | 31 | #include "utf8.h" |
d99ae53a | 32 | #include "hashmap.h" |
86aa7ba4 LP |
33 | |
34 | #define PRINT_THRESHOLD 128 | |
08ace05b | 35 | #define JSON_THRESHOLD 4096 |
86aa7ba4 | 36 | |
d4205751 LP |
37 | static int print_catalog(FILE *f, sd_journal *j) { |
38 | int r; | |
39 | _cleanup_free_ char *t = NULL, *z = NULL; | |
40 | ||
41 | ||
42 | r = sd_journal_get_catalog(j, &t); | |
43 | if (r < 0) | |
44 | return r; | |
45 | ||
46 | z = strreplace(strstrip(t), "\n", "\n-- "); | |
47 | if (!z) | |
48 | return log_oom(); | |
49 | ||
50 | fputs("-- ", f); | |
51 | fputs(z, f); | |
52 | fputc('\n', f); | |
53 | ||
54 | return 0; | |
55 | } | |
56 | ||
55d7bfc1 LP |
57 | static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { |
58 | size_t fl, nl; | |
59 | void *buf; | |
60 | ||
61 | assert(data); | |
62 | assert(field); | |
63 | assert(target); | |
64 | assert(target_size); | |
65 | ||
66 | fl = strlen(field); | |
67 | if (length < fl) | |
68 | return 0; | |
69 | ||
70 | if (memcmp(data, field, fl)) | |
71 | return 0; | |
72 | ||
73 | nl = length - fl; | |
bf967366 | 74 | buf = malloc(nl+1); |
0d0f0c50 SL |
75 | if (!buf) |
76 | return log_oom(); | |
55d7bfc1 | 77 | |
46b0d922 LP |
78 | memcpy(buf, (const char*) data + fl, nl); |
79 | ((char*)buf)[nl] = 0; | |
80 | ||
6c1e6b98 | 81 | free(*target); |
55d7bfc1 LP |
82 | *target = buf; |
83 | *target_size = nl; | |
84 | ||
85 | return 1; | |
86 | } | |
87 | ||
08ace05b LP |
88 | static bool shall_print(const char *p, size_t l, OutputFlags flags) { |
89 | assert(p); | |
90 | ||
91 | if (flags & OUTPUT_SHOW_ALL) | |
55d7bfc1 LP |
92 | return true; |
93 | ||
93b73b06 | 94 | if (l >= PRINT_THRESHOLD) |
55d7bfc1 LP |
95 | return false; |
96 | ||
ba961854 | 97 | if (!utf8_is_printable_n(p, l)) |
55d7bfc1 LP |
98 | return false; |
99 | ||
100 | return true; | |
101 | } | |
102 | ||
08ace05b LP |
103 | static int output_short( |
104 | FILE *f, | |
105 | sd_journal *j, | |
106 | OutputMode mode, | |
107 | unsigned n_columns, | |
108 | OutputFlags flags) { | |
109 | ||
86aa7ba4 | 110 | int r; |
86aa7ba4 LP |
111 | const void *data; |
112 | size_t length; | |
113 | size_t n = 0; | |
08ace05b | 114 | _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL; |
49826187 LP |
115 | size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0; |
116 | int p = LOG_INFO; | |
117 | const char *color_on = "", *color_off = ""; | |
86aa7ba4 | 118 | |
08ace05b | 119 | assert(f); |
86aa7ba4 LP |
120 | assert(j); |
121 | ||
93b73b06 LP |
122 | sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : PRINT_THRESHOLD); |
123 | ||
55d7bfc1 LP |
124 | SD_JOURNAL_FOREACH_DATA(j, data, length) { |
125 | ||
49826187 LP |
126 | r = parse_field(data, length, "PRIORITY=", &priority, &priority_len); |
127 | if (r < 0) | |
08ace05b | 128 | return r; |
49826187 LP |
129 | else if (r > 0) |
130 | continue; | |
131 | ||
55d7bfc1 LP |
132 | r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len); |
133 | if (r < 0) | |
08ace05b | 134 | return r; |
55d7bfc1 LP |
135 | else if (r > 0) |
136 | continue; | |
137 | ||
4cd9a9d9 | 138 | r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len); |
55d7bfc1 | 139 | if (r < 0) |
08ace05b | 140 | return r; |
55d7bfc1 LP |
141 | else if (r > 0) |
142 | continue; | |
143 | ||
144 | r = parse_field(data, length, "_COMM=", &comm, &comm_len); | |
145 | if (r < 0) | |
08ace05b | 146 | return r; |
55d7bfc1 LP |
147 | else if (r > 0) |
148 | continue; | |
149 | ||
150 | r = parse_field(data, length, "_PID=", &pid, &pid_len); | |
151 | if (r < 0) | |
08ace05b | 152 | return r; |
55d7bfc1 LP |
153 | else if (r > 0) |
154 | continue; | |
155 | ||
6c1e6b98 LP |
156 | r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len); |
157 | if (r < 0) | |
08ace05b | 158 | return r; |
6c1e6b98 LP |
159 | else if (r > 0) |
160 | continue; | |
161 | ||
bf967366 LP |
162 | r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len); |
163 | if (r < 0) | |
08ace05b | 164 | return r; |
bf967366 LP |
165 | else if (r > 0) |
166 | continue; | |
167 | ||
168 | r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len); | |
169 | if (r < 0) | |
08ace05b | 170 | return r; |
bf967366 LP |
171 | else if (r > 0) |
172 | continue; | |
173 | ||
55d7bfc1 LP |
174 | r = parse_field(data, length, "MESSAGE=", &message, &message_len); |
175 | if (r < 0) | |
08ace05b | 176 | return r; |
55d7bfc1 LP |
177 | } |
178 | ||
08ace05b LP |
179 | if (!message) |
180 | return 0; | |
55d7bfc1 | 181 | |
e8bc0ea2 LP |
182 | if (!(flags & OUTPUT_SHOW_ALL)) |
183 | strip_tab_ansi(&message, &message_len); | |
184 | ||
49826187 LP |
185 | if (priority_len == 1 && *priority >= '0' && *priority <= '7') |
186 | p = *priority - '0'; | |
187 | ||
a6e87e90 | 188 | if (mode == OUTPUT_SHORT_MONOTONIC) { |
67a12205 | 189 | uint64_t t; |
3ebcdf8c LP |
190 | sd_id128_t boot_id; |
191 | ||
bf967366 LP |
192 | r = -ENOENT; |
193 | ||
194 | if (monotonic) | |
195 | r = safe_atou64(monotonic, &t); | |
196 | ||
197 | if (r < 0) | |
3ebcdf8c | 198 | r = sd_journal_get_monotonic_usec(j, &t, &boot_id); |
86aa7ba4 | 199 | |
3ebcdf8c LP |
200 | if (r < 0) { |
201 | log_error("Failed to get monotonic: %s", strerror(-r)); | |
08ace05b | 202 | return r; |
67a12205 LP |
203 | } |
204 | ||
08ace05b LP |
205 | fprintf(f, "[%5llu.%06llu]", |
206 | (unsigned long long) (t / USEC_PER_SEC), | |
207 | (unsigned long long) (t % USEC_PER_SEC)); | |
3ebcdf8c LP |
208 | |
209 | n += 1 + 5 + 1 + 6 + 1; | |
210 | ||
67a12205 LP |
211 | } else { |
212 | char buf[64]; | |
bf967366 | 213 | uint64_t x; |
67a12205 LP |
214 | time_t t; |
215 | struct tm tm; | |
731a676c | 216 | |
bf967366 LP |
217 | r = -ENOENT; |
218 | ||
219 | if (realtime) | |
220 | r = safe_atou64(realtime, &x); | |
221 | ||
222 | if (r < 0) | |
223 | r = sd_journal_get_realtime_usec(j, &x); | |
67a12205 | 224 | |
67a12205 LP |
225 | if (r < 0) { |
226 | log_error("Failed to get realtime: %s", strerror(-r)); | |
08ace05b | 227 | return r; |
67a12205 LP |
228 | } |
229 | ||
bf967366 | 230 | t = (time_t) (x / USEC_PER_SEC); |
67a12205 LP |
231 | if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) { |
232 | log_error("Failed to format time."); | |
08ace05b | 233 | return r; |
67a12205 LP |
234 | } |
235 | ||
08ace05b | 236 | fputs(buf, f); |
67a12205 LP |
237 | n += strlen(buf); |
238 | } | |
86aa7ba4 | 239 | |
08ace05b LP |
240 | if (hostname && shall_print(hostname, hostname_len, flags)) { |
241 | fprintf(f, " %.*s", (int) hostname_len, hostname); | |
55d7bfc1 LP |
242 | n += hostname_len + 1; |
243 | } | |
244 | ||
08ace05b LP |
245 | if (identifier && shall_print(identifier, identifier_len, flags)) { |
246 | fprintf(f, " %.*s", (int) identifier_len, identifier); | |
4cd9a9d9 | 247 | n += identifier_len + 1; |
08ace05b LP |
248 | } else if (comm && shall_print(comm, comm_len, flags)) { |
249 | fprintf(f, " %.*s", (int) comm_len, comm); | |
55d7bfc1 | 250 | n += comm_len + 1; |
b5936820 | 251 | } else |
08ace05b | 252 | fputc(' ', f); |
86aa7ba4 | 253 | |
08ace05b LP |
254 | if (pid && shall_print(pid, pid_len, flags)) { |
255 | fprintf(f, "[%.*s]", (int) pid_len, pid); | |
55d7bfc1 | 256 | n += pid_len + 2; |
08ace05b LP |
257 | } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) { |
258 | fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid); | |
6c1e6b98 | 259 | n += fake_pid_len + 2; |
86aa7ba4 LP |
260 | } |
261 | ||
49826187 LP |
262 | if (flags & OUTPUT_COLOR) { |
263 | if (p <= LOG_ERR) { | |
264 | color_on = ANSI_HIGHLIGHT_RED_ON; | |
265 | color_off = ANSI_HIGHLIGHT_OFF; | |
266 | } else if (p <= LOG_NOTICE) { | |
267 | color_on = ANSI_HIGHLIGHT_ON; | |
268 | color_off = ANSI_HIGHLIGHT_OFF; | |
269 | } | |
270 | } | |
271 | ||
cd4b13e0 | 272 | if (flags & OUTPUT_SHOW_ALL) |
08ace05b | 273 | fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); |
ba961854 | 274 | else if (!utf8_is_printable_n(message, message_len)) { |
e6acda19 | 275 | char bytes[FORMAT_BYTES_MAX]; |
08ace05b LP |
276 | fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); |
277 | } else if ((flags & OUTPUT_FULL_WIDTH) || (message_len + n + 1 < n_columns)) | |
278 | fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); | |
b61a4660 | 279 | else if (n < n_columns && n_columns - n - 2 >= 3) { |
2e729834 | 280 | char _cleanup_free_ *e; |
55d7bfc1 | 281 | |
34a35ece | 282 | e = ellipsize_mem(message, message_len, n_columns - n - 2, 90); |
55d7bfc1 LP |
283 | |
284 | if (!e) | |
08ace05b | 285 | fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off); |
55d7bfc1 | 286 | else |
08ace05b | 287 | fprintf(f, ": %s%s%s\n", color_on, e, color_off); |
55d7bfc1 | 288 | } else |
08ace05b | 289 | fputs("\n", f); |
55d7bfc1 | 290 | |
d4205751 LP |
291 | if (flags & OUTPUT_CATALOG) |
292 | print_catalog(f, j); | |
293 | ||
08ace05b | 294 | return 0; |
86aa7ba4 LP |
295 | } |
296 | ||
08ace05b LP |
297 | static int output_verbose( |
298 | FILE *f, | |
299 | sd_journal *j, | |
300 | OutputMode mode, | |
301 | unsigned n_columns, | |
302 | OutputFlags flags) { | |
303 | ||
86aa7ba4 LP |
304 | const void *data; |
305 | size_t length; | |
2e729834 | 306 | char _cleanup_free_ *cursor = NULL; |
86aa7ba4 LP |
307 | uint64_t realtime; |
308 | char ts[FORMAT_TIMESTAMP_MAX]; | |
309 | int r; | |
310 | ||
08ace05b | 311 | assert(f); |
86aa7ba4 LP |
312 | assert(j); |
313 | ||
93b73b06 LP |
314 | sd_journal_set_data_threshold(j, 0); |
315 | ||
86aa7ba4 LP |
316 | r = sd_journal_get_realtime_usec(j, &realtime); |
317 | if (r < 0) { | |
318 | log_error("Failed to get realtime timestamp: %s", strerror(-r)); | |
319 | return r; | |
320 | } | |
321 | ||
322 | r = sd_journal_get_cursor(j, &cursor); | |
323 | if (r < 0) { | |
324 | log_error("Failed to get cursor: %s", strerror(-r)); | |
325 | return r; | |
326 | } | |
327 | ||
08ace05b LP |
328 | fprintf(f, "%s [%s]\n", |
329 | format_timestamp(ts, sizeof(ts), realtime), | |
330 | cursor); | |
86aa7ba4 | 331 | |
86aa7ba4 | 332 | SD_JOURNAL_FOREACH_DATA(j, data, length) { |
08ace05b | 333 | if (!shall_print(data, length, flags)) { |
86aa7ba4 | 334 | const char *c; |
e6acda19 | 335 | char bytes[FORMAT_BYTES_MAX]; |
86aa7ba4 LP |
336 | |
337 | c = memchr(data, '=', length); | |
338 | if (!c) { | |
339 | log_error("Invalid field."); | |
340 | return -EINVAL; | |
341 | } | |
342 | ||
08ace05b | 343 | fprintf(f, "\t%.*s=[%s blob data]\n", |
86aa7ba4 | 344 | (int) (c - (const char*) data), |
e6acda19 LP |
345 | (const char*) data, |
346 | format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1)); | |
86aa7ba4 | 347 | } else |
08ace05b | 348 | fprintf(f, "\t%.*s\n", (int) length, (const char*) data); |
86aa7ba4 LP |
349 | } |
350 | ||
d4205751 LP |
351 | if (flags & OUTPUT_CATALOG) |
352 | print_catalog(f, j); | |
353 | ||
86aa7ba4 LP |
354 | return 0; |
355 | } | |
356 | ||
08ace05b LP |
357 | static int output_export( |
358 | FILE *f, | |
359 | sd_journal *j, | |
360 | OutputMode mode, | |
361 | unsigned n_columns, | |
362 | OutputFlags flags) { | |
363 | ||
86aa7ba4 LP |
364 | sd_id128_t boot_id; |
365 | char sid[33]; | |
366 | int r; | |
367 | usec_t realtime, monotonic; | |
2e729834 | 368 | char _cleanup_free_ *cursor = NULL; |
86aa7ba4 LP |
369 | const void *data; |
370 | size_t length; | |
371 | ||
372 | assert(j); | |
373 | ||
93b73b06 LP |
374 | sd_journal_set_data_threshold(j, 0); |
375 | ||
86aa7ba4 LP |
376 | r = sd_journal_get_realtime_usec(j, &realtime); |
377 | if (r < 0) { | |
378 | log_error("Failed to get realtime timestamp: %s", strerror(-r)); | |
379 | return r; | |
380 | } | |
381 | ||
382 | r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); | |
383 | if (r < 0) { | |
384 | log_error("Failed to get monotonic timestamp: %s", strerror(-r)); | |
385 | return r; | |
386 | } | |
387 | ||
388 | r = sd_journal_get_cursor(j, &cursor); | |
389 | if (r < 0) { | |
390 | log_error("Failed to get cursor: %s", strerror(-r)); | |
391 | return r; | |
392 | } | |
393 | ||
08ace05b LP |
394 | fprintf(f, |
395 | "__CURSOR=%s\n" | |
396 | "__REALTIME_TIMESTAMP=%llu\n" | |
397 | "__MONOTONIC_TIMESTAMP=%llu\n" | |
398 | "_BOOT_ID=%s\n", | |
399 | cursor, | |
400 | (unsigned long long) realtime, | |
401 | (unsigned long long) monotonic, | |
402 | sd_id128_to_string(boot_id, sid)); | |
86aa7ba4 | 403 | |
86aa7ba4 LP |
404 | SD_JOURNAL_FOREACH_DATA(j, data, length) { |
405 | ||
112301ae LP |
406 | /* We already printed the boot id, from the data in |
407 | * the header, hence let's suppress it here */ | |
408 | if (length >= 9 && | |
409 | memcmp(data, "_BOOT_ID=", 9) == 0) | |
410 | continue; | |
411 | ||
ba961854 | 412 | if (!utf8_is_printable_n(data, length)) { |
86aa7ba4 LP |
413 | const char *c; |
414 | uint64_t le64; | |
415 | ||
416 | c = memchr(data, '=', length); | |
417 | if (!c) { | |
418 | log_error("Invalid field."); | |
419 | return -EINVAL; | |
420 | } | |
421 | ||
08ace05b LP |
422 | fwrite(data, c - (const char*) data, 1, f); |
423 | fputc('\n', f); | |
86aa7ba4 | 424 | le64 = htole64(length - (c - (const char*) data) - 1); |
08ace05b LP |
425 | fwrite(&le64, sizeof(le64), 1, f); |
426 | fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f); | |
86aa7ba4 | 427 | } else |
08ace05b | 428 | fwrite(data, length, 1, f); |
86aa7ba4 | 429 | |
08ace05b | 430 | fputc('\n', f); |
86aa7ba4 LP |
431 | } |
432 | ||
08ace05b | 433 | fputc('\n', f); |
86aa7ba4 LP |
434 | |
435 | return 0; | |
436 | } | |
437 | ||
240a5fe8 | 438 | void json_escape( |
08ace05b LP |
439 | FILE *f, |
440 | const char* p, | |
441 | size_t l, | |
442 | OutputFlags flags) { | |
443 | ||
444 | assert(f); | |
445 | assert(p); | |
446 | ||
93b73b06 | 447 | if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) |
08ace05b LP |
448 | |
449 | fputs("null", f); | |
450 | ||
451 | else if (!utf8_is_printable_n(p, l)) { | |
86aa7ba4 LP |
452 | bool not_first = false; |
453 | ||
08ace05b | 454 | fputs("[ ", f); |
86aa7ba4 LP |
455 | |
456 | while (l > 0) { | |
457 | if (not_first) | |
08ace05b | 458 | fprintf(f, ", %u", (uint8_t) *p); |
86aa7ba4 LP |
459 | else { |
460 | not_first = true; | |
08ace05b | 461 | fprintf(f, "%u", (uint8_t) *p); |
86aa7ba4 LP |
462 | } |
463 | ||
464 | p++; | |
465 | l--; | |
466 | } | |
467 | ||
08ace05b | 468 | fputs(" ]", f); |
86aa7ba4 | 469 | } else { |
08ace05b | 470 | fputc('\"', f); |
86aa7ba4 LP |
471 | |
472 | while (l > 0) { | |
473 | if (*p == '"' || *p == '\\') { | |
08ace05b LP |
474 | fputc('\\', f); |
475 | fputc(*p, f); | |
476 | } else if (*p < ' ') | |
477 | fprintf(f, "\\u%04x", *p); | |
478 | else | |
479 | fputc(*p, f); | |
86aa7ba4 LP |
480 | |
481 | p++; | |
482 | l--; | |
483 | } | |
484 | ||
08ace05b | 485 | fputc('\"', f); |
86aa7ba4 LP |
486 | } |
487 | } | |
488 | ||
08ace05b LP |
489 | static int output_json( |
490 | FILE *f, | |
491 | sd_journal *j, | |
492 | OutputMode mode, | |
493 | unsigned n_columns, | |
494 | OutputFlags flags) { | |
495 | ||
86aa7ba4 | 496 | uint64_t realtime, monotonic; |
2e729834 | 497 | char _cleanup_free_ *cursor = NULL; |
86aa7ba4 LP |
498 | const void *data; |
499 | size_t length; | |
500 | sd_id128_t boot_id; | |
2e729834 | 501 | char sid[33], *k; |
86aa7ba4 | 502 | int r; |
d99ae53a LP |
503 | Hashmap *h = NULL; |
504 | bool done, separator; | |
86aa7ba4 LP |
505 | |
506 | assert(j); | |
507 | ||
93b73b06 LP |
508 | sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); |
509 | ||
86aa7ba4 LP |
510 | r = sd_journal_get_realtime_usec(j, &realtime); |
511 | if (r < 0) { | |
512 | log_error("Failed to get realtime timestamp: %s", strerror(-r)); | |
513 | return r; | |
514 | } | |
515 | ||
516 | r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); | |
517 | if (r < 0) { | |
518 | log_error("Failed to get monotonic timestamp: %s", strerror(-r)); | |
519 | return r; | |
520 | } | |
521 | ||
522 | r = sd_journal_get_cursor(j, &cursor); | |
523 | if (r < 0) { | |
524 | log_error("Failed to get cursor: %s", strerror(-r)); | |
525 | return r; | |
526 | } | |
527 | ||
a6e87e90 | 528 | if (mode == OUTPUT_JSON_PRETTY) |
08ace05b LP |
529 | fprintf(f, |
530 | "{\n" | |
531 | "\t\"__CURSOR\" : \"%s\",\n" | |
532 | "\t\"__REALTIME_TIMESTAMP\" : \"%llu\",\n" | |
533 | "\t\"__MONOTONIC_TIMESTAMP\" : \"%llu\",\n" | |
534 | "\t\"_BOOT_ID\" : \"%s\"", | |
535 | cursor, | |
536 | (unsigned long long) realtime, | |
537 | (unsigned long long) monotonic, | |
538 | sd_id128_to_string(boot_id, sid)); | |
48383c25 LP |
539 | else { |
540 | if (mode == OUTPUT_JSON_SSE) | |
541 | fputs("data: ", f); | |
542 | ||
08ace05b LP |
543 | fprintf(f, |
544 | "{ \"__CURSOR\" : \"%s\", " | |
545 | "\"__REALTIME_TIMESTAMP\" : \"%llu\", " | |
546 | "\"__MONOTONIC_TIMESTAMP\" : \"%llu\", " | |
547 | "\"_BOOT_ID\" : \"%s\"", | |
548 | cursor, | |
549 | (unsigned long long) realtime, | |
550 | (unsigned long long) monotonic, | |
551 | sd_id128_to_string(boot_id, sid)); | |
48383c25 | 552 | } |
86aa7ba4 | 553 | |
d99ae53a LP |
554 | h = hashmap_new(string_hash_func, string_compare_func); |
555 | if (!h) | |
556 | return -ENOMEM; | |
557 | ||
558 | /* First round, iterate through the entry and count how often each field appears */ | |
86aa7ba4 | 559 | SD_JOURNAL_FOREACH_DATA(j, data, length) { |
d99ae53a LP |
560 | const char *eq; |
561 | char *n; | |
562 | unsigned u; | |
86aa7ba4 | 563 | |
112301ae LP |
564 | if (length >= 9 && |
565 | memcmp(data, "_BOOT_ID=", 9) == 0) | |
566 | continue; | |
567 | ||
d99ae53a LP |
568 | eq = memchr(data, '=', length); |
569 | if (!eq) | |
570 | continue; | |
86aa7ba4 | 571 | |
d99ae53a LP |
572 | n = strndup(data, eq - (const char*) data); |
573 | if (!n) { | |
574 | r = -ENOMEM; | |
575 | goto finish; | |
576 | } | |
a6e87e90 | 577 | |
d99ae53a LP |
578 | u = PTR_TO_UINT(hashmap_get(h, n)); |
579 | if (u == 0) { | |
580 | r = hashmap_put(h, n, UINT_TO_PTR(1)); | |
581 | if (r < 0) { | |
582 | free(n); | |
583 | goto finish; | |
584 | } | |
585 | } else { | |
586 | r = hashmap_update(h, n, UINT_TO_PTR(u + 1)); | |
587 | free(n); | |
588 | if (r < 0) | |
589 | goto finish; | |
590 | } | |
86aa7ba4 LP |
591 | } |
592 | ||
d99ae53a LP |
593 | separator = true; |
594 | do { | |
595 | done = true; | |
596 | ||
597 | SD_JOURNAL_FOREACH_DATA(j, data, length) { | |
598 | const char *eq; | |
599 | char *kk, *n; | |
600 | size_t m; | |
601 | unsigned u; | |
602 | ||
603 | /* We already printed the boot id, from the data in | |
604 | * the header, hence let's suppress it here */ | |
605 | if (length >= 9 && | |
606 | memcmp(data, "_BOOT_ID=", 9) == 0) | |
607 | continue; | |
608 | ||
609 | eq = memchr(data, '=', length); | |
610 | if (!eq) | |
611 | continue; | |
612 | ||
613 | if (separator) { | |
614 | if (mode == OUTPUT_JSON_PRETTY) | |
615 | fputs(",\n\t", f); | |
616 | else | |
617 | fputs(", ", f); | |
618 | } | |
619 | ||
620 | m = eq - (const char*) data; | |
621 | ||
622 | n = strndup(data, m); | |
623 | if (!n) { | |
624 | r = -ENOMEM; | |
625 | goto finish; | |
626 | } | |
627 | ||
628 | u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); | |
629 | if (u == 0) { | |
630 | /* We already printed this, let's jump to the next */ | |
631 | free(n); | |
632 | separator = false; | |
633 | ||
634 | continue; | |
635 | } else if (u == 1) { | |
636 | /* Field only appears once, output it directly */ | |
637 | ||
638 | json_escape(f, data, m, flags); | |
639 | fputs(" : ", f); | |
640 | ||
641 | json_escape(f, eq + 1, length - m - 1, flags); | |
642 | ||
643 | hashmap_remove(h, n); | |
644 | free(kk); | |
645 | free(n); | |
646 | ||
647 | separator = true; | |
648 | ||
649 | continue; | |
650 | ||
651 | } else { | |
652 | /* Field appears multiple times, output it as array */ | |
653 | json_escape(f, data, m, flags); | |
654 | fputs(" : [ ", f); | |
655 | json_escape(f, eq + 1, length - m - 1, flags); | |
656 | ||
657 | /* Iterate through the end of the list */ | |
658 | ||
659 | while (sd_journal_enumerate_data(j, &data, &length) > 0) { | |
660 | if (length < m + 1) | |
661 | continue; | |
662 | ||
663 | if (memcmp(data, n, m) != 0) | |
664 | continue; | |
665 | ||
666 | if (((const char*) data)[m] != '=') | |
667 | continue; | |
668 | ||
669 | fputs(", ", f); | |
670 | json_escape(f, (const char*) data + m + 1, length - m - 1, flags); | |
671 | } | |
672 | ||
673 | fputs(" ]", f); | |
674 | ||
675 | hashmap_remove(h, n); | |
676 | free(kk); | |
677 | free(n); | |
678 | ||
679 | /* Iterate data fields form the beginning */ | |
680 | done = false; | |
681 | separator = true; | |
682 | ||
683 | break; | |
684 | } | |
685 | } | |
686 | ||
687 | } while (!done); | |
688 | ||
a6e87e90 | 689 | if (mode == OUTPUT_JSON_PRETTY) |
08ace05b | 690 | fputs("\n}\n", f); |
48383c25 LP |
691 | else if (mode == OUTPUT_JSON_SSE) |
692 | fputs("}\n\n", f); | |
a6e87e90 | 693 | else |
08ace05b | 694 | fputs(" }\n", f); |
86aa7ba4 | 695 | |
d99ae53a LP |
696 | r = 0; |
697 | ||
698 | finish: | |
699 | while ((k = hashmap_steal_first_key(h))) | |
700 | free(k); | |
701 | ||
702 | hashmap_free(h); | |
703 | ||
704 | return r; | |
86aa7ba4 LP |
705 | } |
706 | ||
08ace05b LP |
707 | static int output_cat( |
708 | FILE *f, | |
709 | sd_journal *j, | |
710 | OutputMode mode, | |
711 | unsigned n_columns, | |
712 | OutputFlags flags) { | |
713 | ||
d3f2bdbf LP |
714 | const void *data; |
715 | size_t l; | |
716 | int r; | |
717 | ||
718 | assert(j); | |
08ace05b | 719 | assert(f); |
d3f2bdbf | 720 | |
93b73b06 LP |
721 | sd_journal_set_data_threshold(j, 0); |
722 | ||
d3f2bdbf LP |
723 | r = sd_journal_get_data(j, "MESSAGE", &data, &l); |
724 | if (r < 0) { | |
c198300f LP |
725 | /* An entry without MESSAGE=? */ |
726 | if (r == -ENOENT) | |
727 | return 0; | |
728 | ||
d3f2bdbf LP |
729 | log_error("Failed to get data: %s", strerror(-r)); |
730 | return r; | |
731 | } | |
732 | ||
733 | assert(l >= 8); | |
734 | ||
08ace05b LP |
735 | fwrite((const char*) data + 8, 1, l - 8, f); |
736 | fputc('\n', f); | |
d3f2bdbf LP |
737 | |
738 | return 0; | |
739 | } | |
740 | ||
08ace05b LP |
741 | static int (*output_funcs[_OUTPUT_MODE_MAX])( |
742 | FILE *f, | |
743 | sd_journal*j, | |
744 | OutputMode mode, | |
745 | unsigned n_columns, | |
746 | OutputFlags flags) = { | |
747 | ||
a6e87e90 LP |
748 | [OUTPUT_SHORT] = output_short, |
749 | [OUTPUT_SHORT_MONOTONIC] = output_short, | |
86aa7ba4 LP |
750 | [OUTPUT_VERBOSE] = output_verbose, |
751 | [OUTPUT_EXPORT] = output_export, | |
d3f2bdbf | 752 | [OUTPUT_JSON] = output_json, |
a6e87e90 | 753 | [OUTPUT_JSON_PRETTY] = output_json, |
48383c25 | 754 | [OUTPUT_JSON_SSE] = output_json, |
d3f2bdbf | 755 | [OUTPUT_CAT] = output_cat |
86aa7ba4 LP |
756 | }; |
757 | ||
08ace05b LP |
758 | int output_journal( |
759 | FILE *f, | |
760 | sd_journal *j, | |
761 | OutputMode mode, | |
762 | unsigned n_columns, | |
763 | OutputFlags flags) { | |
764 | ||
e268b81e | 765 | int ret; |
df50185b | 766 | assert(mode >= 0); |
86aa7ba4 LP |
767 | assert(mode < _OUTPUT_MODE_MAX); |
768 | ||
34a35ece LP |
769 | if (n_columns <= 0) |
770 | n_columns = columns(); | |
771 | ||
08ace05b | 772 | ret = output_funcs[mode](f, j, mode, n_columns, flags); |
e268b81e BP |
773 | fflush(stdout); |
774 | return ret; | |
86aa7ba4 LP |
775 | } |
776 | ||
1a6c43e9 MT |
777 | static int show_journal(FILE *f, |
778 | sd_journal *j, | |
779 | OutputMode mode, | |
780 | unsigned n_columns, | |
781 | usec_t not_before, | |
782 | unsigned how_many, | |
783 | OutputFlags flags) { | |
86aa7ba4 | 784 | |
86aa7ba4 | 785 | int r; |
df50185b LP |
786 | unsigned line = 0; |
787 | bool need_seek = false; | |
085d7120 | 788 | int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; |
86aa7ba4 | 789 | |
1a6c43e9 | 790 | assert(j); |
df50185b LP |
791 | assert(mode >= 0); |
792 | assert(mode < _OUTPUT_MODE_MAX); | |
1946b0bd LP |
793 | |
794 | /* Seek to end */ | |
86aa7ba4 LP |
795 | r = sd_journal_seek_tail(j); |
796 | if (r < 0) | |
797 | goto finish; | |
798 | ||
df50185b LP |
799 | r = sd_journal_previous_skip(j, how_many); |
800 | if (r < 0) | |
801 | goto finish; | |
86aa7ba4 | 802 | |
df50185b LP |
803 | for (;;) { |
804 | for (;;) { | |
805 | usec_t usec; | |
806 | ||
807 | if (need_seek) { | |
808 | r = sd_journal_next(j); | |
809 | if (r < 0) | |
810 | goto finish; | |
811 | } | |
812 | ||
813 | if (r == 0) | |
814 | break; | |
86aa7ba4 | 815 | |
df50185b LP |
816 | need_seek = true; |
817 | ||
818 | if (not_before > 0) { | |
819 | r = sd_journal_get_monotonic_usec(j, &usec, NULL); | |
820 | ||
821 | /* -ESTALE is returned if the | |
822 | timestamp is not from this boot */ | |
823 | if (r == -ESTALE) | |
824 | continue; | |
825 | else if (r < 0) | |
826 | goto finish; | |
827 | ||
828 | if (usec < not_before) | |
829 | continue; | |
830 | } | |
831 | ||
832 | line ++; | |
833 | ||
08ace05b | 834 | r = output_journal(f, j, mode, n_columns, flags); |
df50185b LP |
835 | if (r < 0) |
836 | goto finish; | |
837 | } | |
838 | ||
08984293 LP |
839 | if (warn_cutoff && line < how_many && not_before > 0) { |
840 | sd_id128_t boot_id; | |
841 | usec_t cutoff; | |
842 | ||
843 | /* Check whether the cutoff line is too early */ | |
844 | ||
845 | r = sd_id128_get_boot(&boot_id); | |
846 | if (r < 0) | |
847 | goto finish; | |
848 | ||
849 | r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL); | |
850 | if (r < 0) | |
851 | goto finish; | |
852 | ||
b59866ae | 853 | if (r > 0 && not_before < cutoff) |
08ace05b | 854 | fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n"); |
08984293 LP |
855 | |
856 | warn_cutoff = false; | |
857 | } | |
858 | ||
085d7120 | 859 | if (!(flags & OUTPUT_FOLLOW)) |
86aa7ba4 LP |
860 | break; |
861 | ||
e02d1cf7 | 862 | r = sd_journal_wait(j, (usec_t) -1); |
df50185b LP |
863 | if (r < 0) |
864 | goto finish; | |
865 | ||
86aa7ba4 LP |
866 | } |
867 | ||
1a6c43e9 MT |
868 | finish: |
869 | return r; | |
870 | } | |
871 | ||
872 | int show_journal_by_unit( | |
873 | FILE *f, | |
874 | const char *unit, | |
875 | OutputMode mode, | |
876 | unsigned n_columns, | |
877 | usec_t not_before, | |
878 | unsigned how_many, | |
879 | OutputFlags flags) { | |
880 | ||
881 | _cleanup_free_ char *m1 = NULL, *m2 = NULL, *m3 = NULL; | |
882 | sd_journal *j = NULL; | |
883 | int r; | |
884 | ||
885 | assert(mode >= 0); | |
886 | assert(mode < _OUTPUT_MODE_MAX); | |
887 | assert(unit); | |
888 | ||
889 | if (!endswith(unit, ".service") && | |
890 | !endswith(unit, ".socket") && | |
891 | !endswith(unit, ".mount") && | |
892 | !endswith(unit, ".swap")) | |
893 | return 0; | |
894 | ||
895 | if (how_many <= 0) | |
896 | return 0; | |
897 | ||
898 | if (asprintf(&m1, "_SYSTEMD_UNIT=%s", unit) < 0 || | |
899 | asprintf(&m2, "COREDUMP_UNIT=%s", unit) < 0 || | |
900 | asprintf(&m3, "UNIT=%s", unit) < 0) { | |
901 | r = -ENOMEM; | |
902 | goto finish; | |
903 | } | |
904 | ||
905 | r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY); | |
906 | if (r < 0) | |
907 | goto finish; | |
908 | ||
909 | /* Look for messages from the service itself */ | |
910 | r = sd_journal_add_match(j, m1, 0); | |
911 | if (r < 0) | |
912 | goto finish; | |
913 | ||
914 | /* Look for coredumps of the service */ | |
915 | r = sd_journal_add_disjunction(j); | |
916 | if (r < 0) | |
917 | goto finish; | |
918 | r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0); | |
919 | if (r < 0) | |
920 | goto finish; | |
921 | r = sd_journal_add_match(j, m2, 0); | |
922 | if (r < 0) | |
923 | goto finish; | |
924 | ||
925 | /* Look for messages from PID 1 about this service */ | |
926 | r = sd_journal_add_disjunction(j); | |
927 | if (r < 0) | |
928 | goto finish; | |
929 | r = sd_journal_add_match(j, "_PID=1", 0); | |
930 | if (r < 0) | |
931 | goto finish; | |
932 | r = sd_journal_add_match(j, m3, 0); | |
933 | if (r < 0) | |
934 | goto finish; | |
935 | ||
936 | r = show_journal(f, j, mode, n_columns, not_before, how_many, flags); | |
937 | if (r < 0) | |
938 | goto finish; | |
939 | ||
940 | finish: | |
941 | if (j) | |
942 | sd_journal_close(j); | |
943 | ||
944 | return r; | |
945 | } | |
946 | ||
947 | int show_journal_by_user_unit( | |
948 | FILE *f, | |
949 | const char *unit, | |
950 | OutputMode mode, | |
951 | unsigned n_columns, | |
952 | usec_t not_before, | |
953 | unsigned how_many, | |
954 | uid_t uid, | |
955 | OutputFlags flags) { | |
956 | ||
957 | _cleanup_free_ char *m1 = NULL, *m2 = NULL, *m3 = NULL; | |
958 | sd_journal *j = NULL; | |
959 | int r; | |
960 | ||
961 | assert(mode >= 0); | |
962 | assert(mode < _OUTPUT_MODE_MAX); | |
963 | assert(unit); | |
964 | ||
965 | if (!endswith(unit, ".service") && | |
966 | !endswith(unit, ".socket")) | |
967 | ||
968 | return 0; | |
969 | ||
970 | if (how_many <= 0) | |
971 | return 0; | |
972 | ||
973 | if (asprintf(&m1, "_SYSTEMD_USER_UNIT=%s", unit) < 0 || | |
974 | asprintf(&m2, "USER_UNIT=%s", unit) < 0 || | |
975 | asprintf(&m3, "_UID=%d", uid) < 0) { | |
976 | r = -ENOMEM; | |
977 | goto finish; | |
978 | } | |
979 | ||
980 | r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); | |
981 | if (r < 0) | |
982 | goto finish; | |
983 | ||
984 | /* Look for messages from the user service itself */ | |
985 | r = sd_journal_add_match(j, m1, 0); | |
986 | if (r < 0) | |
987 | goto finish; | |
988 | r = sd_journal_add_match(j, m3, 0); | |
989 | if (r < 0) | |
990 | goto finish; | |
991 | ||
992 | /* Look for messages from systemd about this service */ | |
993 | r = sd_journal_add_disjunction(j); | |
994 | if (r < 0) | |
995 | goto finish; | |
996 | r = sd_journal_add_match(j, m2, 0); | |
997 | if (r < 0) | |
998 | goto finish; | |
999 | r = sd_journal_add_match(j, m3, 0); | |
1000 | if (r < 0) | |
1001 | goto finish; | |
1002 | ||
1003 | r = show_journal(f, j, mode, n_columns, not_before, how_many, flags); | |
1004 | if (r < 0) | |
1005 | goto finish; | |
1006 | ||
86aa7ba4 | 1007 | finish: |
86aa7ba4 LP |
1008 | if (j) |
1009 | sd_journal_close(j); | |
1010 | ||
1011 | return r; | |
1012 | } | |
df50185b LP |
1013 | |
1014 | static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { | |
1015 | [OUTPUT_SHORT] = "short", | |
67a12205 | 1016 | [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", |
df50185b LP |
1017 | [OUTPUT_VERBOSE] = "verbose", |
1018 | [OUTPUT_EXPORT] = "export", | |
d3f2bdbf | 1019 | [OUTPUT_JSON] = "json", |
a6e87e90 | 1020 | [OUTPUT_JSON_PRETTY] = "json-pretty", |
48383c25 | 1021 | [OUTPUT_JSON_SSE] = "json-sse", |
d3f2bdbf | 1022 | [OUTPUT_CAT] = "cat" |
df50185b LP |
1023 | }; |
1024 | ||
1025 | DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode); |