]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
86aa7ba4 | 2 | |
86aa7ba4 | 3 | #include <errno.h> |
b6741478 | 4 | #include <fcntl.h> |
a8fbdf54 TA |
5 | #include <signal.h> |
6 | #include <stdint.h> | |
7 | #include <stdlib.h> | |
07630cea LP |
8 | #include <string.h> |
9 | #include <sys/socket.h> | |
a8fbdf54 | 10 | #include <syslog.h> |
07630cea | 11 | #include <time.h> |
a8fbdf54 TA |
12 | #include <unistd.h> |
13 | ||
14 | #include "sd-id128.h" | |
15 | #include "sd-journal.h" | |
86aa7ba4 | 16 | |
b5efdb8a | 17 | #include "alloc-util.h" |
3ffd4af2 | 18 | #include "fd-util.h" |
f97b34a6 | 19 | #include "format-util.h" |
d99ae53a | 20 | #include "hashmap.h" |
07630cea | 21 | #include "hostname-util.h" |
c004493c | 22 | #include "io-util.h" |
dfb33a97 | 23 | #include "journal-internal.h" |
07630cea | 24 | #include "log.h" |
3ffd4af2 | 25 | #include "logs-show.h" |
a8fbdf54 TA |
26 | #include "macro.h" |
27 | #include "output-mode.h" | |
6bedfcbb | 28 | #include "parse-util.h" |
0b452006 | 29 | #include "process-util.h" |
a8fbdf54 | 30 | #include "sparse-endian.h" |
29a753df | 31 | #include "stdio-util.h" |
8b43440b | 32 | #include "string-table.h" |
07630cea | 33 | #include "string-util.h" |
cc25a67e | 34 | #include "strv.h" |
288a74cc | 35 | #include "terminal-util.h" |
a8fbdf54 | 36 | #include "time-util.h" |
07630cea LP |
37 | #include "utf8.h" |
38 | #include "util.h" | |
86aa7ba4 | 39 | |
07630cea | 40 | /* up to three lines (each up to 100 characters) or 300 characters, whichever is less */ |
a6f0104a ZJS |
41 | #define PRINT_LINE_THRESHOLD 3 |
42 | #define PRINT_CHAR_THRESHOLD 300 | |
43 | ||
08ace05b | 44 | #define JSON_THRESHOLD 4096 |
86aa7ba4 | 45 | |
d4205751 LP |
46 | static int print_catalog(FILE *f, sd_journal *j) { |
47 | int r; | |
48 | _cleanup_free_ char *t = NULL, *z = NULL; | |
49 | ||
d4205751 LP |
50 | r = sd_journal_get_catalog(j, &t); |
51 | if (r < 0) | |
52 | return r; | |
53 | ||
54 | z = strreplace(strstrip(t), "\n", "\n-- "); | |
55 | if (!z) | |
56 | return log_oom(); | |
57 | ||
58 | fputs("-- ", f); | |
59 | fputs(z, f); | |
60 | fputc('\n', f); | |
61 | ||
62 | return 0; | |
63 | } | |
64 | ||
ed054520 VC |
65 | static int parse_field(const void *data, size_t length, const char *field, size_t field_len, char **target, size_t *target_len) { |
66 | size_t nl; | |
d813d7a3 | 67 | char *buf; |
55d7bfc1 LP |
68 | |
69 | assert(data); | |
70 | assert(field); | |
71 | assert(target); | |
55d7bfc1 | 72 | |
ed054520 | 73 | if (length < field_len) |
55d7bfc1 LP |
74 | return 0; |
75 | ||
ed054520 | 76 | if (memcmp(data, field, field_len)) |
55d7bfc1 LP |
77 | return 0; |
78 | ||
ed054520 | 79 | nl = length - field_len; |
c165d97d | 80 | |
ed054520 | 81 | buf = newdup_suffix0(char, (const char*) data + field_len, nl); |
0d0f0c50 SL |
82 | if (!buf) |
83 | return log_oom(); | |
55d7bfc1 | 84 | |
6c1e6b98 | 85 | free(*target); |
55d7bfc1 | 86 | *target = buf; |
d813d7a3 | 87 | |
ed054520 VC |
88 | if (target_len) |
89 | *target_len = nl; | |
55d7bfc1 LP |
90 | |
91 | return 1; | |
92 | } | |
93 | ||
ed054520 VC |
94 | typedef struct ParseFieldVec { |
95 | const char *field; | |
96 | size_t field_len; | |
97 | char **target; | |
98 | size_t *target_len; | |
99 | } ParseFieldVec; | |
100 | ||
101 | #define PARSE_FIELD_VEC_ENTRY(_field, _target, _target_len) \ | |
102 | { .field = _field, .field_len = strlen(_field), .target = _target, .target_len = _target_len } | |
103 | ||
104 | static int parse_fieldv(const void *data, size_t length, const ParseFieldVec *fields, unsigned n_fields) { | |
105 | unsigned i; | |
106 | ||
107 | for (i = 0; i < n_fields; i++) { | |
108 | const ParseFieldVec *f = &fields[i]; | |
109 | int r; | |
110 | ||
111 | r = parse_field(data, length, f->field, f->field_len, f->target, f->target_len); | |
112 | if (r < 0) | |
113 | return r; | |
114 | else if (r > 0) | |
115 | break; | |
116 | } | |
117 | ||
118 | return 0; | |
119 | } | |
120 | ||
cc25a67e LK |
121 | static int field_set_test(Set *fields, const char *name, size_t n) { |
122 | char *s = NULL; | |
123 | ||
124 | if (!fields) | |
125 | return 1; | |
126 | ||
127 | s = strndupa(name, n); | |
128 | if (!s) | |
129 | return log_oom(); | |
130 | ||
131 | return set_get(fields, s) ? 1 : 0; | |
132 | } | |
133 | ||
08ace05b LP |
134 | static bool shall_print(const char *p, size_t l, OutputFlags flags) { |
135 | assert(p); | |
136 | ||
137 | if (flags & OUTPUT_SHOW_ALL) | |
55d7bfc1 LP |
138 | return true; |
139 | ||
a6f0104a | 140 | if (l >= PRINT_CHAR_THRESHOLD) |
55d7bfc1 LP |
141 | return false; |
142 | ||
31f7bf19 | 143 | if (!utf8_is_printable(p, l)) |
55d7bfc1 LP |
144 | return false; |
145 | ||
146 | return true; | |
147 | } | |
148 | ||
b4766d5f ZJS |
149 | static bool print_multiline( |
150 | FILE *f, | |
151 | unsigned prefix, | |
152 | unsigned n_columns, | |
153 | OutputFlags flags, | |
154 | int priority, | |
155 | const char* message, | |
156 | size_t message_len, | |
157 | size_t highlight[2]) { | |
158 | ||
159 | const char *color_on = "", *color_off = "", *highlight_on = ""; | |
31f7bf19 | 160 | const char *pos, *end; |
94e0bd7d | 161 | bool ellipsized = false; |
a6f0104a | 162 | int line = 0; |
31f7bf19 ZJS |
163 | |
164 | if (flags & OUTPUT_COLOR) { | |
165 | if (priority <= LOG_ERR) { | |
1fc464f6 LP |
166 | color_on = ANSI_HIGHLIGHT_RED; |
167 | color_off = ANSI_NORMAL; | |
b4766d5f | 168 | highlight_on = ANSI_HIGHLIGHT; |
31f7bf19 | 169 | } else if (priority <= LOG_NOTICE) { |
1fc464f6 LP |
170 | color_on = ANSI_HIGHLIGHT; |
171 | color_off = ANSI_NORMAL; | |
b4766d5f | 172 | highlight_on = ANSI_HIGHLIGHT_RED; |
31f7bf19 ZJS |
173 | } |
174 | } | |
175 | ||
47d80904 UU |
176 | /* A special case: make sure that we print a newline when |
177 | the message is empty. */ | |
178 | if (message_len == 0) | |
179 | fputs("\n", f); | |
180 | ||
a6f0104a ZJS |
181 | for (pos = message; |
182 | pos < message + message_len; | |
183 | pos = end + 1, line++) { | |
184 | bool continuation = line > 0; | |
185 | bool tail_line; | |
31f7bf19 ZJS |
186 | int len; |
187 | for (end = pos; end < message + message_len && *end != '\n'; end++) | |
188 | ; | |
189 | len = end - pos; | |
190 | assert(len >= 0); | |
191 | ||
2526d626 | 192 | /* We need to figure out when we are showing not-last line, *and* |
a6f0104a ZJS |
193 | * will skip subsequent lines. In that case, we will put the dots |
194 | * at the end of the line, instead of putting dots in the middle | |
195 | * or not at all. | |
196 | */ | |
197 | tail_line = | |
198 | line + 1 == PRINT_LINE_THRESHOLD || | |
2526d626 | 199 | end + 1 >= message + PRINT_CHAR_THRESHOLD; |
a6f0104a ZJS |
200 | |
201 | if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) || | |
202 | (prefix + len + 1 < n_columns && !tail_line)) { | |
b4766d5f ZJS |
203 | if (highlight && |
204 | (size_t) (pos - message) <= highlight[0] && | |
205 | highlight[0] < (size_t) len) { | |
206 | ||
207 | fprintf(f, "%*s%s%.*s", | |
208 | continuation * prefix, "", | |
209 | color_on, (int) highlight[0], pos); | |
210 | fprintf(f, "%s%.*s", | |
211 | highlight_on, | |
212 | (int) (MIN((size_t) len, highlight[1]) - highlight[0]), | |
213 | pos + highlight[0]); | |
214 | if ((size_t) len > highlight[1]) | |
215 | fprintf(f, "%s%.*s", | |
216 | color_on, | |
217 | (int) (len - highlight[1]), | |
218 | pos + highlight[1]); | |
219 | fprintf(f, "%s\n", color_off); | |
220 | ||
221 | } else | |
222 | fprintf(f, "%*s%s%.*s%s\n", | |
223 | continuation * prefix, "", | |
224 | color_on, len, pos, color_off); | |
a6f0104a ZJS |
225 | continue; |
226 | } | |
31f7bf19 | 227 | |
a6f0104a ZJS |
228 | /* Beyond this point, ellipsization will happen. */ |
229 | ellipsized = true; | |
31f7bf19 | 230 | |
a6f0104a ZJS |
231 | if (prefix < n_columns && n_columns - prefix >= 3) { |
232 | if (n_columns - prefix > (unsigned) len + 3) | |
233 | fprintf(f, "%*s%s%.*s...%s\n", | |
b4b02cbe ZJS |
234 | continuation * prefix, "", |
235 | color_on, len, pos, color_off); | |
a6f0104a ZJS |
236 | else { |
237 | _cleanup_free_ char *e; | |
238 | ||
239 | e = ellipsize_mem(pos, len, n_columns - prefix, | |
240 | tail_line ? 100 : 90); | |
241 | if (!e) | |
242 | fprintf(f, "%*s%s%.*s%s\n", | |
243 | continuation * prefix, "", | |
244 | color_on, len, pos, color_off); | |
245 | else | |
246 | fprintf(f, "%*s%s%s%s\n", | |
247 | continuation * prefix, "", | |
248 | color_on, e, color_off); | |
249 | } | |
250 | } else | |
31f7bf19 ZJS |
251 | fputs("...\n", f); |
252 | ||
a6f0104a ZJS |
253 | if (tail_line) |
254 | break; | |
31f7bf19 | 255 | } |
94e0bd7d ZJS |
256 | |
257 | return ellipsized; | |
31f7bf19 ZJS |
258 | } |
259 | ||
29a753df LP |
260 | static int output_timestamp_monotonic(FILE *f, sd_journal *j, const char *monotonic) { |
261 | sd_id128_t boot_id; | |
262 | uint64_t t; | |
263 | int r; | |
264 | ||
265 | assert(f); | |
266 | assert(j); | |
267 | ||
268 | r = -ENXIO; | |
269 | if (monotonic) | |
270 | r = safe_atou64(monotonic, &t); | |
271 | if (r < 0) | |
272 | r = sd_journal_get_monotonic_usec(j, &t, &boot_id); | |
273 | if (r < 0) | |
274 | return log_error_errno(r, "Failed to get monotonic timestamp: %m"); | |
275 | ||
70887c5f | 276 | fprintf(f, "[%5"PRI_USEC".%06"PRI_USEC"]", t / USEC_PER_SEC, t % USEC_PER_SEC); |
29a753df LP |
277 | return 1 + 5 + 1 + 6 + 1; |
278 | } | |
279 | ||
280 | static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, OutputFlags flags, const char *realtime) { | |
281 | char buf[MAX(FORMAT_TIMESTAMP_MAX, 64)]; | |
282 | struct tm *(*gettime_r)(const time_t *, struct tm *); | |
283 | struct tm tm; | |
284 | uint64_t x; | |
285 | time_t t; | |
286 | int r; | |
287 | ||
288 | assert(f); | |
289 | assert(j); | |
290 | ||
29a753df LP |
291 | if (realtime) |
292 | r = safe_atou64(realtime, &x); | |
03d1319b | 293 | if (!realtime || r < 0 || !VALID_REALTIME(x)) |
29a753df LP |
294 | r = sd_journal_get_realtime_usec(j, &x); |
295 | if (r < 0) | |
296 | return log_error_errno(r, "Failed to get realtime timestamp: %m"); | |
297 | ||
49805b3d | 298 | if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) { |
29a753df LP |
299 | const char *k; |
300 | ||
301 | if (flags & OUTPUT_UTC) | |
302 | k = format_timestamp_utc(buf, sizeof(buf), x); | |
303 | else | |
304 | k = format_timestamp(buf, sizeof(buf), x); | |
305 | if (!k) { | |
d3d28024 | 306 | log_error("Failed to format timestamp: %"PRIu64, x); |
29a753df LP |
307 | return -EINVAL; |
308 | } | |
309 | ||
310 | } else { | |
7e563bfc IW |
311 | char usec[7]; |
312 | ||
29a753df LP |
313 | gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; |
314 | t = (time_t) (x / USEC_PER_SEC); | |
315 | ||
316 | switch (mode) { | |
317 | ||
318 | case OUTPUT_SHORT_UNIX: | |
70887c5f | 319 | xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, x % USEC_PER_SEC); |
29a753df LP |
320 | break; |
321 | ||
322 | case OUTPUT_SHORT_ISO: | |
323 | if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)) <= 0) { | |
7e563bfc IW |
324 | log_error("Failed to format ISO time"); |
325 | return -EINVAL; | |
326 | } | |
327 | break; | |
328 | ||
329 | case OUTPUT_SHORT_ISO_PRECISE: | |
330 | /* No usec in strftime, so we leave space and copy over */ | |
331 | if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.xxxxxx%z", gettime_r(&t, &tm)) <= 0) { | |
332 | log_error("Failed to format ISO-precise time"); | |
29a753df LP |
333 | return -EINVAL; |
334 | } | |
7e563bfc IW |
335 | xsprintf(usec, "%06"PRI_USEC, x % USEC_PER_SEC); |
336 | memcpy(buf + 20, usec, 6); | |
29a753df LP |
337 | break; |
338 | ||
339 | case OUTPUT_SHORT: | |
340 | case OUTPUT_SHORT_PRECISE: | |
341 | ||
342 | if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)) <= 0) { | |
343 | log_error("Failed to format syslog time"); | |
344 | return -EINVAL; | |
345 | } | |
346 | ||
347 | if (mode == OUTPUT_SHORT_PRECISE) { | |
348 | size_t k; | |
349 | ||
350 | assert(sizeof(buf) > strlen(buf)); | |
351 | k = sizeof(buf) - strlen(buf); | |
352 | ||
70887c5f | 353 | r = snprintf(buf + strlen(buf), k, ".%06"PRIu64, x % USEC_PER_SEC); |
29a753df LP |
354 | if (r <= 0 || (size_t) r >= k) { /* too long? */ |
355 | log_error("Failed to format precise time"); | |
356 | return -EINVAL; | |
357 | } | |
358 | } | |
359 | break; | |
360 | ||
361 | default: | |
362 | assert_not_reached("Unknown time format"); | |
363 | } | |
364 | } | |
365 | ||
366 | fputs(buf, f); | |
367 | return (int) strlen(buf); | |
368 | } | |
369 | ||
08ace05b LP |
370 | static int output_short( |
371 | FILE *f, | |
372 | sd_journal *j, | |
373 | OutputMode mode, | |
374 | unsigned n_columns, | |
cc25a67e | 375 | OutputFlags flags, |
b4766d5f ZJS |
376 | Set *output_fields, |
377 | size_t highlight[2]) { | |
08ace05b | 378 | |
86aa7ba4 | 379 | int r; |
86aa7ba4 LP |
380 | const void *data; |
381 | size_t length; | |
382 | size_t n = 0; | |
49805b3d LB |
383 | _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL, *unit = NULL, *user_unit = NULL; |
384 | 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, unit_len = 0, user_unit_len = 0; | |
49826187 | 385 | int p = LOG_INFO; |
94e0bd7d | 386 | bool ellipsized = false; |
ed054520 VC |
387 | const ParseFieldVec fields[] = { |
388 | PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len), | |
389 | PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len), | |
390 | PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len), | |
391 | PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len), | |
392 | PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len), | |
393 | PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len), | |
394 | PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len), | |
395 | PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len), | |
396 | PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len), | |
49805b3d LB |
397 | PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len), |
398 | PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len), | |
ed054520 | 399 | }; |
b4766d5f | 400 | size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0}; |
86aa7ba4 | 401 | |
08ace05b | 402 | assert(f); |
86aa7ba4 LP |
403 | assert(j); |
404 | ||
a6f0104a | 405 | /* Set the threshold to one bigger than the actual print |
69ab8088 | 406 | * threshold, so that if the line is actually longer than what |
a6f0104a ZJS |
407 | * we're willing to print, ellipsization will occur. This way |
408 | * we won't output a misleading line without any indication of | |
409 | * truncation. | |
410 | */ | |
411 | sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1); | |
93b73b06 | 412 | |
a72b6353 | 413 | JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { |
ed054520 | 414 | r = parse_fieldv(data, length, fields, ELEMENTSOF(fields)); |
55d7bfc1 | 415 | if (r < 0) |
08ace05b | 416 | return r; |
55d7bfc1 | 417 | } |
d00f1d57 LP |
418 | if (r == -EBADMSG) { |
419 | log_debug_errno(r, "Skipping message we can't read: %m"); | |
420 | return 0; | |
421 | } | |
a72b6353 | 422 | if (r < 0) |
b56d608e | 423 | return log_error_errno(r, "Failed to get journal fields: %m"); |
a72b6353 | 424 | |
07d21025 LP |
425 | if (!message) { |
426 | log_debug("Skipping message without MESSAGE= field."); | |
08ace05b | 427 | return 0; |
07d21025 | 428 | } |
55d7bfc1 | 429 | |
e8bc0ea2 | 430 | if (!(flags & OUTPUT_SHOW_ALL)) |
b4766d5f | 431 | strip_tab_ansi(&message, &message_len, highlight_shifted); |
e8bc0ea2 | 432 | |
49826187 LP |
433 | if (priority_len == 1 && *priority >= '0' && *priority <= '7') |
434 | p = *priority - '0'; | |
435 | ||
29a753df LP |
436 | if (mode == OUTPUT_SHORT_MONOTONIC) |
437 | r = output_timestamp_monotonic(f, j, monotonic); | |
438 | else | |
439 | r = output_timestamp_realtime(f, j, mode, flags, realtime); | |
440 | if (r < 0) | |
441 | return r; | |
442 | n += r; | |
86aa7ba4 | 443 | |
29a753df | 444 | if (flags & OUTPUT_NO_HOSTNAME) { |
991e274b | 445 | /* Suppress display of the hostname if this is requested. */ |
12104159 | 446 | hostname = mfree(hostname); |
991e274b LP |
447 | hostname_len = 0; |
448 | } | |
449 | ||
08ace05b LP |
450 | if (hostname && shall_print(hostname, hostname_len, flags)) { |
451 | fprintf(f, " %.*s", (int) hostname_len, hostname); | |
55d7bfc1 LP |
452 | n += hostname_len + 1; |
453 | } | |
454 | ||
49805b3d LB |
455 | if (mode == OUTPUT_WITH_UNIT && ((unit && shall_print(unit, unit_len, flags)) || (user_unit && shall_print(user_unit, user_unit_len, flags)))) { |
456 | if (unit) { | |
457 | fprintf(f, " %.*s", (int) unit_len, unit); | |
458 | n += unit_len + 1; | |
459 | } | |
460 | if (user_unit) { | |
461 | if (unit) | |
462 | fprintf(f, "/%.*s", (int) user_unit_len, user_unit); | |
463 | else | |
464 | fprintf(f, " %.*s", (int) user_unit_len, user_unit); | |
465 | n += unit_len + 1; | |
466 | } | |
467 | } else if (identifier && shall_print(identifier, identifier_len, flags)) { | |
08ace05b | 468 | fprintf(f, " %.*s", (int) identifier_len, identifier); |
4cd9a9d9 | 469 | n += identifier_len + 1; |
08ace05b LP |
470 | } else if (comm && shall_print(comm, comm_len, flags)) { |
471 | fprintf(f, " %.*s", (int) comm_len, comm); | |
55d7bfc1 | 472 | n += comm_len + 1; |
b5936820 | 473 | } else |
1248e840 | 474 | fputs(" unknown", f); |
86aa7ba4 | 475 | |
08ace05b LP |
476 | if (pid && shall_print(pid, pid_len, flags)) { |
477 | fprintf(f, "[%.*s]", (int) pid_len, pid); | |
55d7bfc1 | 478 | n += pid_len + 2; |
08ace05b LP |
479 | } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) { |
480 | fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid); | |
6c1e6b98 | 481 | n += fake_pid_len + 2; |
86aa7ba4 LP |
482 | } |
483 | ||
31f7bf19 | 484 | if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) { |
e6acda19 | 485 | char bytes[FORMAT_BYTES_MAX]; |
08ace05b | 486 | fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); |
31f7bf19 ZJS |
487 | } else { |
488 | fputs(": ", f); | |
94e0bd7d | 489 | ellipsized |= |
b4766d5f ZJS |
490 | print_multiline(f, n + 2, n_columns, flags, p, |
491 | message, message_len, | |
492 | highlight_shifted); | |
31f7bf19 | 493 | } |
55d7bfc1 | 494 | |
d4205751 LP |
495 | if (flags & OUTPUT_CATALOG) |
496 | print_catalog(f, j); | |
497 | ||
94e0bd7d | 498 | return ellipsized; |
86aa7ba4 LP |
499 | } |
500 | ||
08ace05b LP |
501 | static int output_verbose( |
502 | FILE *f, | |
503 | sd_journal *j, | |
504 | OutputMode mode, | |
505 | unsigned n_columns, | |
cc25a67e | 506 | OutputFlags flags, |
b4766d5f ZJS |
507 | Set *output_fields, |
508 | size_t highlight[2]) { | |
08ace05b | 509 | |
86aa7ba4 LP |
510 | const void *data; |
511 | size_t length; | |
7fd1b19b | 512 | _cleanup_free_ char *cursor = NULL; |
d813d7a3 | 513 | uint64_t realtime = 0; |
f02d8367 | 514 | char ts[FORMAT_TIMESTAMP_MAX + 7]; |
8924973a | 515 | const char *timestamp; |
86aa7ba4 LP |
516 | int r; |
517 | ||
08ace05b | 518 | assert(f); |
86aa7ba4 LP |
519 | assert(j); |
520 | ||
93b73b06 LP |
521 | sd_journal_set_data_threshold(j, 0); |
522 | ||
cf40f0be ZJS |
523 | r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length); |
524 | if (r == -ENOENT) | |
525 | log_debug("Source realtime timestamp not found"); | |
b56d608e LP |
526 | else if (r < 0) |
527 | return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get source realtime timestamp: %m"); | |
528 | else { | |
cf40f0be | 529 | _cleanup_free_ char *value = NULL; |
cf40f0be | 530 | |
fbd0b64f LP |
531 | r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", |
532 | STRLEN("_SOURCE_REALTIME_TIMESTAMP="), &value, | |
533 | NULL); | |
cf40f0be | 534 | if (r < 0) |
e64c53fd | 535 | return r; |
d813d7a3 LP |
536 | assert(r > 0); |
537 | ||
538 | r = safe_atou64(value, &realtime); | |
539 | if (r < 0) | |
540 | log_debug_errno(r, "Failed to parse realtime timestamp: %m"); | |
cf40f0be ZJS |
541 | } |
542 | ||
543 | if (r < 0) { | |
544 | r = sd_journal_get_realtime_usec(j, &realtime); | |
b56d608e LP |
545 | if (r < 0) |
546 | return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get realtime timestamp: %m"); | |
86aa7ba4 LP |
547 | } |
548 | ||
549 | r = sd_journal_get_cursor(j, &cursor); | |
f647962d MS |
550 | if (r < 0) |
551 | return log_error_errno(r, "Failed to get cursor: %m"); | |
86aa7ba4 | 552 | |
8924973a ZJS |
553 | timestamp = flags & OUTPUT_UTC ? format_timestamp_us_utc(ts, sizeof ts, realtime) |
554 | : format_timestamp_us(ts, sizeof ts, realtime); | |
08ace05b | 555 | fprintf(f, "%s [%s]\n", |
8924973a | 556 | timestamp ?: "(no timestamp)", |
08ace05b | 557 | cursor); |
86aa7ba4 | 558 | |
a72b6353 | 559 | JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { |
31f7bf19 ZJS |
560 | const char *c; |
561 | int fieldlen; | |
7ac4fa7e ZJS |
562 | const char *on = "", *off = ""; |
563 | ||
31f7bf19 ZJS |
564 | c = memchr(data, '=', length); |
565 | if (!c) { | |
566 | log_error("Invalid field."); | |
567 | return -EINVAL; | |
568 | } | |
569 | fieldlen = c - (const char*) data; | |
86aa7ba4 | 570 | |
cc25a67e LK |
571 | r = field_set_test(output_fields, data, fieldlen); |
572 | if (r < 0) | |
573 | return r; | |
574 | if (!r) | |
575 | continue; | |
576 | ||
7ac4fa7e | 577 | if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) { |
1fc464f6 LP |
578 | on = ANSI_HIGHLIGHT; |
579 | off = ANSI_NORMAL; | |
7ac4fa7e ZJS |
580 | } |
581 | ||
8980058a | 582 | if ((flags & OUTPUT_SHOW_ALL) || |
a6f0104a ZJS |
583 | (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH) |
584 | && utf8_is_printable(data, length))) { | |
7ac4fa7e | 585 | fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data); |
b4766d5f | 586 | print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1, NULL); |
7ac4fa7e | 587 | fputs(off, f); |
31f7bf19 ZJS |
588 | } else { |
589 | char bytes[FORMAT_BYTES_MAX]; | |
86aa7ba4 | 590 | |
7ac4fa7e ZJS |
591 | fprintf(f, " %s%.*s=[%s blob data]%s\n", |
592 | on, | |
31f7bf19 ZJS |
593 | (int) (c - (const char*) data), |
594 | (const char*) data, | |
7ac4fa7e ZJS |
595 | format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1), |
596 | off); | |
31f7bf19 | 597 | } |
86aa7ba4 LP |
598 | } |
599 | ||
a72b6353 ZJS |
600 | if (r < 0) |
601 | return r; | |
602 | ||
d4205751 LP |
603 | if (flags & OUTPUT_CATALOG) |
604 | print_catalog(f, j); | |
605 | ||
86aa7ba4 LP |
606 | return 0; |
607 | } | |
608 | ||
08ace05b LP |
609 | static int output_export( |
610 | FILE *f, | |
611 | sd_journal *j, | |
612 | OutputMode mode, | |
613 | unsigned n_columns, | |
cc25a67e | 614 | OutputFlags flags, |
b4766d5f ZJS |
615 | Set *output_fields, |
616 | size_t highlight[2]) { | |
08ace05b | 617 | |
86aa7ba4 LP |
618 | sd_id128_t boot_id; |
619 | char sid[33]; | |
620 | int r; | |
621 | usec_t realtime, monotonic; | |
7fd1b19b | 622 | _cleanup_free_ char *cursor = NULL; |
86aa7ba4 LP |
623 | const void *data; |
624 | size_t length; | |
625 | ||
626 | assert(j); | |
627 | ||
93b73b06 LP |
628 | sd_journal_set_data_threshold(j, 0); |
629 | ||
86aa7ba4 | 630 | r = sd_journal_get_realtime_usec(j, &realtime); |
f647962d MS |
631 | if (r < 0) |
632 | return log_error_errno(r, "Failed to get realtime timestamp: %m"); | |
86aa7ba4 LP |
633 | |
634 | r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); | |
f647962d MS |
635 | if (r < 0) |
636 | return log_error_errno(r, "Failed to get monotonic timestamp: %m"); | |
86aa7ba4 LP |
637 | |
638 | r = sd_journal_get_cursor(j, &cursor); | |
f647962d MS |
639 | if (r < 0) |
640 | return log_error_errno(r, "Failed to get cursor: %m"); | |
86aa7ba4 | 641 | |
08ace05b LP |
642 | fprintf(f, |
643 | "__CURSOR=%s\n" | |
de0671ee ZJS |
644 | "__REALTIME_TIMESTAMP="USEC_FMT"\n" |
645 | "__MONOTONIC_TIMESTAMP="USEC_FMT"\n" | |
08ace05b LP |
646 | "_BOOT_ID=%s\n", |
647 | cursor, | |
de0671ee ZJS |
648 | realtime, |
649 | monotonic, | |
08ace05b | 650 | sd_id128_to_string(boot_id, sid)); |
86aa7ba4 | 651 | |
a72b6353 | 652 | JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { |
cc25a67e | 653 | const char *c; |
86aa7ba4 | 654 | |
0ab896b3 ZJS |
655 | /* We already printed the boot id from the data in the header, hence let's suppress it here */ |
656 | if (memory_startswith(data, length, "_BOOT_ID=")) | |
112301ae LP |
657 | continue; |
658 | ||
cc25a67e LK |
659 | c = memchr(data, '=', length); |
660 | if (!c) { | |
661 | log_error("Invalid field."); | |
662 | return -EINVAL; | |
663 | } | |
664 | ||
665 | r = field_set_test(output_fields, data, c - (const char *) data); | |
666 | if (r < 0) | |
667 | return r; | |
668 | if (!r) | |
669 | continue; | |
670 | ||
0ade5ffe ZJS |
671 | if (utf8_is_printable_newline(data, length, false)) |
672 | fwrite(data, length, 1, f); | |
673 | else { | |
86aa7ba4 LP |
674 | uint64_t le64; |
675 | ||
08ace05b LP |
676 | fwrite(data, c - (const char*) data, 1, f); |
677 | fputc('\n', f); | |
86aa7ba4 | 678 | le64 = htole64(length - (c - (const char*) data) - 1); |
08ace05b LP |
679 | fwrite(&le64, sizeof(le64), 1, f); |
680 | fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f); | |
0ade5ffe | 681 | } |
86aa7ba4 | 682 | |
08ace05b | 683 | fputc('\n', f); |
86aa7ba4 | 684 | } |
4f5e1723 AW |
685 | if (r == -EBADMSG) { |
686 | log_debug_errno(r, "Skipping message we can't read: %m"); | |
687 | return 0; | |
688 | } | |
86aa7ba4 | 689 | |
a72b6353 ZJS |
690 | if (r < 0) |
691 | return r; | |
692 | ||
08ace05b | 693 | fputc('\n', f); |
86aa7ba4 LP |
694 | |
695 | return 0; | |
696 | } | |
697 | ||
240a5fe8 | 698 | void json_escape( |
08ace05b LP |
699 | FILE *f, |
700 | const char* p, | |
701 | size_t l, | |
702 | OutputFlags flags) { | |
703 | ||
704 | assert(f); | |
705 | assert(p); | |
706 | ||
93b73b06 | 707 | if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) |
08ace05b LP |
708 | fputs("null", f); |
709 | ||
8980058a | 710 | else if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(p, l)) { |
86aa7ba4 LP |
711 | bool not_first = false; |
712 | ||
08ace05b | 713 | fputs("[ ", f); |
86aa7ba4 LP |
714 | |
715 | while (l > 0) { | |
716 | if (not_first) | |
08ace05b | 717 | fprintf(f, ", %u", (uint8_t) *p); |
86aa7ba4 LP |
718 | else { |
719 | not_first = true; | |
08ace05b | 720 | fprintf(f, "%u", (uint8_t) *p); |
86aa7ba4 LP |
721 | } |
722 | ||
723 | p++; | |
724 | l--; | |
725 | } | |
726 | ||
08ace05b | 727 | fputs(" ]", f); |
86aa7ba4 | 728 | } else { |
08ace05b | 729 | fputc('\"', f); |
86aa7ba4 LP |
730 | |
731 | while (l > 0) { | |
4c701096 | 732 | if (IN_SET(*p, '"', '\\')) { |
08ace05b LP |
733 | fputc('\\', f); |
734 | fputc(*p, f); | |
31f7bf19 ZJS |
735 | } else if (*p == '\n') |
736 | fputs("\\n", f); | |
91a8a108 DM |
737 | else if ((uint8_t) *p < ' ') |
738 | fprintf(f, "\\u%04x", (uint8_t) *p); | |
08ace05b LP |
739 | else |
740 | fputc(*p, f); | |
86aa7ba4 LP |
741 | |
742 | p++; | |
743 | l--; | |
744 | } | |
745 | ||
08ace05b | 746 | fputc('\"', f); |
86aa7ba4 LP |
747 | } |
748 | } | |
749 | ||
08ace05b LP |
750 | static int output_json( |
751 | FILE *f, | |
752 | sd_journal *j, | |
753 | OutputMode mode, | |
754 | unsigned n_columns, | |
cc25a67e | 755 | OutputFlags flags, |
b4766d5f ZJS |
756 | Set *output_fields, |
757 | size_t highlight[2]) { | |
08ace05b | 758 | |
86aa7ba4 | 759 | uint64_t realtime, monotonic; |
7fd1b19b | 760 | _cleanup_free_ char *cursor = NULL; |
86aa7ba4 LP |
761 | const void *data; |
762 | size_t length; | |
763 | sd_id128_t boot_id; | |
2e729834 | 764 | char sid[33], *k; |
86aa7ba4 | 765 | int r; |
d99ae53a LP |
766 | Hashmap *h = NULL; |
767 | bool done, separator; | |
86aa7ba4 LP |
768 | |
769 | assert(j); | |
770 | ||
93b73b06 LP |
771 | sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); |
772 | ||
86aa7ba4 | 773 | r = sd_journal_get_realtime_usec(j, &realtime); |
f647962d MS |
774 | if (r < 0) |
775 | return log_error_errno(r, "Failed to get realtime timestamp: %m"); | |
86aa7ba4 LP |
776 | |
777 | r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); | |
f647962d MS |
778 | if (r < 0) |
779 | return log_error_errno(r, "Failed to get monotonic timestamp: %m"); | |
86aa7ba4 LP |
780 | |
781 | r = sd_journal_get_cursor(j, &cursor); | |
f647962d MS |
782 | if (r < 0) |
783 | return log_error_errno(r, "Failed to get cursor: %m"); | |
86aa7ba4 | 784 | |
a6e87e90 | 785 | if (mode == OUTPUT_JSON_PRETTY) |
08ace05b LP |
786 | fprintf(f, |
787 | "{\n" | |
788 | "\t\"__CURSOR\" : \"%s\",\n" | |
de0671ee ZJS |
789 | "\t\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\",\n" |
790 | "\t\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\",\n" | |
08ace05b LP |
791 | "\t\"_BOOT_ID\" : \"%s\"", |
792 | cursor, | |
de0671ee ZJS |
793 | realtime, |
794 | monotonic, | |
08ace05b | 795 | sd_id128_to_string(boot_id, sid)); |
48383c25 LP |
796 | else { |
797 | if (mode == OUTPUT_JSON_SSE) | |
798 | fputs("data: ", f); | |
799 | ||
08ace05b LP |
800 | fprintf(f, |
801 | "{ \"__CURSOR\" : \"%s\", " | |
de0671ee ZJS |
802 | "\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\", " |
803 | "\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\", " | |
08ace05b LP |
804 | "\"_BOOT_ID\" : \"%s\"", |
805 | cursor, | |
de0671ee ZJS |
806 | realtime, |
807 | monotonic, | |
08ace05b | 808 | sd_id128_to_string(boot_id, sid)); |
48383c25 | 809 | } |
86aa7ba4 | 810 | |
d5099efc | 811 | h = hashmap_new(&string_hash_ops); |
d99ae53a | 812 | if (!h) |
b56d608e | 813 | return log_oom(); |
d99ae53a LP |
814 | |
815 | /* First round, iterate through the entry and count how often each field appears */ | |
a72b6353 | 816 | JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { |
d99ae53a LP |
817 | const char *eq; |
818 | char *n; | |
819 | unsigned u; | |
86aa7ba4 | 820 | |
d27b725a | 821 | if (memory_startswith(data, length, "_BOOT_ID=")) |
112301ae LP |
822 | continue; |
823 | ||
d99ae53a LP |
824 | eq = memchr(data, '=', length); |
825 | if (!eq) | |
826 | continue; | |
86aa7ba4 | 827 | |
324d6aa9 | 828 | n = memdup_suffix0(data, eq - (const char*) data); |
d99ae53a | 829 | if (!n) { |
b56d608e | 830 | r = log_oom(); |
d99ae53a LP |
831 | goto finish; |
832 | } | |
a6e87e90 | 833 | |
d99ae53a LP |
834 | u = PTR_TO_UINT(hashmap_get(h, n)); |
835 | if (u == 0) { | |
836 | r = hashmap_put(h, n, UINT_TO_PTR(1)); | |
837 | if (r < 0) { | |
838 | free(n); | |
b56d608e | 839 | log_oom(); |
d99ae53a LP |
840 | goto finish; |
841 | } | |
842 | } else { | |
843 | r = hashmap_update(h, n, UINT_TO_PTR(u + 1)); | |
844 | free(n); | |
b56d608e LP |
845 | if (r < 0) { |
846 | log_oom(); | |
d99ae53a | 847 | goto finish; |
b56d608e | 848 | } |
d99ae53a | 849 | } |
86aa7ba4 | 850 | } |
4f5e1723 AW |
851 | if (r == -EBADMSG) { |
852 | log_debug_errno(r, "Skipping message we can't read: %m"); | |
853 | return 0; | |
854 | } | |
a72b6353 ZJS |
855 | if (r < 0) |
856 | return r; | |
857 | ||
d99ae53a LP |
858 | separator = true; |
859 | do { | |
860 | done = true; | |
861 | ||
862 | SD_JOURNAL_FOREACH_DATA(j, data, length) { | |
863 | const char *eq; | |
9be391d4 ZJS |
864 | char *kk; |
865 | _cleanup_free_ char *n = NULL; | |
d99ae53a LP |
866 | size_t m; |
867 | unsigned u; | |
868 | ||
0ab896b3 ZJS |
869 | /* We already printed the boot id from the data in |
870 | * the header, hence let's suppress it here */ | |
d27b725a | 871 | if (memory_startswith(data, length, "_BOOT_ID=")) |
d99ae53a LP |
872 | continue; |
873 | ||
874 | eq = memchr(data, '=', length); | |
875 | if (!eq) | |
876 | continue; | |
877 | ||
d99ae53a | 878 | m = eq - (const char*) data; |
324d6aa9 | 879 | n = memdup_suffix0(data, m); |
d99ae53a | 880 | if (!n) { |
b56d608e | 881 | r = log_oom(); |
d99ae53a LP |
882 | goto finish; |
883 | } | |
884 | ||
9be391d4 | 885 | if (output_fields && !set_get(output_fields, n)) |
cc25a67e | 886 | continue; |
cc25a67e | 887 | |
9be391d4 ZJS |
888 | if (separator) |
889 | fputs(mode == OUTPUT_JSON_PRETTY ? ",\n\t" : ", ", f); | |
cc25a67e | 890 | |
d99ae53a | 891 | u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); |
9be391d4 | 892 | if (u == 0) |
d99ae53a | 893 | /* We already printed this, let's jump to the next */ |
d99ae53a LP |
894 | separator = false; |
895 | ||
9be391d4 | 896 | else if (u == 1) { |
d99ae53a LP |
897 | /* Field only appears once, output it directly */ |
898 | ||
899 | json_escape(f, data, m, flags); | |
900 | fputs(" : ", f); | |
901 | ||
902 | json_escape(f, eq + 1, length - m - 1, flags); | |
903 | ||
904 | hashmap_remove(h, n); | |
905 | free(kk); | |
d99ae53a LP |
906 | |
907 | separator = true; | |
908 | ||
d99ae53a LP |
909 | } else { |
910 | /* Field appears multiple times, output it as array */ | |
911 | json_escape(f, data, m, flags); | |
912 | fputs(" : [ ", f); | |
913 | json_escape(f, eq + 1, length - m - 1, flags); | |
914 | ||
915 | /* Iterate through the end of the list */ | |
916 | ||
917 | while (sd_journal_enumerate_data(j, &data, &length) > 0) { | |
918 | if (length < m + 1) | |
919 | continue; | |
920 | ||
921 | if (memcmp(data, n, m) != 0) | |
922 | continue; | |
923 | ||
924 | if (((const char*) data)[m] != '=') | |
925 | continue; | |
926 | ||
927 | fputs(", ", f); | |
928 | json_escape(f, (const char*) data + m + 1, length - m - 1, flags); | |
929 | } | |
930 | ||
931 | fputs(" ]", f); | |
932 | ||
933 | hashmap_remove(h, n); | |
934 | free(kk); | |
d99ae53a LP |
935 | |
936 | /* Iterate data fields form the beginning */ | |
937 | done = false; | |
938 | separator = true; | |
939 | ||
940 | break; | |
941 | } | |
942 | } | |
943 | ||
944 | } while (!done); | |
945 | ||
a6e87e90 | 946 | if (mode == OUTPUT_JSON_PRETTY) |
08ace05b | 947 | fputs("\n}\n", f); |
48383c25 LP |
948 | else if (mode == OUTPUT_JSON_SSE) |
949 | fputs("}\n\n", f); | |
a6e87e90 | 950 | else |
08ace05b | 951 | fputs(" }\n", f); |
86aa7ba4 | 952 | |
d99ae53a LP |
953 | r = 0; |
954 | ||
955 | finish: | |
956 | while ((k = hashmap_steal_first_key(h))) | |
957 | free(k); | |
958 | ||
959 | hashmap_free(h); | |
960 | ||
961 | return r; | |
86aa7ba4 LP |
962 | } |
963 | ||
08ace05b LP |
964 | static int output_cat( |
965 | FILE *f, | |
966 | sd_journal *j, | |
967 | OutputMode mode, | |
968 | unsigned n_columns, | |
cc25a67e | 969 | OutputFlags flags, |
b4766d5f ZJS |
970 | Set *output_fields, |
971 | size_t highlight[2]) { | |
08ace05b | 972 | |
d3f2bdbf LP |
973 | const void *data; |
974 | size_t l; | |
975 | int r; | |
b4766d5f | 976 | const char *highlight_on = "", *highlight_off = ""; |
d3f2bdbf LP |
977 | |
978 | assert(j); | |
08ace05b | 979 | assert(f); |
d3f2bdbf | 980 | |
b4766d5f ZJS |
981 | if (flags & OUTPUT_COLOR) { |
982 | highlight_on = ANSI_HIGHLIGHT_RED; | |
983 | highlight_off = ANSI_NORMAL; | |
984 | } | |
985 | ||
93b73b06 LP |
986 | sd_journal_set_data_threshold(j, 0); |
987 | ||
d3f2bdbf | 988 | r = sd_journal_get_data(j, "MESSAGE", &data, &l); |
4f5e1723 AW |
989 | if (r == -EBADMSG) { |
990 | log_debug_errno(r, "Skipping message we can't read: %m"); | |
991 | return 0; | |
992 | } | |
d3f2bdbf | 993 | if (r < 0) { |
c198300f LP |
994 | /* An entry without MESSAGE=? */ |
995 | if (r == -ENOENT) | |
996 | return 0; | |
997 | ||
8d3d7072 | 998 | return log_error_errno(r, "Failed to get data: %m"); |
d3f2bdbf LP |
999 | } |
1000 | ||
1001 | assert(l >= 8); | |
1002 | ||
b4766d5f ZJS |
1003 | if (highlight && (flags & OUTPUT_COLOR)) { |
1004 | assert(highlight[0] <= highlight[1]); | |
1005 | assert(highlight[1] <= l - 8); | |
1006 | ||
1007 | fwrite((const char*) data + 8, 1, highlight[0], f); | |
1008 | fwrite(highlight_on, 1, strlen(highlight_on), f); | |
1009 | fwrite((const char*) data + 8 + highlight[0], 1, highlight[1] - highlight[0], f); | |
1010 | fwrite(highlight_off, 1, strlen(highlight_off), f); | |
1011 | fwrite((const char*) data + 8 + highlight[1], 1, l - 8 - highlight[1], f); | |
1012 | } else | |
1013 | fwrite((const char*) data + 8, 1, l - 8, f); | |
08ace05b | 1014 | fputc('\n', f); |
d3f2bdbf LP |
1015 | |
1016 | return 0; | |
1017 | } | |
1018 | ||
08ace05b LP |
1019 | static int (*output_funcs[_OUTPUT_MODE_MAX])( |
1020 | FILE *f, | |
1021 | sd_journal*j, | |
1022 | OutputMode mode, | |
1023 | unsigned n_columns, | |
cc25a67e | 1024 | OutputFlags flags, |
b4766d5f ZJS |
1025 | Set *output_fields, |
1026 | size_t highlight[2]) = { | |
08ace05b | 1027 | |
a6e87e90 | 1028 | [OUTPUT_SHORT] = output_short, |
44bc6e1f | 1029 | [OUTPUT_SHORT_ISO] = output_short, |
7e563bfc | 1030 | [OUTPUT_SHORT_ISO_PRECISE] = output_short, |
f02d8367 ZJS |
1031 | [OUTPUT_SHORT_PRECISE] = output_short, |
1032 | [OUTPUT_SHORT_MONOTONIC] = output_short, | |
bb321ed9 | 1033 | [OUTPUT_SHORT_UNIX] = output_short, |
29a753df | 1034 | [OUTPUT_SHORT_FULL] = output_short, |
86aa7ba4 LP |
1035 | [OUTPUT_VERBOSE] = output_verbose, |
1036 | [OUTPUT_EXPORT] = output_export, | |
d3f2bdbf | 1037 | [OUTPUT_JSON] = output_json, |
a6e87e90 | 1038 | [OUTPUT_JSON_PRETTY] = output_json, |
48383c25 | 1039 | [OUTPUT_JSON_SSE] = output_json, |
49805b3d LB |
1040 | [OUTPUT_CAT] = output_cat, |
1041 | [OUTPUT_WITH_UNIT] = output_short, | |
86aa7ba4 LP |
1042 | }; |
1043 | ||
9b972c9a | 1044 | int show_journal_entry( |
08ace05b LP |
1045 | FILE *f, |
1046 | sd_journal *j, | |
1047 | OutputMode mode, | |
1048 | unsigned n_columns, | |
94e0bd7d | 1049 | OutputFlags flags, |
cc25a67e | 1050 | char **output_fields, |
b4766d5f | 1051 | size_t highlight[2], |
94e0bd7d | 1052 | bool *ellipsized) { |
08ace05b | 1053 | |
e268b81e | 1054 | int ret; |
cc25a67e | 1055 | _cleanup_set_free_free_ Set *fields = NULL; |
df50185b | 1056 | assert(mode >= 0); |
86aa7ba4 LP |
1057 | assert(mode < _OUTPUT_MODE_MAX); |
1058 | ||
34a35ece LP |
1059 | if (n_columns <= 0) |
1060 | n_columns = columns(); | |
1061 | ||
cc25a67e LK |
1062 | if (output_fields) { |
1063 | fields = set_new(&string_hash_ops); | |
1064 | if (!fields) | |
1065 | return log_oom(); | |
1066 | ||
1067 | ret = set_put_strdupv(fields, output_fields); | |
1068 | if (ret < 0) | |
1069 | return ret; | |
1070 | } | |
1071 | ||
b4766d5f | 1072 | ret = output_funcs[mode](f, j, mode, n_columns, flags, fields, highlight); |
94e0bd7d ZJS |
1073 | |
1074 | if (ellipsized && ret > 0) | |
1075 | *ellipsized = true; | |
1076 | ||
e268b81e | 1077 | return ret; |
86aa7ba4 LP |
1078 | } |
1079 | ||
ea6c2dd1 LP |
1080 | static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) { |
1081 | assert(f); | |
1082 | assert(flags); | |
1083 | ||
1084 | if (!(*flags & OUTPUT_BEGIN_NEWLINE)) | |
1085 | return 0; | |
1086 | ||
1087 | /* Print a beginning new line if that's request, but only once | |
1088 | * on the first line we print. */ | |
1089 | ||
1090 | fputc('\n', f); | |
1091 | *flags &= ~OUTPUT_BEGIN_NEWLINE; | |
1092 | return 0; | |
1093 | } | |
1094 | ||
889e3960 ZJS |
1095 | int show_journal( |
1096 | FILE *f, | |
1097 | sd_journal *j, | |
1098 | OutputMode mode, | |
1099 | unsigned n_columns, | |
1100 | usec_t not_before, | |
1101 | unsigned how_many, | |
1102 | OutputFlags flags, | |
1103 | bool *ellipsized) { | |
86aa7ba4 | 1104 | |
86aa7ba4 | 1105 | int r; |
df50185b LP |
1106 | unsigned line = 0; |
1107 | bool need_seek = false; | |
085d7120 | 1108 | int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; |
86aa7ba4 | 1109 | |
1a6c43e9 | 1110 | assert(j); |
df50185b LP |
1111 | assert(mode >= 0); |
1112 | assert(mode < _OUTPUT_MODE_MAX); | |
1946b0bd | 1113 | |
889e3960 ZJS |
1114 | if (how_many == (unsigned) -1) |
1115 | need_seek = true; | |
1116 | else { | |
1117 | /* Seek to end */ | |
1118 | r = sd_journal_seek_tail(j); | |
1119 | if (r < 0) | |
1120 | return log_error_errno(r, "Failed to seek to tail: %m"); | |
86aa7ba4 | 1121 | |
889e3960 ZJS |
1122 | r = sd_journal_previous_skip(j, how_many); |
1123 | if (r < 0) | |
1124 | return log_error_errno(r, "Failed to skip previous: %m"); | |
1125 | } | |
86aa7ba4 | 1126 | |
df50185b LP |
1127 | for (;;) { |
1128 | for (;;) { | |
1129 | usec_t usec; | |
1130 | ||
1131 | if (need_seek) { | |
1132 | r = sd_journal_next(j); | |
1133 | if (r < 0) | |
b56d608e | 1134 | return log_error_errno(r, "Failed to iterate through journal: %m"); |
df50185b LP |
1135 | } |
1136 | ||
1137 | if (r == 0) | |
1138 | break; | |
86aa7ba4 | 1139 | |
df50185b LP |
1140 | need_seek = true; |
1141 | ||
1142 | if (not_before > 0) { | |
1143 | r = sd_journal_get_monotonic_usec(j, &usec, NULL); | |
1144 | ||
1145 | /* -ESTALE is returned if the | |
1146 | timestamp is not from this boot */ | |
1147 | if (r == -ESTALE) | |
1148 | continue; | |
1149 | else if (r < 0) | |
b56d608e | 1150 | return log_error_errno(r, "Failed to get journal time: %m"); |
df50185b LP |
1151 | |
1152 | if (usec < not_before) | |
1153 | continue; | |
1154 | } | |
1155 | ||
313cefa1 | 1156 | line++; |
ea6c2dd1 | 1157 | maybe_print_begin_newline(f, &flags); |
df50185b | 1158 | |
9b972c9a | 1159 | r = show_journal_entry(f, j, mode, n_columns, flags, NULL, NULL, ellipsized); |
df50185b | 1160 | if (r < 0) |
b56d608e | 1161 | return r; |
df50185b LP |
1162 | } |
1163 | ||
08984293 LP |
1164 | if (warn_cutoff && line < how_many && not_before > 0) { |
1165 | sd_id128_t boot_id; | |
a7f7d1bd | 1166 | usec_t cutoff = 0; |
08984293 LP |
1167 | |
1168 | /* Check whether the cutoff line is too early */ | |
1169 | ||
1170 | r = sd_id128_get_boot(&boot_id); | |
1171 | if (r < 0) | |
b56d608e | 1172 | return log_error_errno(r, "Failed to get boot id: %m"); |
08984293 LP |
1173 | |
1174 | r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL); | |
1175 | if (r < 0) | |
b56d608e | 1176 | return log_error_errno(r, "Failed to get journal cutoff time: %m"); |
08984293 | 1177 | |
ea6c2dd1 LP |
1178 | if (r > 0 && not_before < cutoff) { |
1179 | maybe_print_begin_newline(f, &flags); | |
08ace05b | 1180 | fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n"); |
ea6c2dd1 | 1181 | } |
08984293 LP |
1182 | |
1183 | warn_cutoff = false; | |
1184 | } | |
1185 | ||
085d7120 | 1186 | if (!(flags & OUTPUT_FOLLOW)) |
86aa7ba4 LP |
1187 | break; |
1188 | ||
3a43da28 | 1189 | r = sd_journal_wait(j, USEC_INFINITY); |
df50185b | 1190 | if (r < 0) |
b56d608e | 1191 | return log_error_errno(r, "Failed to wait for journal: %m"); |
df50185b | 1192 | |
86aa7ba4 LP |
1193 | } |
1194 | ||
b56d608e | 1195 | return 0; |
1a6c43e9 MT |
1196 | } |
1197 | ||
886a64fe | 1198 | int add_matches_for_unit(sd_journal *j, const char *unit) { |
2b45d881 | 1199 | const char *m1, *m2, *m3, *m4; |
1a6c43e9 MT |
1200 | int r; |
1201 | ||
886a64fe | 1202 | assert(j); |
1a6c43e9 MT |
1203 | assert(unit); |
1204 | ||
63c372cb LP |
1205 | m1 = strjoina("_SYSTEMD_UNIT=", unit); |
1206 | m2 = strjoina("COREDUMP_UNIT=", unit); | |
1207 | m3 = strjoina("UNIT=", unit); | |
1208 | m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit); | |
1a6c43e9 | 1209 | |
886a64fe ZJS |
1210 | (void)( |
1211 | /* Look for messages from the service itself */ | |
1212 | (r = sd_journal_add_match(j, m1, 0)) || | |
1213 | ||
1214 | /* Look for coredumps of the service */ | |
1215 | (r = sd_journal_add_disjunction(j)) || | |
fdcd37df ZJS |
1216 | (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || |
1217 | (r = sd_journal_add_match(j, "_UID=0", 0)) || | |
886a64fe ZJS |
1218 | (r = sd_journal_add_match(j, m2, 0)) || |
1219 | ||
1220 | /* Look for messages from PID 1 about this service */ | |
1221 | (r = sd_journal_add_disjunction(j)) || | |
1222 | (r = sd_journal_add_match(j, "_PID=1", 0)) || | |
2d0b2e87 ZJS |
1223 | (r = sd_journal_add_match(j, m3, 0)) || |
1224 | ||
1225 | /* Look for messages from authorized daemons about this service */ | |
1226 | (r = sd_journal_add_disjunction(j)) || | |
1227 | (r = sd_journal_add_match(j, "_UID=0", 0)) || | |
1228 | (r = sd_journal_add_match(j, m4, 0)) | |
886a64fe | 1229 | ); |
2d0b2e87 | 1230 | |
69ae3ee0 | 1231 | if (r == 0 && endswith(unit, ".slice")) { |
2b45d881 LP |
1232 | const char *m5; |
1233 | ||
1234 | m5 = strjoina("_SYSTEMD_SLICE=", unit); | |
69ae3ee0 ZJS |
1235 | |
1236 | /* Show all messages belonging to a slice */ | |
1237 | (void)( | |
1238 | (r = sd_journal_add_disjunction(j)) || | |
1239 | (r = sd_journal_add_match(j, m5, 0)) | |
1240 | ); | |
1241 | } | |
1242 | ||
886a64fe ZJS |
1243 | return r; |
1244 | } | |
1a6c43e9 | 1245 | |
886a64fe ZJS |
1246 | int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { |
1247 | int r; | |
2d0b2e87 ZJS |
1248 | char *m1, *m2, *m3, *m4; |
1249 | char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; | |
1a6c43e9 | 1250 | |
886a64fe ZJS |
1251 | assert(j); |
1252 | assert(unit); | |
1a6c43e9 | 1253 | |
63c372cb LP |
1254 | m1 = strjoina("_SYSTEMD_USER_UNIT=", unit); |
1255 | m2 = strjoina("USER_UNIT=", unit); | |
1256 | m3 = strjoina("COREDUMP_USER_UNIT=", unit); | |
1257 | m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit); | |
de0671ee | 1258 | sprintf(muid, "_UID="UID_FMT, uid); |
1a6c43e9 | 1259 | |
886a64fe ZJS |
1260 | (void) ( |
1261 | /* Look for messages from the user service itself */ | |
1262 | (r = sd_journal_add_match(j, m1, 0)) || | |
2d0b2e87 | 1263 | (r = sd_journal_add_match(j, muid, 0)) || |
886a64fe ZJS |
1264 | |
1265 | /* Look for messages from systemd about this service */ | |
1266 | (r = sd_journal_add_disjunction(j)) || | |
1267 | (r = sd_journal_add_match(j, m2, 0)) || | |
2d0b2e87 | 1268 | (r = sd_journal_add_match(j, muid, 0)) || |
886a64fe ZJS |
1269 | |
1270 | /* Look for coredumps of the service */ | |
1271 | (r = sd_journal_add_disjunction(j)) || | |
1272 | (r = sd_journal_add_match(j, m3, 0)) || | |
2d0b2e87 ZJS |
1273 | (r = sd_journal_add_match(j, muid, 0)) || |
1274 | (r = sd_journal_add_match(j, "_UID=0", 0)) || | |
1275 | ||
1276 | /* Look for messages from authorized daemons about this service */ | |
1277 | (r = sd_journal_add_disjunction(j)) || | |
fdcd37df | 1278 | (r = sd_journal_add_match(j, m4, 0)) || |
2d0b2e87 | 1279 | (r = sd_journal_add_match(j, muid, 0)) || |
fdcd37df | 1280 | (r = sd_journal_add_match(j, "_UID=0", 0)) |
886a64fe | 1281 | ); |
69ae3ee0 ZJS |
1282 | |
1283 | if (r == 0 && endswith(unit, ".slice")) { | |
2b45d881 LP |
1284 | const char *m5; |
1285 | ||
1286 | m5 = strjoina("_SYSTEMD_SLICE=", unit); | |
69ae3ee0 ZJS |
1287 | |
1288 | /* Show all messages belonging to a slice */ | |
1289 | (void)( | |
1290 | (r = sd_journal_add_disjunction(j)) || | |
1291 | (r = sd_journal_add_match(j, m5, 0)) || | |
1292 | (r = sd_journal_add_match(j, muid, 0)) | |
1293 | ); | |
1294 | } | |
1295 | ||
1a6c43e9 MT |
1296 | return r; |
1297 | } | |
1298 | ||
b6741478 | 1299 | static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { |
3d94f76c | 1300 | _cleanup_close_pair_ int pair[2] = { -1, -1 }; |
a4475f57 | 1301 | _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; |
b6741478 | 1302 | pid_t pid, child; |
b6741478 LP |
1303 | char buf[37]; |
1304 | ssize_t k; | |
1305 | int r; | |
1306 | ||
1307 | assert(machine); | |
1308 | assert(boot_id); | |
1309 | ||
affcf189 | 1310 | if (!machine_name_is_valid(machine)) |
b6741478 LP |
1311 | return -EINVAL; |
1312 | ||
e04b0cdb | 1313 | r = container_get_leader(machine, &pid); |
b6741478 LP |
1314 | if (r < 0) |
1315 | return r; | |
e04b0cdb | 1316 | |
671c3419 | 1317 | r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd); |
b6741478 LP |
1318 | if (r < 0) |
1319 | return r; | |
1320 | ||
e04b0cdb | 1321 | if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) |
b6741478 LP |
1322 | return -errno; |
1323 | ||
4c253ed1 LP |
1324 | r = safe_fork("(sd-bootid)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &child); |
1325 | if (r < 0) | |
1326 | return r; | |
1327 | if (r == 0) { | |
b6741478 LP |
1328 | int fd; |
1329 | ||
03e334a1 | 1330 | pair[0] = safe_close(pair[0]); |
b6741478 | 1331 | |
671c3419 | 1332 | r = namespace_enter(pidnsfd, mntnsfd, -1, -1, rootfd); |
b6741478 LP |
1333 | if (r < 0) |
1334 | _exit(EXIT_FAILURE); | |
1335 | ||
1336 | fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); | |
1337 | if (fd < 0) | |
1338 | _exit(EXIT_FAILURE); | |
1339 | ||
a6dcc7e5 | 1340 | r = loop_read_exact(fd, buf, 36, false); |
03e334a1 | 1341 | safe_close(fd); |
6c767d1e | 1342 | if (r < 0) |
b6741478 LP |
1343 | _exit(EXIT_FAILURE); |
1344 | ||
e04b0cdb | 1345 | k = send(pair[1], buf, 36, MSG_NOSIGNAL); |
b6741478 LP |
1346 | if (k != 36) |
1347 | _exit(EXIT_FAILURE); | |
1348 | ||
1349 | _exit(EXIT_SUCCESS); | |
1350 | } | |
1351 | ||
03e334a1 | 1352 | pair[1] = safe_close(pair[1]); |
b6741478 | 1353 | |
2e87a1fd LP |
1354 | r = wait_for_terminate_and_check("(sd-bootid)", child, 0); |
1355 | if (r < 0) | |
1356 | return r; | |
1357 | if (r != EXIT_SUCCESS) | |
1358 | return -EIO; | |
b6741478 | 1359 | |
fbadf045 LP |
1360 | k = recv(pair[0], buf, 36, 0); |
1361 | if (k != 36) | |
1362 | return -EIO; | |
1363 | ||
b6741478 LP |
1364 | buf[36] = 0; |
1365 | r = sd_id128_from_string(buf, boot_id); | |
1366 | if (r < 0) | |
1367 | return r; | |
1368 | ||
1369 | return 0; | |
1370 | } | |
1371 | ||
1372 | int add_match_this_boot(sd_journal *j, const char *machine) { | |
5ec76417 ZJS |
1373 | char match[9+32+1] = "_BOOT_ID="; |
1374 | sd_id128_t boot_id; | |
1375 | int r; | |
1376 | ||
1377 | assert(j); | |
1378 | ||
b6741478 LP |
1379 | if (machine) { |
1380 | r = get_boot_id_for_machine(machine, &boot_id); | |
f647962d MS |
1381 | if (r < 0) |
1382 | return log_error_errno(r, "Failed to get boot id of container %s: %m", machine); | |
b6741478 LP |
1383 | } else { |
1384 | r = sd_id128_get_boot(&boot_id); | |
f647962d MS |
1385 | if (r < 0) |
1386 | return log_error_errno(r, "Failed to get boot id: %m"); | |
5ec76417 ZJS |
1387 | } |
1388 | ||
1389 | sd_id128_to_string(boot_id, match + 9); | |
1390 | r = sd_journal_add_match(j, match, strlen(match)); | |
f647962d MS |
1391 | if (r < 0) |
1392 | return log_error_errno(r, "Failed to add match: %m"); | |
5ec76417 ZJS |
1393 | |
1394 | r = sd_journal_add_conjunction(j); | |
1395 | if (r < 0) | |
b56d608e | 1396 | return log_error_errno(r, "Failed to add conjunction: %m"); |
5ec76417 ZJS |
1397 | |
1398 | return 0; | |
1399 | } | |
1400 | ||
886a64fe | 1401 | int show_journal_by_unit( |
1a6c43e9 MT |
1402 | FILE *f, |
1403 | const char *unit, | |
1404 | OutputMode mode, | |
1405 | unsigned n_columns, | |
1406 | usec_t not_before, | |
1407 | unsigned how_many, | |
1408 | uid_t uid, | |
886a64fe | 1409 | OutputFlags flags, |
3c756001 LP |
1410 | int journal_open_flags, |
1411 | bool system_unit, | |
94e0bd7d | 1412 | bool *ellipsized) { |
1a6c43e9 | 1413 | |
4afd3348 | 1414 | _cleanup_(sd_journal_closep) sd_journal *j = NULL; |
1a6c43e9 MT |
1415 | int r; |
1416 | ||
1417 | assert(mode >= 0); | |
1418 | assert(mode < _OUTPUT_MODE_MAX); | |
1419 | assert(unit); | |
1420 | ||
1a6c43e9 MT |
1421 | if (how_many <= 0) |
1422 | return 0; | |
1423 | ||
3c756001 | 1424 | r = sd_journal_open(&j, journal_open_flags); |
f9045468 | 1425 | if (r < 0) |
b56d608e | 1426 | return log_error_errno(r, "Failed to open journal: %m"); |
f9045468 | 1427 | |
b6741478 | 1428 | r = add_match_this_boot(j, NULL); |
5ec76417 ZJS |
1429 | if (r < 0) |
1430 | return r; | |
1431 | ||
3c756001 | 1432 | if (system_unit) |
886a64fe ZJS |
1433 | r = add_matches_for_unit(j, unit); |
1434 | else | |
1435 | r = add_matches_for_user_unit(j, unit, uid); | |
1a6c43e9 | 1436 | if (r < 0) |
b56d608e | 1437 | return log_error_errno(r, "Failed to add unit matches: %m"); |
1a6c43e9 | 1438 | |
f1d34068 | 1439 | if (DEBUG_LOGGING) { |
4ad16808 ZJS |
1440 | _cleanup_free_ char *filter; |
1441 | ||
1442 | filter = journal_make_match_string(j); | |
b56d608e LP |
1443 | if (!filter) |
1444 | return log_oom(); | |
1445 | ||
4ad16808 ZJS |
1446 | log_debug("Journal filter: %s", filter); |
1447 | } | |
5ec76417 | 1448 | |
94e0bd7d | 1449 | return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized); |
86aa7ba4 | 1450 | } |