diff options
Diffstat (limited to '')
-rw-r--r-- | src/shared/logs-show.c | 1777 |
1 files changed, 1777 insertions, 0 deletions
diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c new file mode 100644 index 0000000..2b180a8 --- /dev/null +++ b/src/shared/logs-show.c @@ -0,0 +1,1777 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <syslog.h> +#include <unistd.h> + +#include "sd-id128.h" +#include "sd-journal.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "glyph-util.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "id128-util.h" +#include "io-util.h" +#include "journal-internal.h" +#include "journal-util.h" +#include "json.h" +#include "locale-util.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "namespace-util.h" +#include "output-mode.h" +#include "parse-util.h" +#include "pretty-print.h" +#include "process-util.h" +#include "sparse-endian.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" +#include "web-util.h" + +/* up to three lines (each up to 100 characters) or 300 characters, whichever is less */ +#define PRINT_LINE_THRESHOLD 3 +#define PRINT_CHAR_THRESHOLD 300 + +#define JSON_THRESHOLD 4096U + +static int print_catalog(FILE *f, sd_journal *j) { + _cleanup_free_ char *t = NULL, *z = NULL; + const char *newline, *prefix; + int r; + + assert(j); + + r = sd_journal_get_catalog(j, &t); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to find catalog entry: %m"); + + if (is_locale_utf8()) + prefix = strjoina(special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), special_glyph(SPECIAL_GLYPH_LIGHT_SHADE)); + else + prefix = "--"; + + newline = strjoina(ansi_normal(), "\n", ansi_grey(), prefix, ansi_normal(), " ", ansi_green()); + + z = strreplace(strstrip(t), "\n", newline); + if (!z) + return log_oom(); + + fprintf(f, "%s%s %s%s", ansi_grey(), prefix, ansi_normal(), ansi_green()); + + fputs(z, f); + + fprintf(f, "%s\n", ansi_normal()); + + return 1; +} + +static int url_from_catalog(sd_journal *j, char **ret) { + _cleanup_free_ char *t = NULL, *url = NULL; + const char *weblink; + int r; + + assert(j); + assert(ret); + + r = sd_journal_get_catalog(j, &t); + if (r == -ENOENT) + goto notfound; + if (r < 0) + return log_error_errno(r, "Failed to find catalog entry: %m"); + + weblink = startswith(t, "Documentation:"); + if (!weblink) { + weblink = strstr(t + 1, "\nDocumentation:"); + if (!weblink) + goto notfound; + + weblink += 15; + } + + /* Skip whitespace to value */ + weblink += strspn(weblink, " \t"); + + /* Cut out till next whitespace/newline */ + url = strndup(weblink, strcspn(weblink, WHITESPACE)); + if (!url) + return log_oom(); + + if (!documentation_url_is_valid(url)) + goto notfound; + + *ret = TAKE_PTR(url); + return 1; + +notfound: + *ret = NULL; + return 0; +} + +static int parse_field(const void *data, size_t length, const char *field, size_t field_len, char **target, size_t *target_len) { + size_t nl; + char *buf; + + assert(data); + assert(field); + assert(target); + + if (length < field_len) + return 0; + + if (memcmp(data, field, field_len)) + return 0; + + nl = length - field_len; + + buf = newdup_suffix0(char, (const char*) data + field_len, nl); + if (!buf) + return log_oom(); + + free(*target); + *target = buf; + + if (target_len) + *target_len = nl; + + return 1; +} + +typedef struct ParseFieldVec { + const char *field; + size_t field_len; + char **target; + size_t *target_len; +} ParseFieldVec; + +#define PARSE_FIELD_VEC_ENTRY(_field, _target, _target_len) { \ + .field = _field, \ + .field_len = strlen(_field), \ + .target = _target, \ + .target_len = _target_len \ + } + +static int parse_fieldv(const void *data, size_t length, const ParseFieldVec *fields, unsigned n_fields) { + unsigned i; + + for (i = 0; i < n_fields; i++) { + const ParseFieldVec *f = &fields[i]; + int r; + + r = parse_field(data, length, f->field, f->field_len, f->target, f->target_len); + if (r < 0) + return r; + else if (r > 0) + break; + } + + return 0; +} + +static int field_set_test(const Set *fields, const char *name, size_t n) { + char *s; + + if (!fields) + return 1; + + s = strndupa_safe(name, n); + return set_contains(fields, s); +} + +static bool shall_print(const char *p, size_t l, OutputFlags flags) { + assert(p); + + if (flags & OUTPUT_SHOW_ALL) + return true; + + if (l >= PRINT_CHAR_THRESHOLD) + return false; + + if (!utf8_is_printable(p, l)) + return false; + + return true; +} + +static bool print_multiline( + FILE *f, + unsigned prefix, + unsigned n_columns, + OutputFlags flags, + int priority, + bool audit, + const char* message, + size_t message_len, + size_t highlight[2]) { + + const char *color_on = "", *color_off = "", *highlight_on = ""; + const char *pos, *end; + bool ellipsized = false; + int line = 0; + + if (flags & OUTPUT_COLOR) { + get_log_colors(priority, &color_on, &color_off, &highlight_on); + + if (audit && strempty(color_on)) { + color_on = ANSI_BLUE; + color_off = ANSI_NORMAL; + } + } + + /* A special case: make sure that we print a newline when + the message is empty. */ + if (message_len == 0) + fputs("\n", f); + + for (pos = message; + pos < message + message_len; + pos = end + 1, line++) { + bool tail_line; + int len, indent = (line > 0) * prefix; + for (end = pos; end < message + message_len && *end != '\n'; end++) + ; + len = end - pos; + assert(len >= 0); + + /* We need to figure out when we are showing not-last line, *and* + * will skip subsequent lines. In that case, we will put the dots + * at the end of the line, instead of putting dots in the middle + * or not at all. + */ + tail_line = + line + 1 == PRINT_LINE_THRESHOLD || + end + 1 >= message + PRINT_CHAR_THRESHOLD; + + if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) || + (prefix + len + 1 < n_columns && !tail_line)) { + if (highlight && + (size_t) (pos - message) <= highlight[0] && + highlight[0] < (size_t) len) { + + fprintf(f, "%*s%s%.*s", + indent, "", + color_on, (int) highlight[0], pos); + fprintf(f, "%s%.*s", + highlight_on, + (int) (MIN((size_t) len, highlight[1]) - highlight[0]), + pos + highlight[0]); + if ((size_t) len > highlight[1]) + fprintf(f, "%s%.*s", + color_on, + (int) (len - highlight[1]), + pos + highlight[1]); + fprintf(f, "%s\n", color_off); + + } else + fprintf(f, "%*s%s%.*s%s\n", + indent, "", + color_on, len, pos, color_off); + continue; + } + + /* Beyond this point, ellipsization will happen. */ + ellipsized = true; + + if (prefix < n_columns && n_columns - prefix >= 3) { + if (n_columns - prefix > (unsigned) len + 3) + fprintf(f, "%*s%s%.*s...%s\n", + indent, "", + color_on, len, pos, color_off); + else { + _cleanup_free_ char *e = NULL; + + e = ellipsize_mem(pos, len, n_columns - prefix, + tail_line ? 100 : 90); + if (!e) + fprintf(f, "%*s%s%.*s%s\n", + indent, "", + color_on, len, pos, color_off); + else + fprintf(f, "%*s%s%s%s\n", + indent, "", + color_on, e, color_off); + } + } else + fputs("...\n", f); + + if (tail_line) + break; + } + + return ellipsized; +} + +static int output_timestamp_monotonic( + FILE *f, OutputMode mode, + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { + + int written_chars = 0; + + assert(f); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); + + if (!VALID_MONOTONIC(ts->monotonic)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid monotonic timestamp available"); + + written_chars += fprintf(f, "[%5"PRI_USEC".%06"PRI_USEC, ts->monotonic / USEC_PER_SEC, ts->monotonic % USEC_PER_SEC); + + if (mode == OUTPUT_SHORT_DELTA) { + uint64_t delta; + bool reliable_ts = true; + + if (VALID_MONOTONIC(previous_ts->monotonic) && sd_id128_equal(*boot_id, *previous_boot_id)) + delta = usec_sub_unsigned(ts->monotonic, previous_ts->monotonic); + else if (VALID_REALTIME(ts->realtime) && VALID_REALTIME(previous_ts->realtime)) { + delta = usec_sub_unsigned(ts->realtime, previous_ts->realtime); + reliable_ts = false; + } else { + written_chars += fprintf(f, "%16s", ""); + goto finish; + } + + written_chars += fprintf(f, " <%5"PRI_USEC".%06"PRI_USEC"%s>", delta / USEC_PER_SEC, delta % USEC_PER_SEC, reliable_ts ? " " : "*"); + } + +finish: + written_chars += fprintf(f, "%s", "]"); + + return written_chars; +} + +static int output_timestamp_realtime( + FILE *f, + sd_journal *j, + OutputMode mode, + OutputFlags flags, + const dual_timestamp *ts) { + + char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, 64U)]; + int r; + + assert(f); + assert(j); + assert(ts); + + if (!VALID_REALTIME(ts->realtime)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); + + if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) { + const char *k; + + if (flags & OUTPUT_UTC) + k = format_timestamp_style(buf, sizeof(buf), ts->realtime, TIMESTAMP_UTC); + else + k = format_timestamp(buf, sizeof(buf), ts->realtime); + if (!k) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to format timestamp: %" PRIu64, ts->realtime); + + } else { + struct tm tm; + time_t t; + + t = (time_t) (ts->realtime / USEC_PER_SEC); + + switch (mode) { + + case OUTPUT_SHORT_UNIX: + xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, ts->realtime % USEC_PER_SEC); + break; + + case OUTPUT_SHORT_ISO: + if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", + localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to format ISO time"); + break; + + case OUTPUT_SHORT_ISO_PRECISE: { + char usec[7]; + + /* No usec in strftime, so we leave space and copy over */ + if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.xxxxxx%z", + localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to format ISO-precise time"); + xsprintf(usec, "%06"PRI_USEC, ts->realtime % USEC_PER_SEC); + memcpy(buf + 20, usec, 6); + break; + } + case OUTPUT_SHORT: + case OUTPUT_SHORT_PRECISE: + + if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", + localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to format syslog time"); + + if (mode == OUTPUT_SHORT_PRECISE) { + size_t k; + + assert(sizeof(buf) > strlen(buf)); + k = sizeof(buf) - strlen(buf); + + r = snprintf(buf + strlen(buf), k, ".%06"PRIu64, ts->realtime % USEC_PER_SEC); + if (r <= 0 || (size_t) r >= k) /* too long? */ + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to format precise time"); + } + break; + + default: + assert_not_reached(); + } + } + + fputs(buf, f); + return (int) strlen(buf); +} + +static int output_short( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + const Set *output_fields, + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { + + int r; + const void *data; + size_t length, n = 0; + _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, + *message = NULL, *priority = NULL, *transport = NULL, + *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL; + size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, + priority_len = 0, transport_len = 0, config_file_len = 0, + unit_len = 0, user_unit_len = 0, documentation_url_len = 0; + int p = LOG_INFO; + bool ellipsized = false, audit; + const ParseFieldVec fields[] = { + PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len), + PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len), + PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len), + PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len), + PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len), + PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len), + PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len), + PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len), + PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len), + PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len), + PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len), + PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len), + }; + size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0}; + + assert(f); + assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); + + /* Set the threshold to one bigger than the actual print + * threshold, so that if the line is actually longer than what + * we're willing to print, ellipsization will occur. This way + * we won't output a misleading line without any indication of + * truncation. + */ + sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + r = parse_fieldv(data, length, fields, ELEMENTSOF(fields)); + if (r < 0) + return r; + } + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to get journal fields: %m"); + + if (!message) { + log_debug("Skipping message without MESSAGE= field."); + return 0; + } + + if (!(flags & OUTPUT_SHOW_ALL)) + strip_tab_ansi(&message, &message_len, highlight_shifted); + + if (priority_len == 1 && *priority >= '0' && *priority <= '7') + p = *priority - '0'; + + audit = streq_ptr(transport, "audit"); + + if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) + r = output_timestamp_monotonic(f, mode, ts, boot_id, previous_ts, previous_boot_id); + else + r = output_timestamp_realtime(f, j, mode, flags, ts); + if (r < 0) + return r; + n += r; + + if (flags & OUTPUT_NO_HOSTNAME) { + /* Suppress display of the hostname if this is requested. */ + hostname = mfree(hostname); + hostname_len = 0; + } + + if (hostname && shall_print(hostname, hostname_len, flags)) { + fprintf(f, " %.*s", (int) hostname_len, hostname); + n += hostname_len + 1; + } + + if (mode == OUTPUT_WITH_UNIT && ((unit && shall_print(unit, unit_len, flags)) || + (user_unit && shall_print(user_unit, user_unit_len, flags)))) { + if (unit) { + fprintf(f, " %.*s", (int) unit_len, unit); + n += unit_len + 1; + } + if (user_unit) { + if (unit) + fprintf(f, "/%.*s", (int) user_unit_len, user_unit); + else + fprintf(f, " %.*s", (int) user_unit_len, user_unit); + n += unit_len + 1; + } + } else if (identifier && shall_print(identifier, identifier_len, flags)) { + fprintf(f, " %.*s", (int) identifier_len, identifier); + n += identifier_len + 1; + } else if (comm && shall_print(comm, comm_len, flags)) { + fprintf(f, " %.*s", (int) comm_len, comm); + n += comm_len + 1; + } else + fputs(" unknown", f); + + if (pid && shall_print(pid, pid_len, flags)) { + fprintf(f, "[%.*s]", (int) pid_len, pid); + n += pid_len + 2; + } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) { + fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid); + n += fake_pid_len + 2; + } + + fputs(": ", f); + + if (urlify_enabled()) { + _cleanup_free_ char *c = NULL; + + /* Insert a hyperlink to a documentation URL before the message. Note that we don't make the + * whole message a hyperlink, since otherwise the whole screen might end up being just + * hyperlinks. Moreover, we want to be able to highlight parts of the message (such as the + * config file, see below) hence let's keep the documentation URL link separate. */ + + if (documentation_url && shall_print(documentation_url, documentation_url_len, flags)) { + c = strndup(documentation_url, documentation_url_len); + if (!c) + return log_oom(); + + if (!documentation_url_is_valid(c)) /* Eat up invalid links */ + c = mfree(c); + } + + if (!c) + (void) url_from_catalog(j, &c); /* Acquire from catalog if not embedded in log message itself */ + + if (c) { + _cleanup_free_ char *urlified = NULL; + + if (terminal_urlify(c, special_glyph(SPECIAL_GLYPH_EXTERNAL_LINK), &urlified) >= 0) { + fputs(urlified, f); + fputc(' ', f); + } + } + } + + if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) + fprintf(f, "[%s blob data]\n", FORMAT_BYTES(message_len)); + else { + + /* URLify config_file string in message, if the message starts with it. + * Skip URLification if the highlighted pattern overlaps. */ + if (config_file && + message_len >= config_file_len && + memcmp(message, config_file, config_file_len) == 0 && + (message_len == config_file_len || IN_SET(message[config_file_len], ':', ' ')) && + (!highlight || highlight_shifted[0] == 0 || highlight_shifted[0] > config_file_len)) { + + _cleanup_free_ char *t = NULL, *urlified = NULL; + + t = strndup(config_file, config_file_len); + if (t && terminal_urlify_path(t, NULL, &urlified) >= 0) { + size_t urlified_len = strlen(urlified); + size_t shift = urlified_len - config_file_len; + char *joined; + + joined = realloc(urlified, message_len + shift); + if (joined) { + memcpy(joined + urlified_len, message + config_file_len, message_len - config_file_len); + free_and_replace(message, joined); + TAKE_PTR(urlified); + message_len += shift; + if (highlight) { + highlight_shifted[0] += shift; + highlight_shifted[1] += shift; + } + } + } + } + + ellipsized |= + print_multiline(f, n + 2, n_columns, flags, p, audit, + message, message_len, + highlight_shifted); + } + + if (flags & OUTPUT_CATALOG) + (void) print_catalog(f, j); + + return ellipsized; +} + +static int output_verbose( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + const Set *output_fields, + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { + + const void *data; + size_t length; + _cleanup_free_ char *cursor = NULL; + char buf[FORMAT_TIMESTAMP_MAX + 7]; + const char *timestamp; + int r; + + assert(f); + assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); + + sd_journal_set_data_threshold(j, 0); + + if (!VALID_REALTIME(ts->realtime)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + timestamp = format_timestamp_style(buf, sizeof buf, ts->realtime, + flags & OUTPUT_UTC ? TIMESTAMP_US_UTC : TIMESTAMP_US); + fprintf(f, "%s [%s]\n", + timestamp ?: "(no timestamp)", + cursor); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + const char *c, *p; + int fieldlen; + const char *on = "", *off = ""; + _cleanup_free_ char *urlified = NULL; + size_t valuelen; + + c = memchr(data, '=', length); + if (!c) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field."); + + fieldlen = c - (const char*) data; + if (!journal_field_valid(data, fieldlen, true)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field."); + + r = field_set_test(output_fields, data, fieldlen); + if (r < 0) + return r; + if (r == 0) + continue; + + valuelen = length - 1 - fieldlen; + + if ((flags & OUTPUT_COLOR) && (p = startswith(data, "MESSAGE="))) { + on = ANSI_HIGHLIGHT; + off = ANSI_NORMAL; + } else if ((p = startswith(data, "CONFIG_FILE="))) { + if (terminal_urlify_path(p, NULL, &urlified) >= 0) { + p = urlified; + valuelen = strlen(urlified); + } + } else + p = c + 1; + + if ((flags & OUTPUT_SHOW_ALL) || + (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH) + && utf8_is_printable(data, length))) { + fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data); + print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, false, + p, valuelen, + NULL); + fputs(off, f); + } else + fprintf(f, " %s%.*s=[%s blob data]%s\n", + on, + (int) (c - (const char*) data), + (const char*) data, + FORMAT_BYTES(length - (c - (const char *) data) - 1), + off); + } + + if (r < 0) + return r; + + if (flags & OUTPUT_CATALOG) + (void) print_catalog(f, j); + + return 0; +} + +static int output_export( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + const Set *output_fields, + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { + + _cleanup_free_ char *cursor = NULL; + const void *data; + size_t length; + usec_t monotonic, realtime; + sd_id128_t journal_boot_id; + int r; + + assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + r = sd_journal_get_monotonic_usec(j, &monotonic, &journal_boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + fprintf(f, + "__CURSOR=%s\n" + "__REALTIME_TIMESTAMP="USEC_FMT"\n" + "__MONOTONIC_TIMESTAMP="USEC_FMT"\n" + "_BOOT_ID=%s\n", + cursor, + realtime, + monotonic, + SD_ID128_TO_STRING(journal_boot_id)); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + size_t fieldlen; + const char *c; + + /* We already printed the boot id from the data in the header, hence let's suppress it here */ + if (memory_startswith(data, length, "_BOOT_ID=")) + continue; + + c = memchr(data, '=', length); + if (!c) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field."); + + fieldlen = c - (const char*) data; + if (!journal_field_valid(data, fieldlen, true)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field."); + + r = field_set_test(output_fields, data, fieldlen); + if (r < 0) + return r; + if (!r) + continue; + + if (utf8_is_printable_newline(data, length, false)) + fwrite(data, length, 1, f); + else { + uint64_t le64; + + fwrite(data, fieldlen, 1, f); + fputc('\n', f); + le64 = htole64(length - fieldlen - 1); + fwrite(&le64, sizeof(le64), 1, f); + fwrite(c + 1, length - fieldlen - 1, 1, f); + } + + fputc('\n', f); + } + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + + if (r < 0) + return r; + + fputc('\n', f); + + return 0; +} + +void json_escape( + FILE *f, + const char* p, + size_t l, + OutputFlags flags) { + + assert(f); + assert(p); + + if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) + fputs("null", f); + + else if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(p, l)) { + bool not_first = false; + + fputs("[ ", f); + + while (l > 0) { + if (not_first) + fprintf(f, ", %u", (uint8_t) *p); + else { + not_first = true; + fprintf(f, "%u", (uint8_t) *p); + } + + p++; + l--; + } + + fputs(" ]", f); + } else { + fputc('"', f); + + while (l > 0) { + if (IN_SET(*p, '"', '\\')) { + fputc('\\', f); + fputc(*p, f); + } else if (*p == '\n') + fputs("\\n", f); + else if ((uint8_t) *p < ' ') + fprintf(f, "\\u%04x", (uint8_t) *p); + else + fputc(*p, f); + + p++; + l--; + } + + fputc('"', f); + } +} + +struct json_data { + JsonVariant* name; + size_t n_values; + JsonVariant* values[]; +}; + +static int update_json_data( + Hashmap *h, + OutputFlags flags, + const char *name, + const void *value, + size_t size) { + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + struct json_data *d; + int r; + + if (!(flags & OUTPUT_SHOW_ALL) && strlen(name) + 1 + size >= JSON_THRESHOLD) + r = json_variant_new_null(&v); + else if (utf8_is_printable(value, size)) + r = json_variant_new_stringn(&v, value, size); + else + r = json_variant_new_array_bytes(&v, value, size); + if (r < 0) + return log_error_errno(r, "Failed to allocate JSON data: %m"); + + d = hashmap_get(h, name); + if (d) { + struct json_data *w; + + w = realloc(d, offsetof(struct json_data, values) + sizeof(JsonVariant*) * (d->n_values + 1)); + if (!w) + return log_oom(); + + d = w; + assert_se(hashmap_update(h, json_variant_string(d->name), d) >= 0); + } else { + _cleanup_(json_variant_unrefp) JsonVariant *n = NULL; + + r = json_variant_new_string(&n, name); + if (r < 0) + return log_error_errno(r, "Failed to allocate JSON name variant: %m"); + + d = malloc0(offsetof(struct json_data, values) + sizeof(JsonVariant*)); + if (!d) + return log_oom(); + + r = hashmap_put(h, json_variant_string(n), d); + if (r < 0) { + free(d); + return log_error_errno(r, "Failed to insert JSON name into hashmap: %m"); + } + + d->name = TAKE_PTR(n); + } + + d->values[d->n_values++] = TAKE_PTR(v); + return 0; +} + +static int update_json_data_split( + Hashmap *h, + OutputFlags flags, + const Set *output_fields, + const void *data, + size_t size) { + + size_t fieldlen; + const char *eq; + char *name; + + assert(h); + assert(data || size == 0); + + if (memory_startswith(data, size, "_BOOT_ID=")) + return 0; + + eq = memchr(data, '=', MIN(size, JSON_THRESHOLD)); + if (!eq) + return 0; + + fieldlen = eq - (const char*) data; + if (!journal_field_valid(data, fieldlen, true)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid field."); + + name = strndupa_safe(data, fieldlen); + if (output_fields && !set_contains(output_fields, name)) + return 0; + + return update_json_data(h, flags, name, eq + 1, size - fieldlen - 1); +} + +static int output_json( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + const Set *output_fields, + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { + + char sid[SD_ID128_STRING_MAX], usecbuf[DECIMAL_STR_MAX(usec_t)]; + _cleanup_(json_variant_unrefp) JsonVariant *object = NULL; + _cleanup_free_ char *cursor = NULL; + JsonVariant **array = NULL; + struct json_data *d; + Hashmap *h = NULL; + size_t n = 0; + usec_t realtime, monotonic; + sd_id128_t journal_boot_id; + int r; + + assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); + + (void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + r = sd_journal_get_monotonic_usec(j, &monotonic, &journal_boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + h = hashmap_new(&string_hash_ops); + if (!h) + return log_oom(); + + r = update_json_data(h, flags, "__CURSOR", cursor, strlen(cursor)); + if (r < 0) + goto finish; + + xsprintf(usecbuf, USEC_FMT, realtime); + r = update_json_data(h, flags, "__REALTIME_TIMESTAMP", usecbuf, strlen(usecbuf)); + if (r < 0) + goto finish; + + xsprintf(usecbuf, USEC_FMT, monotonic); + r = update_json_data(h, flags, "__MONOTONIC_TIMESTAMP", usecbuf, strlen(usecbuf)); + if (r < 0) + goto finish; + + sd_id128_to_string(journal_boot_id, sid); + r = update_json_data(h, flags, "_BOOT_ID", sid, strlen(sid)); + if (r < 0) + goto finish; + + for (;;) { + const void *data; + size_t size; + + r = sd_journal_enumerate_data(j, &data, &size); + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + r = 0; + goto finish; + } + if (r < 0) { + log_error_errno(r, "Failed to read journal: %m"); + goto finish; + } + if (r == 0) + break; + + r = update_json_data_split(h, flags, output_fields, data, size); + if (r < 0) + goto finish; + } + + array = new(JsonVariant*, hashmap_size(h)*2); + if (!array) { + r = log_oom(); + goto finish; + } + + HASHMAP_FOREACH(d, h) { + assert(d->n_values > 0); + + array[n++] = json_variant_ref(d->name); + + if (d->n_values == 1) + array[n++] = json_variant_ref(d->values[0]); + else { + _cleanup_(json_variant_unrefp) JsonVariant *q = NULL; + + r = json_variant_new_array(&q, d->values, d->n_values); + if (r < 0) { + log_error_errno(r, "Failed to create JSON array: %m"); + goto finish; + } + + array[n++] = TAKE_PTR(q); + } + } + + r = json_variant_new_object(&object, array, n); + if (r < 0) { + log_error_errno(r, "Failed to allocate JSON object: %m"); + goto finish; + } + + json_variant_dump(object, + output_mode_to_json_format_flags(mode) | + (FLAGS_SET(flags, OUTPUT_COLOR) ? JSON_FORMAT_COLOR : 0), + f, NULL); + + r = 0; + +finish: + while ((d = hashmap_steal_first(h))) { + size_t k; + + json_variant_unref(d->name); + for (k = 0; k < d->n_values; k++) + json_variant_unref(d->values[k]); + + free(d); + } + + hashmap_free(h); + + json_variant_unref_many(array, n); + free(array); + + return r; +} + +static int output_cat_field( + FILE *f, + sd_journal *j, + OutputFlags flags, + int prio, + const char *field, + const size_t highlight[2]) { + + const char *color_on = "", *color_off = "", *highlight_on = ""; + const void *data; + size_t l, fl; + int r; + + if (FLAGS_SET(flags, OUTPUT_COLOR)) + get_log_colors(prio, &color_on, &color_off, &highlight_on); + + r = sd_journal_get_data(j, field, &data, &l); + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + if (r == -ENOENT) /* An entry without the requested field */ + return 0; + if (r < 0) + return log_error_errno(r, "Failed to get data: %m"); + + fl = strlen(field); + assert(l >= fl + 1); + assert(((char*) data)[fl] == '='); + + data = (const uint8_t*) data + fl + 1; + l -= fl + 1; + + if (FLAGS_SET(flags, OUTPUT_COLOR)) { + if (highlight) { + assert(highlight[0] <= highlight[1]); + assert(highlight[1] <= l); + + fputs(color_on, f); + fwrite((const char*) data, 1, highlight[0], f); + fputs(highlight_on, f); + fwrite((const char*) data + highlight[0], 1, highlight[1] - highlight[0], f); + fputs(color_on, f); + fwrite((const char*) data + highlight[1], 1, l - highlight[1], f); + fputs(color_off, f); + } else { + fputs(color_on, f); + fwrite((const char*) data, 1, l, f); + fputs(color_off, f); + } + } else + fwrite((const char*) data, 1, l, f); + + fputc('\n', f); + return 0; +} + +static int output_cat( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + const Set *output_fields, + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { + + int r, prio = LOG_INFO; + const char *field; + + assert(j); + assert(f); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); + + (void) sd_journal_set_data_threshold(j, 0); + + if (FLAGS_SET(flags, OUTPUT_COLOR)) { + const void *data; + size_t l; + + /* Determine priority of this entry, so that we can color it nicely */ + + r = sd_journal_get_data(j, "PRIORITY", &data, &l); + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Failed to get data: %m"); + + /* An entry without PRIORITY */ + } else if (l == 10 && memcmp(data, "PRIORITY=", 9) == 0) { + char c = ((char*) data)[9]; + + if (c >= '0' && c <= '7') + prio = c - '0'; + } + } + + if (set_isempty(output_fields)) + return output_cat_field(f, j, flags, prio, "MESSAGE", highlight); + + SET_FOREACH(field, output_fields) { + r = output_cat_field(f, j, flags, prio, field, streq(field, "MESSAGE") ? highlight : NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int get_dual_timestamp(sd_journal *j, dual_timestamp *ret_ts, sd_id128_t *ret_boot_id) { + const void *data; + _cleanup_free_ char *realtime = NULL, *monotonic = NULL; + size_t length = 0, realtime_len = 0, monotonic_len = 0; + const ParseFieldVec message_fields[] = { + PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len), + PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len), + }; + int r; + bool realtime_good = false, monotonic_good = false, boot_id_good = false; + + assert(j); + assert(ret_ts); + assert(ret_boot_id); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields)); + if (r < 0) + return r; + + if (realtime && monotonic) + break; + } + if (r < 0) + return r; + + if (realtime) + realtime_good = safe_atou64(realtime, &ret_ts->realtime) >= 0; + if (!realtime_good || !VALID_REALTIME(ret_ts->realtime)) + realtime_good = sd_journal_get_realtime_usec(j, &ret_ts->realtime) >= 0; + if (!realtime_good) + ret_ts->realtime = USEC_INFINITY; + + if (monotonic) + monotonic_good = safe_atou64(monotonic, &ret_ts->monotonic) >= 0; + if (!monotonic_good || !VALID_MONOTONIC(ret_ts->monotonic)) + monotonic_good = boot_id_good = sd_journal_get_monotonic_usec(j, &ret_ts->monotonic, ret_boot_id) >= 0; + if (!monotonic_good) + ret_ts->monotonic = USEC_INFINITY; + + if (!boot_id_good) + boot_id_good = sd_journal_get_monotonic_usec(j, NULL, ret_boot_id) >= 0; + if (!boot_id_good) + *ret_boot_id = SD_ID128_NULL; + + /* Restart all data before */ + sd_journal_restart_data(j); + sd_journal_restart_unique(j); + sd_journal_restart_fields(j); + + return 0; +} + +static int (*output_funcs[_OUTPUT_MODE_MAX])( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + const Set *output_fields, + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) = { + + [OUTPUT_SHORT] = output_short, + [OUTPUT_SHORT_ISO] = output_short, + [OUTPUT_SHORT_ISO_PRECISE] = output_short, + [OUTPUT_SHORT_PRECISE] = output_short, + [OUTPUT_SHORT_MONOTONIC] = output_short, + [OUTPUT_SHORT_DELTA] = output_short, + [OUTPUT_SHORT_UNIX] = output_short, + [OUTPUT_SHORT_FULL] = output_short, + [OUTPUT_VERBOSE] = output_verbose, + [OUTPUT_EXPORT] = output_export, + [OUTPUT_JSON] = output_json, + [OUTPUT_JSON_PRETTY] = output_json, + [OUTPUT_JSON_SSE] = output_json, + [OUTPUT_JSON_SEQ] = output_json, + [OUTPUT_CAT] = output_cat, + [OUTPUT_WITH_UNIT] = output_short, +}; + +int show_journal_entry( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + char **output_fields, + const size_t highlight[2], + bool *ellipsized, + dual_timestamp *previous_ts, + sd_id128_t *previous_boot_id) { + + _cleanup_set_free_ Set *fields = NULL; + dual_timestamp ts = DUAL_TIMESTAMP_NULL; + sd_id128_t boot_id = SD_ID128_NULL; + int r; + + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + assert(previous_ts); + assert(previous_boot_id); + + if (n_columns <= 0) + n_columns = columns(); + + r = set_put_strdupv(&fields, output_fields); + if (r < 0) + return r; + + r = get_dual_timestamp(j, &ts, &boot_id); + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to get journal fields: %m"); + + r = output_funcs[mode](f, j, mode, n_columns, flags, fields, highlight, &ts, &boot_id, previous_ts, previous_boot_id); + + /* Store timestamp and boot ID for next iteration */ + *previous_ts = ts; + *previous_boot_id = boot_id; + + if (ellipsized && r > 0) + *ellipsized = true; + + return r; +} + +static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) { + assert(f); + assert(flags); + + if (!(*flags & OUTPUT_BEGIN_NEWLINE)) + return 0; + + /* Print a beginning new line if that's request, but only once + * on the first line we print. */ + + fputc('\n', f); + *flags &= ~OUTPUT_BEGIN_NEWLINE; + return 0; +} + +int show_journal( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + OutputFlags flags, + bool *ellipsized) { + + int r; + unsigned line = 0; + bool need_seek = false; + int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; + dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; + sd_id128_t previous_boot_id = SD_ID128_NULL; + + assert(j); + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + + if (how_many == UINT_MAX) + need_seek = true; + else { + /* Seek to end */ + r = sd_journal_seek_tail(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to tail: %m"); + + r = sd_journal_previous_skip(j, how_many); + if (r < 0) + return log_error_errno(r, "Failed to skip previous: %m"); + } + + for (;;) { + usec_t usec; + + if (need_seek) { + r = sd_journal_next(j); + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + } + + if (r == 0) + break; + + need_seek = true; + + if (not_before > 0) { + r = sd_journal_get_monotonic_usec(j, &usec, NULL); + + /* -ESTALE is returned if the timestamp is not from this boot */ + if (r == -ESTALE) + continue; + else if (r < 0) + return log_error_errno(r, "Failed to get journal time: %m"); + + if (usec < not_before) + continue; + } + + line++; + maybe_print_begin_newline(f, &flags); + + r = show_journal_entry(f, j, mode, n_columns, flags, NULL, NULL, ellipsized, + &previous_ts, &previous_boot_id); + if (r < 0) + return r; + } + + if (warn_cutoff && line < how_many && not_before > 0) { + sd_id128_t boot_id; + usec_t cutoff = 0; + + /* Check whether the cutoff line is too early */ + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id: %m"); + + r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get journal cutoff time: %m"); + + if (r > 0 && not_before < cutoff) { + maybe_print_begin_newline(f, &flags); + + /* If we logged *something* and no permission error happened, than we can reliably + * emit the warning about rotation. If we didn't log anything and access errors + * happened, emit hint about permissions. Otherwise, give a generic message, since we + * can't diagnose the issue. */ + + bool noaccess = journal_access_blocked(j); + + if (line == 0 && noaccess) + fprintf(f, "Warning: some journal files were not opened due to insufficient permissions.\n"); + else if (!noaccess) + fprintf(f, "Notice: journal has been rotated since unit was started, output may be incomplete.\n"); + else + fprintf(f, "Warning: journal has been rotated since unit was started and some journal " + "files were not opened due to insufficient permissions, output may be incomplete.\n"); + } + + warn_cutoff = false; + } + + return 0; +} + +int add_matches_for_unit(sd_journal *j, const char *unit) { + const char *m1, *m2, *m3, *m4; + int r; + + assert(j); + assert(unit); + + m1 = strjoina("_SYSTEMD_UNIT=", unit); + m2 = strjoina("COREDUMP_UNIT=", unit); + m3 = strjoina("UNIT=", unit); + m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit); + + (void)( + /* Look for messages from the service itself */ + (r = sd_journal_add_match(j, m1, 0)) || + + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = sd_journal_add_match(j, m2, 0)) || + + /* Look for messages from PID 1 about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_PID=1", 0)) || + (r = sd_journal_add_match(j, m3, 0)) || + + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = sd_journal_add_match(j, m4, 0)) + ); + + if (r == 0 && endswith(unit, ".slice")) { + const char *m5; + + m5 = strjoina("_SYSTEMD_SLICE=", unit); + + /* Show all messages belonging to a slice */ + (void)( + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m5, 0)) + ); + } + + return r; +} + +int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { + int r; + char *m1, *m2, *m3, *m4; + char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; + + assert(j); + assert(unit); + + m1 = strjoina("_SYSTEMD_USER_UNIT=", unit); + m2 = strjoina("USER_UNIT=", unit); + m3 = strjoina("COREDUMP_USER_UNIT=", unit); + m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit); + sprintf(muid, "_UID="UID_FMT, uid); + + (void) ( + /* Look for messages from the user service itself */ + (r = sd_journal_add_match(j, m1, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + + /* Look for messages from systemd about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m2, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m3, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m4, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) + ); + + if (r == 0 && endswith(unit, ".slice")) { + const char *m5; + + m5 = strjoina("_SYSTEMD_USER_SLICE=", unit); + + /* Show all messages belonging to a slice */ + (void)( + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m5, 0)) || + (r = sd_journal_add_match(j, muid, 0)) + ); + } + + return r; +} + +static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; + char buf[SD_ID128_UUID_STRING_MAX]; + pid_t pid, child; + ssize_t k; + int r; + + assert(machine); + assert(boot_id); + + r = container_get_leader(machine, &pid); + if (r < 0) + return r; + + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG, + pidnsfd, mntnsfd, -1, -1, rootfd, &child); + if (r < 0) + return r; + if (r == 0) { + int fd; + + pair[0] = safe_close(pair[0]); + + fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + _exit(EXIT_FAILURE); + + r = loop_read_exact(fd, buf, 36, false); + safe_close(fd); + if (r < 0) + _exit(EXIT_FAILURE); + + k = send(pair[1], buf, 36, MSG_NOSIGNAL); + if (k != 36) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate_and_check("(sd-bootidns)", child, 0); + if (r < 0) + return r; + if (r != EXIT_SUCCESS) + return -EIO; + + k = recv(pair[0], buf, 36, 0); + if (k != 36) + return -EIO; + + buf[36] = 0; + r = sd_id128_from_string(buf, boot_id); + if (r < 0) + return r; + + return 0; +} + +int add_match_this_boot(sd_journal *j, const char *machine) { + char match[9+32+1] = "_BOOT_ID="; + sd_id128_t boot_id; + int r; + + assert(j); + + if (machine) { + r = get_boot_id_for_machine(machine, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id of container %s: %m", machine); + } else { + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id: %m"); + } + + sd_id128_to_string(boot_id, match + 9); + r = sd_journal_add_match(j, match, strlen(match)); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + r = sd_journal_add_conjunction(j); + if (r < 0) + return log_error_errno(r, "Failed to add conjunction: %m"); + + return 0; +} + +int show_journal_by_unit( + FILE *f, + const char *unit, + const char *log_namespace, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + uid_t uid, + OutputFlags flags, + int journal_open_flags, + bool system_unit, + bool *ellipsized) { + + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + assert(unit); + + if (how_many <= 0) + return 0; + + r = sd_journal_open_namespace(&j, log_namespace, journal_open_flags | SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE); + if (r < 0) + return log_error_errno(r, "Failed to open journal: %m"); + + if (system_unit) + r = add_matches_for_unit(j, unit); + else + r = add_matches_for_user_unit(j, unit, uid); + if (r < 0) + return log_error_errno(r, "Failed to add unit matches: %m"); + + r = sd_journal_add_conjunction(j); + if (r < 0) + return log_error_errno(r, "Failed to add conjunction: %m"); + + r = add_match_this_boot(j, NULL); + if (r < 0) + return r; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *filter = NULL; + + filter = journal_make_match_string(j); + if (!filter) + return log_oom(); + + log_debug("Journal filter: %s", filter); + } + + return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized); +} |