]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/logs-show.c
strv: multiple cleanups
[thirdparty/systemd.git] / src / shared / logs-show.c
CommitLineData
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>
2a410422 26#include <sys/socket.h>
55d7bfc1 27#include <string.h>
b6741478 28#include <fcntl.h>
86aa7ba4
LP
29
30#include "logs-show.h"
31#include "log.h"
32#include "util.h"
ba961854 33#include "utf8.h"
d99ae53a 34#include "hashmap.h"
b6741478 35#include "fileio.h"
dfb33a97 36#include "journal-internal.h"
86aa7ba4 37
a6f0104a
ZJS
38/* up to three lines (each up to 100 characters),
39 or 300 characters, whichever is less */
40#define PRINT_LINE_THRESHOLD 3
41#define PRINT_CHAR_THRESHOLD 300
42
08ace05b 43#define JSON_THRESHOLD 4096
86aa7ba4 44
d4205751
LP
45static int print_catalog(FILE *f, sd_journal *j) {
46 int r;
47 _cleanup_free_ char *t = NULL, *z = NULL;
48
49
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
55d7bfc1
LP
65static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
66 size_t fl, nl;
67 void *buf;
68
69 assert(data);
70 assert(field);
71 assert(target);
72 assert(target_size);
73
74 fl = strlen(field);
75 if (length < fl)
76 return 0;
77
78 if (memcmp(data, field, fl))
79 return 0;
80
81 nl = length - fl;
bf967366 82 buf = malloc(nl+1);
0d0f0c50
SL
83 if (!buf)
84 return log_oom();
55d7bfc1 85
46b0d922
LP
86 memcpy(buf, (const char*) data + fl, nl);
87 ((char*)buf)[nl] = 0;
88
6c1e6b98 89 free(*target);
55d7bfc1
LP
90 *target = buf;
91 *target_size = nl;
92
93 return 1;
94}
95
08ace05b
LP
96static bool shall_print(const char *p, size_t l, OutputFlags flags) {
97 assert(p);
98
99 if (flags & OUTPUT_SHOW_ALL)
55d7bfc1
LP
100 return true;
101
a6f0104a 102 if (l >= PRINT_CHAR_THRESHOLD)
55d7bfc1
LP
103 return false;
104
31f7bf19 105 if (!utf8_is_printable(p, l))
55d7bfc1
LP
106 return false;
107
108 return true;
109}
110
00f117a5 111static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) {
31f7bf19
ZJS
112 const char *color_on = "", *color_off = "";
113 const char *pos, *end;
94e0bd7d 114 bool ellipsized = false;
a6f0104a 115 int line = 0;
31f7bf19
ZJS
116
117 if (flags & OUTPUT_COLOR) {
118 if (priority <= LOG_ERR) {
119 color_on = ANSI_HIGHLIGHT_RED_ON;
120 color_off = ANSI_HIGHLIGHT_OFF;
121 } else if (priority <= LOG_NOTICE) {
122 color_on = ANSI_HIGHLIGHT_ON;
123 color_off = ANSI_HIGHLIGHT_OFF;
124 }
125 }
126
a6f0104a
ZJS
127 for (pos = message;
128 pos < message + message_len;
129 pos = end + 1, line++) {
130 bool continuation = line > 0;
131 bool tail_line;
31f7bf19
ZJS
132 int len;
133 for (end = pos; end < message + message_len && *end != '\n'; end++)
134 ;
135 len = end - pos;
136 assert(len >= 0);
137
2526d626 138 /* We need to figure out when we are showing not-last line, *and*
a6f0104a
ZJS
139 * will skip subsequent lines. In that case, we will put the dots
140 * at the end of the line, instead of putting dots in the middle
141 * or not at all.
142 */
143 tail_line =
144 line + 1 == PRINT_LINE_THRESHOLD ||
2526d626 145 end + 1 >= message + PRINT_CHAR_THRESHOLD;
a6f0104a
ZJS
146
147 if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) ||
148 (prefix + len + 1 < n_columns && !tail_line)) {
31f7bf19
ZJS
149 fprintf(f, "%*s%s%.*s%s\n",
150 continuation * prefix, "",
151 color_on, len, pos, color_off);
a6f0104a
ZJS
152 continue;
153 }
31f7bf19 154
a6f0104a
ZJS
155 /* Beyond this point, ellipsization will happen. */
156 ellipsized = true;
31f7bf19 157
a6f0104a
ZJS
158 if (prefix < n_columns && n_columns - prefix >= 3) {
159 if (n_columns - prefix > (unsigned) len + 3)
160 fprintf(f, "%*s%s%.*s...%s\n",
b4b02cbe
ZJS
161 continuation * prefix, "",
162 color_on, len, pos, color_off);
a6f0104a
ZJS
163 else {
164 _cleanup_free_ char *e;
165
166 e = ellipsize_mem(pos, len, n_columns - prefix,
167 tail_line ? 100 : 90);
168 if (!e)
169 fprintf(f, "%*s%s%.*s%s\n",
170 continuation * prefix, "",
171 color_on, len, pos, color_off);
172 else
173 fprintf(f, "%*s%s%s%s\n",
174 continuation * prefix, "",
175 color_on, e, color_off);
176 }
177 } else
31f7bf19
ZJS
178 fputs("...\n", f);
179
a6f0104a
ZJS
180 if (tail_line)
181 break;
31f7bf19 182 }
94e0bd7d
ZJS
183
184 return ellipsized;
31f7bf19
ZJS
185}
186
08ace05b
LP
187static int output_short(
188 FILE *f,
189 sd_journal *j,
190 OutputMode mode,
191 unsigned n_columns,
192 OutputFlags flags) {
193
86aa7ba4 194 int r;
86aa7ba4
LP
195 const void *data;
196 size_t length;
197 size_t n = 0;
08ace05b 198 _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL;
49826187
LP
199 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;
200 int p = LOG_INFO;
94e0bd7d 201 bool ellipsized = false;
86aa7ba4 202
08ace05b 203 assert(f);
86aa7ba4
LP
204 assert(j);
205
a6f0104a 206 /* Set the threshold to one bigger than the actual print
69ab8088 207 * threshold, so that if the line is actually longer than what
a6f0104a
ZJS
208 * we're willing to print, ellipsization will occur. This way
209 * we won't output a misleading line without any indication of
210 * truncation.
211 */
212 sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1);
93b73b06 213
a72b6353 214 JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
55d7bfc1 215
49826187
LP
216 r = parse_field(data, length, "PRIORITY=", &priority, &priority_len);
217 if (r < 0)
08ace05b 218 return r;
49826187
LP
219 else if (r > 0)
220 continue;
221
55d7bfc1
LP
222 r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len);
223 if (r < 0)
08ace05b 224 return r;
55d7bfc1
LP
225 else if (r > 0)
226 continue;
227
4cd9a9d9 228 r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len);
55d7bfc1 229 if (r < 0)
08ace05b 230 return r;
55d7bfc1
LP
231 else if (r > 0)
232 continue;
233
234 r = parse_field(data, length, "_COMM=", &comm, &comm_len);
235 if (r < 0)
08ace05b 236 return r;
55d7bfc1
LP
237 else if (r > 0)
238 continue;
239
240 r = parse_field(data, length, "_PID=", &pid, &pid_len);
241 if (r < 0)
08ace05b 242 return r;
55d7bfc1
LP
243 else if (r > 0)
244 continue;
245
6c1e6b98
LP
246 r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len);
247 if (r < 0)
08ace05b 248 return r;
6c1e6b98
LP
249 else if (r > 0)
250 continue;
251
bf967366
LP
252 r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len);
253 if (r < 0)
08ace05b 254 return r;
bf967366
LP
255 else if (r > 0)
256 continue;
257
258 r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len);
259 if (r < 0)
08ace05b 260 return r;
bf967366
LP
261 else if (r > 0)
262 continue;
263
55d7bfc1
LP
264 r = parse_field(data, length, "MESSAGE=", &message, &message_len);
265 if (r < 0)
08ace05b 266 return r;
55d7bfc1
LP
267 }
268
a72b6353
ZJS
269 if (r < 0)
270 return r;
271
08ace05b
LP
272 if (!message)
273 return 0;
55d7bfc1 274
e8bc0ea2
LP
275 if (!(flags & OUTPUT_SHOW_ALL))
276 strip_tab_ansi(&message, &message_len);
277
49826187
LP
278 if (priority_len == 1 && *priority >= '0' && *priority <= '7')
279 p = *priority - '0';
280
a6e87e90 281 if (mode == OUTPUT_SHORT_MONOTONIC) {
67a12205 282 uint64_t t;
3ebcdf8c
LP
283 sd_id128_t boot_id;
284
bf967366
LP
285 r = -ENOENT;
286
287 if (monotonic)
288 r = safe_atou64(monotonic, &t);
289
290 if (r < 0)
3ebcdf8c 291 r = sd_journal_get_monotonic_usec(j, &t, &boot_id);
86aa7ba4 292
3ebcdf8c 293 if (r < 0) {
a72b6353 294 log_error("Failed to get monotonic timestamp: %s", strerror(-r));
08ace05b 295 return r;
67a12205
LP
296 }
297
08ace05b
LP
298 fprintf(f, "[%5llu.%06llu]",
299 (unsigned long long) (t / USEC_PER_SEC),
300 (unsigned long long) (t % USEC_PER_SEC));
3ebcdf8c
LP
301
302 n += 1 + 5 + 1 + 6 + 1;
303
67a12205
LP
304 } else {
305 char buf[64];
bf967366 306 uint64_t x;
67a12205
LP
307 time_t t;
308 struct tm tm;
731a676c 309
bf967366
LP
310 r = -ENOENT;
311
312 if (realtime)
313 r = safe_atou64(realtime, &x);
314
315 if (r < 0)
316 r = sd_journal_get_realtime_usec(j, &x);
67a12205 317
67a12205 318 if (r < 0) {
a72b6353 319 log_error("Failed to get realtime timestamp: %s", strerror(-r));
08ace05b 320 return r;
67a12205
LP
321 }
322
bf967366 323 t = (time_t) (x / USEC_PER_SEC);
f02d8367
ZJS
324
325 switch(mode) {
326 case OUTPUT_SHORT_ISO:
44bc6e1f 327 r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", localtime_r(&t, &tm));
f02d8367
ZJS
328 break;
329 case OUTPUT_SHORT_PRECISE:
330 r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm));
331 if (r > 0) {
332 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
333 ".%06llu", x % USEC_PER_SEC);
334 }
335 break;
336 default:
44bc6e1f 337 r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm));
f02d8367 338 }
44bc6e1f
TT
339
340 if (r <= 0) {
67a12205 341 log_error("Failed to format time.");
44bc6e1f 342 return -EINVAL;
67a12205
LP
343 }
344
08ace05b 345 fputs(buf, f);
67a12205
LP
346 n += strlen(buf);
347 }
86aa7ba4 348
08ace05b
LP
349 if (hostname && shall_print(hostname, hostname_len, flags)) {
350 fprintf(f, " %.*s", (int) hostname_len, hostname);
55d7bfc1
LP
351 n += hostname_len + 1;
352 }
353
08ace05b
LP
354 if (identifier && shall_print(identifier, identifier_len, flags)) {
355 fprintf(f, " %.*s", (int) identifier_len, identifier);
4cd9a9d9 356 n += identifier_len + 1;
08ace05b
LP
357 } else if (comm && shall_print(comm, comm_len, flags)) {
358 fprintf(f, " %.*s", (int) comm_len, comm);
55d7bfc1 359 n += comm_len + 1;
b5936820 360 } else
08ace05b 361 fputc(' ', f);
86aa7ba4 362
08ace05b
LP
363 if (pid && shall_print(pid, pid_len, flags)) {
364 fprintf(f, "[%.*s]", (int) pid_len, pid);
55d7bfc1 365 n += pid_len + 2;
08ace05b
LP
366 } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) {
367 fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid);
6c1e6b98 368 n += fake_pid_len + 2;
86aa7ba4
LP
369 }
370
31f7bf19 371 if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) {
e6acda19 372 char bytes[FORMAT_BYTES_MAX];
08ace05b 373 fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len));
31f7bf19
ZJS
374 } else {
375 fputs(": ", f);
94e0bd7d
ZJS
376 ellipsized |=
377 print_multiline(f, n + 2, n_columns, flags, p, message, message_len);
31f7bf19 378 }
55d7bfc1 379
d4205751
LP
380 if (flags & OUTPUT_CATALOG)
381 print_catalog(f, j);
382
94e0bd7d 383 return ellipsized;
86aa7ba4
LP
384}
385
08ace05b
LP
386static int output_verbose(
387 FILE *f,
388 sd_journal *j,
389 OutputMode mode,
390 unsigned n_columns,
391 OutputFlags flags) {
392
86aa7ba4
LP
393 const void *data;
394 size_t length;
7fd1b19b 395 _cleanup_free_ char *cursor = NULL;
86aa7ba4 396 uint64_t realtime;
f02d8367 397 char ts[FORMAT_TIMESTAMP_MAX + 7];
86aa7ba4
LP
398 int r;
399
08ace05b 400 assert(f);
86aa7ba4
LP
401 assert(j);
402
93b73b06
LP
403 sd_journal_set_data_threshold(j, 0);
404
cf40f0be
ZJS
405 r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length);
406 if (r == -ENOENT)
407 log_debug("Source realtime timestamp not found");
408 else if (r < 0) {
a72b6353 409 log_full(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR,
cf40f0be 410 "Failed to get source realtime timestamp: %s", strerror(-r));
86aa7ba4 411 return r;
cf40f0be
ZJS
412 } else {
413 _cleanup_free_ char *value = NULL;
414 size_t size;
415
416 r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, &size);
417 if (r < 0)
418 log_debug("_SOURCE_REALTIME_TIMESTAMP invalid: %s", strerror(-r));
419 else {
420 r = safe_atou64(value, &realtime);
421 if (r < 0)
422 log_debug("Failed to parse realtime timestamp: %s",
423 strerror(-r));
424 }
425 }
426
427 if (r < 0) {
428 r = sd_journal_get_realtime_usec(j, &realtime);
429 if (r < 0) {
430 log_full(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR,
431 "Failed to get realtime timestamp: %s", strerror(-r));
432 return r;
433 }
86aa7ba4
LP
434 }
435
436 r = sd_journal_get_cursor(j, &cursor);
437 if (r < 0) {
438 log_error("Failed to get cursor: %s", strerror(-r));
439 return r;
440 }
441
08ace05b 442 fprintf(f, "%s [%s]\n",
f02d8367 443 format_timestamp_us(ts, sizeof(ts), realtime),
08ace05b 444 cursor);
86aa7ba4 445
a72b6353 446 JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
31f7bf19
ZJS
447 const char *c;
448 int fieldlen;
7ac4fa7e
ZJS
449 const char *on = "", *off = "";
450
31f7bf19
ZJS
451 c = memchr(data, '=', length);
452 if (!c) {
453 log_error("Invalid field.");
454 return -EINVAL;
455 }
456 fieldlen = c - (const char*) data;
86aa7ba4 457
7ac4fa7e
ZJS
458 if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) {
459 on = ANSI_HIGHLIGHT_ON;
460 off = ANSI_HIGHLIGHT_OFF;
461 }
462
463 if (flags & OUTPUT_SHOW_ALL ||
a6f0104a
ZJS
464 (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH)
465 && utf8_is_printable(data, length))) {
7ac4fa7e 466 fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data);
31f7bf19 467 print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1);
7ac4fa7e 468 fputs(off, f);
31f7bf19
ZJS
469 } else {
470 char bytes[FORMAT_BYTES_MAX];
86aa7ba4 471
7ac4fa7e
ZJS
472 fprintf(f, " %s%.*s=[%s blob data]%s\n",
473 on,
31f7bf19
ZJS
474 (int) (c - (const char*) data),
475 (const char*) data,
7ac4fa7e
ZJS
476 format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1),
477 off);
31f7bf19 478 }
86aa7ba4
LP
479 }
480
a72b6353
ZJS
481 if (r < 0)
482 return r;
483
d4205751
LP
484 if (flags & OUTPUT_CATALOG)
485 print_catalog(f, j);
486
86aa7ba4
LP
487 return 0;
488}
489
08ace05b
LP
490static int output_export(
491 FILE *f,
492 sd_journal *j,
493 OutputMode mode,
494 unsigned n_columns,
495 OutputFlags flags) {
496
86aa7ba4
LP
497 sd_id128_t boot_id;
498 char sid[33];
499 int r;
500 usec_t realtime, monotonic;
7fd1b19b 501 _cleanup_free_ char *cursor = NULL;
86aa7ba4
LP
502 const void *data;
503 size_t length;
504
505 assert(j);
506
93b73b06
LP
507 sd_journal_set_data_threshold(j, 0);
508
86aa7ba4
LP
509 r = sd_journal_get_realtime_usec(j, &realtime);
510 if (r < 0) {
511 log_error("Failed to get realtime timestamp: %s", strerror(-r));
512 return r;
513 }
514
515 r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id);
516 if (r < 0) {
517 log_error("Failed to get monotonic timestamp: %s", strerror(-r));
518 return r;
519 }
520
521 r = sd_journal_get_cursor(j, &cursor);
522 if (r < 0) {
523 log_error("Failed to get cursor: %s", strerror(-r));
524 return r;
525 }
526
08ace05b
LP
527 fprintf(f,
528 "__CURSOR=%s\n"
529 "__REALTIME_TIMESTAMP=%llu\n"
530 "__MONOTONIC_TIMESTAMP=%llu\n"
531 "_BOOT_ID=%s\n",
532 cursor,
533 (unsigned long long) realtime,
534 (unsigned long long) monotonic,
535 sd_id128_to_string(boot_id, sid));
86aa7ba4 536
a72b6353 537 JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
86aa7ba4 538
112301ae
LP
539 /* We already printed the boot id, from the data in
540 * the header, hence let's suppress it here */
541 if (length >= 9 &&
2a0e0692 542 startswith(data, "_BOOT_ID="))
112301ae
LP
543 continue;
544
31f7bf19 545 if (!utf8_is_printable(data, length)) {
86aa7ba4
LP
546 const char *c;
547 uint64_t le64;
548
549 c = memchr(data, '=', length);
550 if (!c) {
551 log_error("Invalid field.");
552 return -EINVAL;
553 }
554
08ace05b
LP
555 fwrite(data, c - (const char*) data, 1, f);
556 fputc('\n', f);
86aa7ba4 557 le64 = htole64(length - (c - (const char*) data) - 1);
08ace05b
LP
558 fwrite(&le64, sizeof(le64), 1, f);
559 fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f);
86aa7ba4 560 } else
08ace05b 561 fwrite(data, length, 1, f);
86aa7ba4 562
08ace05b 563 fputc('\n', f);
86aa7ba4
LP
564 }
565
a72b6353
ZJS
566 if (r < 0)
567 return r;
568
08ace05b 569 fputc('\n', f);
86aa7ba4
LP
570
571 return 0;
572}
573
240a5fe8 574void json_escape(
08ace05b
LP
575 FILE *f,
576 const char* p,
577 size_t l,
578 OutputFlags flags) {
579
580 assert(f);
581 assert(p);
582
93b73b06 583 if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD)
08ace05b
LP
584
585 fputs("null", f);
586
31f7bf19 587 else if (!utf8_is_printable(p, l)) {
86aa7ba4
LP
588 bool not_first = false;
589
08ace05b 590 fputs("[ ", f);
86aa7ba4
LP
591
592 while (l > 0) {
593 if (not_first)
08ace05b 594 fprintf(f, ", %u", (uint8_t) *p);
86aa7ba4
LP
595 else {
596 not_first = true;
08ace05b 597 fprintf(f, "%u", (uint8_t) *p);
86aa7ba4
LP
598 }
599
600 p++;
601 l--;
602 }
603
08ace05b 604 fputs(" ]", f);
86aa7ba4 605 } else {
08ace05b 606 fputc('\"', f);
86aa7ba4
LP
607
608 while (l > 0) {
609 if (*p == '"' || *p == '\\') {
08ace05b
LP
610 fputc('\\', f);
611 fputc(*p, f);
31f7bf19
ZJS
612 } else if (*p == '\n')
613 fputs("\\n", f);
614 else if (*p < ' ')
08ace05b
LP
615 fprintf(f, "\\u%04x", *p);
616 else
617 fputc(*p, f);
86aa7ba4
LP
618
619 p++;
620 l--;
621 }
622
08ace05b 623 fputc('\"', f);
86aa7ba4
LP
624 }
625}
626
08ace05b
LP
627static int output_json(
628 FILE *f,
629 sd_journal *j,
630 OutputMode mode,
631 unsigned n_columns,
632 OutputFlags flags) {
633
86aa7ba4 634 uint64_t realtime, monotonic;
7fd1b19b 635 _cleanup_free_ char *cursor = NULL;
86aa7ba4
LP
636 const void *data;
637 size_t length;
638 sd_id128_t boot_id;
2e729834 639 char sid[33], *k;
86aa7ba4 640 int r;
d99ae53a
LP
641 Hashmap *h = NULL;
642 bool done, separator;
86aa7ba4
LP
643
644 assert(j);
645
93b73b06
LP
646 sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD);
647
86aa7ba4
LP
648 r = sd_journal_get_realtime_usec(j, &realtime);
649 if (r < 0) {
650 log_error("Failed to get realtime timestamp: %s", strerror(-r));
651 return r;
652 }
653
654 r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id);
655 if (r < 0) {
656 log_error("Failed to get monotonic timestamp: %s", strerror(-r));
657 return r;
658 }
659
660 r = sd_journal_get_cursor(j, &cursor);
661 if (r < 0) {
662 log_error("Failed to get cursor: %s", strerror(-r));
663 return r;
664 }
665
a6e87e90 666 if (mode == OUTPUT_JSON_PRETTY)
08ace05b
LP
667 fprintf(f,
668 "{\n"
669 "\t\"__CURSOR\" : \"%s\",\n"
670 "\t\"__REALTIME_TIMESTAMP\" : \"%llu\",\n"
671 "\t\"__MONOTONIC_TIMESTAMP\" : \"%llu\",\n"
672 "\t\"_BOOT_ID\" : \"%s\"",
673 cursor,
674 (unsigned long long) realtime,
675 (unsigned long long) monotonic,
676 sd_id128_to_string(boot_id, sid));
48383c25
LP
677 else {
678 if (mode == OUTPUT_JSON_SSE)
679 fputs("data: ", f);
680
08ace05b
LP
681 fprintf(f,
682 "{ \"__CURSOR\" : \"%s\", "
683 "\"__REALTIME_TIMESTAMP\" : \"%llu\", "
684 "\"__MONOTONIC_TIMESTAMP\" : \"%llu\", "
685 "\"_BOOT_ID\" : \"%s\"",
686 cursor,
687 (unsigned long long) realtime,
688 (unsigned long long) monotonic,
689 sd_id128_to_string(boot_id, sid));
48383c25 690 }
86aa7ba4 691
d99ae53a
LP
692 h = hashmap_new(string_hash_func, string_compare_func);
693 if (!h)
694 return -ENOMEM;
695
696 /* First round, iterate through the entry and count how often each field appears */
a72b6353 697 JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
d99ae53a
LP
698 const char *eq;
699 char *n;
700 unsigned u;
86aa7ba4 701
112301ae
LP
702 if (length >= 9 &&
703 memcmp(data, "_BOOT_ID=", 9) == 0)
704 continue;
705
d99ae53a
LP
706 eq = memchr(data, '=', length);
707 if (!eq)
708 continue;
86aa7ba4 709
d99ae53a
LP
710 n = strndup(data, eq - (const char*) data);
711 if (!n) {
712 r = -ENOMEM;
713 goto finish;
714 }
a6e87e90 715
d99ae53a
LP
716 u = PTR_TO_UINT(hashmap_get(h, n));
717 if (u == 0) {
718 r = hashmap_put(h, n, UINT_TO_PTR(1));
719 if (r < 0) {
720 free(n);
721 goto finish;
722 }
723 } else {
724 r = hashmap_update(h, n, UINT_TO_PTR(u + 1));
725 free(n);
726 if (r < 0)
727 goto finish;
728 }
86aa7ba4
LP
729 }
730
a72b6353
ZJS
731 if (r < 0)
732 return r;
733
d99ae53a
LP
734 separator = true;
735 do {
736 done = true;
737
738 SD_JOURNAL_FOREACH_DATA(j, data, length) {
739 const char *eq;
740 char *kk, *n;
741 size_t m;
742 unsigned u;
743
744 /* We already printed the boot id, from the data in
745 * the header, hence let's suppress it here */
746 if (length >= 9 &&
747 memcmp(data, "_BOOT_ID=", 9) == 0)
748 continue;
749
750 eq = memchr(data, '=', length);
751 if (!eq)
752 continue;
753
754 if (separator) {
755 if (mode == OUTPUT_JSON_PRETTY)
756 fputs(",\n\t", f);
757 else
758 fputs(", ", f);
759 }
760
761 m = eq - (const char*) data;
762
763 n = strndup(data, m);
764 if (!n) {
765 r = -ENOMEM;
766 goto finish;
767 }
768
769 u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk));
770 if (u == 0) {
771 /* We already printed this, let's jump to the next */
772 free(n);
773 separator = false;
774
775 continue;
776 } else if (u == 1) {
777 /* Field only appears once, output it directly */
778
779 json_escape(f, data, m, flags);
780 fputs(" : ", f);
781
782 json_escape(f, eq + 1, length - m - 1, flags);
783
784 hashmap_remove(h, n);
785 free(kk);
786 free(n);
787
788 separator = true;
789
790 continue;
791
792 } else {
793 /* Field appears multiple times, output it as array */
794 json_escape(f, data, m, flags);
795 fputs(" : [ ", f);
796 json_escape(f, eq + 1, length - m - 1, flags);
797
798 /* Iterate through the end of the list */
799
800 while (sd_journal_enumerate_data(j, &data, &length) > 0) {
801 if (length < m + 1)
802 continue;
803
804 if (memcmp(data, n, m) != 0)
805 continue;
806
807 if (((const char*) data)[m] != '=')
808 continue;
809
810 fputs(", ", f);
811 json_escape(f, (const char*) data + m + 1, length - m - 1, flags);
812 }
813
814 fputs(" ]", f);
815
816 hashmap_remove(h, n);
817 free(kk);
818 free(n);
819
820 /* Iterate data fields form the beginning */
821 done = false;
822 separator = true;
823
824 break;
825 }
826 }
827
828 } while (!done);
829
a6e87e90 830 if (mode == OUTPUT_JSON_PRETTY)
08ace05b 831 fputs("\n}\n", f);
48383c25
LP
832 else if (mode == OUTPUT_JSON_SSE)
833 fputs("}\n\n", f);
a6e87e90 834 else
08ace05b 835 fputs(" }\n", f);
86aa7ba4 836
d99ae53a
LP
837 r = 0;
838
839finish:
840 while ((k = hashmap_steal_first_key(h)))
841 free(k);
842
843 hashmap_free(h);
844
845 return r;
86aa7ba4
LP
846}
847
08ace05b
LP
848static int output_cat(
849 FILE *f,
850 sd_journal *j,
851 OutputMode mode,
852 unsigned n_columns,
853 OutputFlags flags) {
854
d3f2bdbf
LP
855 const void *data;
856 size_t l;
857 int r;
858
859 assert(j);
08ace05b 860 assert(f);
d3f2bdbf 861
93b73b06
LP
862 sd_journal_set_data_threshold(j, 0);
863
d3f2bdbf
LP
864 r = sd_journal_get_data(j, "MESSAGE", &data, &l);
865 if (r < 0) {
c198300f
LP
866 /* An entry without MESSAGE=? */
867 if (r == -ENOENT)
868 return 0;
869
d3f2bdbf
LP
870 log_error("Failed to get data: %s", strerror(-r));
871 return r;
872 }
873
874 assert(l >= 8);
875
08ace05b
LP
876 fwrite((const char*) data + 8, 1, l - 8, f);
877 fputc('\n', f);
d3f2bdbf
LP
878
879 return 0;
880}
881
08ace05b
LP
882static int (*output_funcs[_OUTPUT_MODE_MAX])(
883 FILE *f,
884 sd_journal*j,
885 OutputMode mode,
886 unsigned n_columns,
887 OutputFlags flags) = {
888
a6e87e90 889 [OUTPUT_SHORT] = output_short,
44bc6e1f 890 [OUTPUT_SHORT_ISO] = output_short,
f02d8367
ZJS
891 [OUTPUT_SHORT_PRECISE] = output_short,
892 [OUTPUT_SHORT_MONOTONIC] = output_short,
86aa7ba4
LP
893 [OUTPUT_VERBOSE] = output_verbose,
894 [OUTPUT_EXPORT] = output_export,
d3f2bdbf 895 [OUTPUT_JSON] = output_json,
a6e87e90 896 [OUTPUT_JSON_PRETTY] = output_json,
48383c25 897 [OUTPUT_JSON_SSE] = output_json,
d3f2bdbf 898 [OUTPUT_CAT] = output_cat
86aa7ba4
LP
899};
900
08ace05b
LP
901int output_journal(
902 FILE *f,
903 sd_journal *j,
904 OutputMode mode,
905 unsigned n_columns,
94e0bd7d
ZJS
906 OutputFlags flags,
907 bool *ellipsized) {
08ace05b 908
e268b81e 909 int ret;
df50185b 910 assert(mode >= 0);
86aa7ba4
LP
911 assert(mode < _OUTPUT_MODE_MAX);
912
34a35ece
LP
913 if (n_columns <= 0)
914 n_columns = columns();
915
08ace05b 916 ret = output_funcs[mode](f, j, mode, n_columns, flags);
e268b81e 917 fflush(stdout);
94e0bd7d
ZJS
918
919 if (ellipsized && ret > 0)
920 *ellipsized = true;
921
e268b81e 922 return ret;
86aa7ba4
LP
923}
924
1a6c43e9
MT
925static int show_journal(FILE *f,
926 sd_journal *j,
927 OutputMode mode,
928 unsigned n_columns,
929 usec_t not_before,
930 unsigned how_many,
94e0bd7d
ZJS
931 OutputFlags flags,
932 bool *ellipsized) {
86aa7ba4 933
86aa7ba4 934 int r;
df50185b
LP
935 unsigned line = 0;
936 bool need_seek = false;
085d7120 937 int warn_cutoff = flags & OUTPUT_WARN_CUTOFF;
86aa7ba4 938
1a6c43e9 939 assert(j);
df50185b
LP
940 assert(mode >= 0);
941 assert(mode < _OUTPUT_MODE_MAX);
1946b0bd
LP
942
943 /* Seek to end */
86aa7ba4
LP
944 r = sd_journal_seek_tail(j);
945 if (r < 0)
946 goto finish;
947
df50185b
LP
948 r = sd_journal_previous_skip(j, how_many);
949 if (r < 0)
950 goto finish;
86aa7ba4 951
df50185b
LP
952 for (;;) {
953 for (;;) {
954 usec_t usec;
955
956 if (need_seek) {
957 r = sd_journal_next(j);
958 if (r < 0)
959 goto finish;
960 }
961
962 if (r == 0)
963 break;
86aa7ba4 964
df50185b
LP
965 need_seek = true;
966
967 if (not_before > 0) {
968 r = sd_journal_get_monotonic_usec(j, &usec, NULL);
969
970 /* -ESTALE is returned if the
971 timestamp is not from this boot */
972 if (r == -ESTALE)
973 continue;
974 else if (r < 0)
975 goto finish;
976
977 if (usec < not_before)
978 continue;
979 }
980
981 line ++;
982
94e0bd7d 983 r = output_journal(f, j, mode, n_columns, flags, ellipsized);
df50185b
LP
984 if (r < 0)
985 goto finish;
986 }
987
08984293
LP
988 if (warn_cutoff && line < how_many && not_before > 0) {
989 sd_id128_t boot_id;
990 usec_t cutoff;
991
992 /* Check whether the cutoff line is too early */
993
994 r = sd_id128_get_boot(&boot_id);
995 if (r < 0)
996 goto finish;
997
998 r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL);
999 if (r < 0)
1000 goto finish;
1001
b59866ae 1002 if (r > 0 && not_before < cutoff)
08ace05b 1003 fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n");
08984293
LP
1004
1005 warn_cutoff = false;
1006 }
1007
085d7120 1008 if (!(flags & OUTPUT_FOLLOW))
86aa7ba4
LP
1009 break;
1010
e02d1cf7 1011 r = sd_journal_wait(j, (usec_t) -1);
df50185b
LP
1012 if (r < 0)
1013 goto finish;
1014
86aa7ba4
LP
1015 }
1016
1a6c43e9
MT
1017finish:
1018 return r;
1019}
1020
886a64fe 1021int add_matches_for_unit(sd_journal *j, const char *unit) {
1a6c43e9 1022 int r;
2d0b2e87 1023 char *m1, *m2, *m3, *m4;
1a6c43e9 1024
886a64fe 1025 assert(j);
1a6c43e9
MT
1026 assert(unit);
1027
2d0b2e87
ZJS
1028 m1 = strappenda("_SYSTEMD_UNIT=", unit);
1029 m2 = strappenda("COREDUMP_UNIT=", unit);
1030 m3 = strappenda("UNIT=", unit);
1031 m4 = strappenda("OBJECT_SYSTEMD_UNIT=", unit);
1a6c43e9 1032
886a64fe
ZJS
1033 (void)(
1034 /* Look for messages from the service itself */
1035 (r = sd_journal_add_match(j, m1, 0)) ||
1036
1037 /* Look for coredumps of the service */
1038 (r = sd_journal_add_disjunction(j)) ||
fdcd37df
ZJS
1039 (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) ||
1040 (r = sd_journal_add_match(j, "_UID=0", 0)) ||
886a64fe
ZJS
1041 (r = sd_journal_add_match(j, m2, 0)) ||
1042
1043 /* Look for messages from PID 1 about this service */
1044 (r = sd_journal_add_disjunction(j)) ||
1045 (r = sd_journal_add_match(j, "_PID=1", 0)) ||
2d0b2e87
ZJS
1046 (r = sd_journal_add_match(j, m3, 0)) ||
1047
1048 /* Look for messages from authorized daemons about this service */
1049 (r = sd_journal_add_disjunction(j)) ||
1050 (r = sd_journal_add_match(j, "_UID=0", 0)) ||
1051 (r = sd_journal_add_match(j, m4, 0))
886a64fe 1052 );
2d0b2e87 1053
69ae3ee0
ZJS
1054 if (r == 0 && endswith(unit, ".slice")) {
1055 char *m5 = strappend("_SYSTEMD_SLICE=", unit);
1056
1057 /* Show all messages belonging to a slice */
1058 (void)(
1059 (r = sd_journal_add_disjunction(j)) ||
1060 (r = sd_journal_add_match(j, m5, 0))
1061 );
1062 }
1063
886a64fe
ZJS
1064 return r;
1065}
1a6c43e9 1066
886a64fe
ZJS
1067int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) {
1068 int r;
2d0b2e87
ZJS
1069 char *m1, *m2, *m3, *m4;
1070 char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)];
1a6c43e9 1071
886a64fe
ZJS
1072 assert(j);
1073 assert(unit);
1a6c43e9 1074
2d0b2e87
ZJS
1075 m1 = strappenda("_SYSTEMD_USER_UNIT=", unit);
1076 m2 = strappenda("USER_UNIT=", unit);
1077 m3 = strappenda("COREDUMP_USER_UNIT=", unit);
1078 m4 = strappenda("OBJECT_SYSTEMD_USER_UNIT=", unit);
1079 sprintf(muid, "_UID=%lu", (unsigned long) uid);
1a6c43e9 1080
886a64fe
ZJS
1081 (void) (
1082 /* Look for messages from the user service itself */
1083 (r = sd_journal_add_match(j, m1, 0)) ||
2d0b2e87 1084 (r = sd_journal_add_match(j, muid, 0)) ||
886a64fe
ZJS
1085
1086 /* Look for messages from systemd about this service */
1087 (r = sd_journal_add_disjunction(j)) ||
1088 (r = sd_journal_add_match(j, m2, 0)) ||
2d0b2e87 1089 (r = sd_journal_add_match(j, muid, 0)) ||
886a64fe
ZJS
1090
1091 /* Look for coredumps of the service */
1092 (r = sd_journal_add_disjunction(j)) ||
1093 (r = sd_journal_add_match(j, m3, 0)) ||
2d0b2e87
ZJS
1094 (r = sd_journal_add_match(j, muid, 0)) ||
1095 (r = sd_journal_add_match(j, "_UID=0", 0)) ||
1096
1097 /* Look for messages from authorized daemons about this service */
1098 (r = sd_journal_add_disjunction(j)) ||
fdcd37df 1099 (r = sd_journal_add_match(j, m4, 0)) ||
2d0b2e87 1100 (r = sd_journal_add_match(j, muid, 0)) ||
fdcd37df 1101 (r = sd_journal_add_match(j, "_UID=0", 0))
886a64fe 1102 );
69ae3ee0
ZJS
1103
1104 if (r == 0 && endswith(unit, ".slice")) {
1105 char *m5 = strappend("_SYSTEMD_SLICE=", unit);
1106
1107 /* Show all messages belonging to a slice */
1108 (void)(
1109 (r = sd_journal_add_disjunction(j)) ||
1110 (r = sd_journal_add_match(j, m5, 0)) ||
1111 (r = sd_journal_add_match(j, muid, 0))
1112 );
1113 }
1114
1a6c43e9
MT
1115 return r;
1116}
1117
b6741478 1118static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) {
e04b0cdb 1119 _cleanup_close_pipe_ int pair[2] = { -1, -1 };
a4475f57 1120 _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1;
b6741478
LP
1121 pid_t pid, child;
1122 siginfo_t si;
1123 char buf[37];
1124 ssize_t k;
1125 int r;
1126
1127 assert(machine);
1128 assert(boot_id);
1129
1130 if (!filename_is_safe(machine))
1131 return -EINVAL;
1132
e04b0cdb 1133 r = container_get_leader(machine, &pid);
b6741478
LP
1134 if (r < 0)
1135 return r;
e04b0cdb 1136
a4475f57 1137 r = namespace_open(pid, &pidnsfd, &mntnsfd, &rootfd);
b6741478
LP
1138 if (r < 0)
1139 return r;
1140
e04b0cdb 1141 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
b6741478
LP
1142 return -errno;
1143
1144 child = fork();
1145 if (child < 0)
1146 return -errno;
1147
1148 if (child == 0) {
1149 int fd;
1150
e04b0cdb
LP
1151 close_nointr_nofail(pair[0]);
1152 pair[0] = -1;
b6741478 1153
a4475f57 1154 r = namespace_enter(pidnsfd, mntnsfd, rootfd);
b6741478
LP
1155 if (r < 0)
1156 _exit(EXIT_FAILURE);
1157
1158 fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
1159 if (fd < 0)
1160 _exit(EXIT_FAILURE);
1161
1162 k = loop_read(fd, buf, 36, false);
1163 close_nointr_nofail(fd);
1164 if (k != 36)
1165 _exit(EXIT_FAILURE);
1166
e04b0cdb 1167 k = send(pair[1], buf, 36, MSG_NOSIGNAL);
b6741478
LP
1168 if (k != 36)
1169 _exit(EXIT_FAILURE);
1170
1171 _exit(EXIT_SUCCESS);
1172 }
1173
e04b0cdb
LP
1174 close_nointr_nofail(pair[1]);
1175 pair[1] = -1;
b6741478 1176
b6741478
LP
1177 r = wait_for_terminate(child, &si);
1178 if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
1179 return r < 0 ? r : -EIO;
1180
fbadf045
LP
1181 k = recv(pair[0], buf, 36, 0);
1182 if (k != 36)
1183 return -EIO;
1184
b6741478
LP
1185 buf[36] = 0;
1186 r = sd_id128_from_string(buf, boot_id);
1187 if (r < 0)
1188 return r;
1189
1190 return 0;
1191}
1192
1193int add_match_this_boot(sd_journal *j, const char *machine) {
5ec76417
ZJS
1194 char match[9+32+1] = "_BOOT_ID=";
1195 sd_id128_t boot_id;
1196 int r;
1197
1198 assert(j);
1199
b6741478
LP
1200 if (machine) {
1201 r = get_boot_id_for_machine(machine, &boot_id);
1202 if (r < 0) {
1203 log_error("Failed to get boot id of container %s: %s", machine, strerror(-r));
1204 return r;
1205 }
1206 } else {
1207 r = sd_id128_get_boot(&boot_id);
1208 if (r < 0) {
1209 log_error("Failed to get boot id: %s", strerror(-r));
1210 return r;
1211 }
5ec76417
ZJS
1212 }
1213
1214 sd_id128_to_string(boot_id, match + 9);
1215 r = sd_journal_add_match(j, match, strlen(match));
1216 if (r < 0) {
1217 log_error("Failed to add match: %s", strerror(-r));
1218 return r;
1219 }
1220
1221 r = sd_journal_add_conjunction(j);
1222 if (r < 0)
1223 return r;
1224
1225 return 0;
1226}
1227
886a64fe 1228int show_journal_by_unit(
1a6c43e9
MT
1229 FILE *f,
1230 const char *unit,
1231 OutputMode mode,
1232 unsigned n_columns,
1233 usec_t not_before,
1234 unsigned how_many,
1235 uid_t uid,
886a64fe 1236 OutputFlags flags,
94e0bd7d
ZJS
1237 bool system,
1238 bool *ellipsized) {
1a6c43e9 1239
7fd1b19b 1240 _cleanup_journal_close_ sd_journal*j = NULL;
1a6c43e9 1241 int r;
a688baa8 1242 int jflags = SD_JOURNAL_LOCAL_ONLY | system * SD_JOURNAL_SYSTEM;
1a6c43e9
MT
1243
1244 assert(mode >= 0);
1245 assert(mode < _OUTPUT_MODE_MAX);
1246 assert(unit);
1247
1a6c43e9
MT
1248 if (how_many <= 0)
1249 return 0;
1250
886a64fe 1251 r = sd_journal_open(&j, jflags);
f9045468 1252 if (r < 0)
763c7aa2 1253 return r;
f9045468 1254
b6741478 1255 r = add_match_this_boot(j, NULL);
5ec76417
ZJS
1256 if (r < 0)
1257 return r;
1258
886a64fe
ZJS
1259 if (system)
1260 r = add_matches_for_unit(j, unit);
1261 else
1262 r = add_matches_for_user_unit(j, unit, uid);
1a6c43e9 1263 if (r < 0)
763c7aa2 1264 return r;
1a6c43e9 1265
4ad16808
ZJS
1266 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
1267 _cleanup_free_ char *filter;
1268
1269 filter = journal_make_match_string(j);
1270 log_debug("Journal filter: %s", filter);
1271 }
5ec76417 1272
94e0bd7d 1273 return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized);
86aa7ba4 1274}
df50185b
LP
1275
1276static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
1277 [OUTPUT_SHORT] = "short",
44bc6e1f 1278 [OUTPUT_SHORT_ISO] = "short-iso",
f02d8367
ZJS
1279 [OUTPUT_SHORT_PRECISE] = "short-precise",
1280 [OUTPUT_SHORT_MONOTONIC] = "short-monotonic",
df50185b
LP
1281 [OUTPUT_VERBOSE] = "verbose",
1282 [OUTPUT_EXPORT] = "export",
d3f2bdbf 1283 [OUTPUT_JSON] = "json",
a6e87e90 1284 [OUTPUT_JSON_PRETTY] = "json-pretty",
48383c25 1285 [OUTPUT_JSON_SSE] = "json-sse",
d3f2bdbf 1286 [OUTPUT_CAT] = "cat"
df50185b
LP
1287};
1288
1289DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode);