diff options
author | Lennart Poettering <lennart@poettering.net> | 2018-05-31 15:33:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-31 15:33:44 +0200 |
commit | 89544ae65827c6fc19579861961d26157b572bbb (patch) | |
tree | 6ada0a4d4eaac607ab88ffc6ed9cf9fe65707351 | |
parent | 8d96289711c51c5099be2241af8b213e89d9dbd1 (diff) | |
parent | 0ab896b343afeaa70f69881626a50855dc518d68 (diff) | |
download | systemd-89544ae65827c6fc19579861961d26157b572bbb.tar.gz |
Merge pull request #9014 from keszybz/fuzz-journal-remote
A fuzzer for journal-remote
47 files changed, 1882 insertions, 1351 deletions
diff --git a/man/sd_id128_get_machine.xml b/man/sd_id128_get_machine.xml index 3622bbd9f2..827756d135 100644 --- a/man/sd_id128_get_machine.xml +++ b/man/sd_id128_get_machine.xml @@ -116,9 +116,11 @@ <refsect1> <title>Return Value</title> - <para>The two calls return 0 on success (in which case - <parameter>ret</parameter> is filled in), or a negative - errno-style error code.</para> + <para>Those calls return 0 on success (in which case <parameter>ret</parameter> is filled in), + or a negative errno-style error code. In particular, <function>sd_id128_get_machine()</function> + and <function>sd_id128_get_machine_app_specific()</function> return <constant>-ENOENT</constant> + if <filename>/etc/machine-id</filename> is missing, and <constant>-ENOMEDIUM</constant> if is + empty or all zeros.</para> </refsect1> <refsect1> diff --git a/meson.build b/meson.build index 83fed46cc7..484271ff1c 100644 --- a/meson.build +++ b/meson.build @@ -1258,6 +1258,7 @@ includes = include_directories('src/basic', 'src/shared', 'src/systemd', 'src/journal', + 'src/journal-remote', 'src/nspawn', 'src/resolve', 'src/timesync', @@ -2007,7 +2008,8 @@ if conf.get('ENABLE_REMOTE') == 1 and conf.get('HAVE_MICROHTTPD') == 1 s_j_remote = executable('systemd-journal-remote', systemd_journal_remote_sources, include_directories : includes, - link_with : [libshared], + link_with : [libshared, + libsystemd_journal_remote], dependencies : [threads, libmicrohttpd, libgnutls, diff --git a/src/basic/escape.c b/src/basic/escape.c index fe951e3db8..2e605b2ebe 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -15,8 +15,8 @@ #include "macro.h" #include "utf8.h" -size_t cescape_char(char c, char *buf) { - char * buf_old = buf; +int cescape_char(char c, char *buf) { + char *buf_old = buf; switch (c) { diff --git a/src/basic/escape.h b/src/basic/escape.h index 6893f0199b..b47052b142 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -45,7 +45,7 @@ typedef enum EscapeStyle { char *cescape(const char *s); char *cescape_length(const char *s, size_t n); -size_t cescape_char(char c, char *buf); +int cescape_char(char c, char *buf); int cunescape(const char *s, UnescapeFlags flags, char **ret); int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret); diff --git a/src/basic/journal-importer.c b/src/basic/journal-importer.c index 7445a308a4..6fd0f937e2 100644 --- a/src/basic/journal-importer.c +++ b/src/basic/journal-importer.c @@ -9,9 +9,12 @@ #include <unistd.h> #include "alloc-util.h" +#include "escape.h" #include "fd-util.h" #include "io-util.h" +#include "journal-file.h" #include "journal-importer.h" +#include "journal-util.h" #include "parse-util.h" #include "string-util.h" #include "unaligned.h" @@ -232,56 +235,78 @@ static int get_data_newline(JournalImporter *imp) { assert(data); if (*data != '\n') { - log_error("expected newline, got '%c'", *data); + char buf[4]; + int l; + + l = cescape_char(*data, buf); + log_error("Expected newline, got '%.*s'", l, buf); return -EINVAL; } return 1; } -static int process_dunder(JournalImporter *imp, char *line, size_t n) { - const char *timestamp; +static int process_special_field(JournalImporter *imp, char *line) { + const char *value; + char buf[CELLESCAPE_DEFAULT_LENGTH]; int r; assert(line); - assert(n > 0); - assert(line[n-1] == '\n'); - - /* XXX: is it worth to support timestamps in extended format? - * We don't produce them, but who knows... */ - timestamp = startswith(line, "__CURSOR="); - if (timestamp) + value = startswith(line, "__CURSOR="); + if (value) /* ignore __CURSOR */ return 1; - timestamp = startswith(line, "__REALTIME_TIMESTAMP="); - if (timestamp) { - long long unsigned x; - line[n-1] = '\0'; - r = safe_atollu(timestamp, &x); + value = startswith(line, "__REALTIME_TIMESTAMP="); + if (value) { + uint64_t x; + + r = safe_atou64(value, &x); if (r < 0) - log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp); - else - imp->ts.realtime = x; - return r < 0 ? r : 1; + return log_warning_errno(r, "Failed to parse __REALTIME_TIMESTAMP '%s': %m", + cellescape(buf, sizeof buf, value)); + else if (!VALID_REALTIME(x)) { + log_warning("__REALTIME_TIMESTAMP out of range, ignoring: %"PRIu64, x); + return -ERANGE; + } + + imp->ts.realtime = x; + return 1; } - timestamp = startswith(line, "__MONOTONIC_TIMESTAMP="); - if (timestamp) { - long long unsigned x; - line[n-1] = '\0'; - r = safe_atollu(timestamp, &x); + value = startswith(line, "__MONOTONIC_TIMESTAMP="); + if (value) { + uint64_t x; + + r = safe_atou64(value, &x); if (r < 0) - log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp); - else - imp->ts.monotonic = x; - return r < 0 ? r : 1; + return log_warning_errno(r, "Failed to parse __MONOTONIC_TIMESTAMP '%s': %m", + cellescape(buf, sizeof buf, value)); + else if (!VALID_MONOTONIC(x)) { + log_warning("__MONOTONIC_TIMESTAMP out of range, ignoring: %"PRIu64, x); + return -ERANGE; + } + + imp->ts.monotonic = x; + return 1; } - timestamp = startswith(line, "__"); - if (timestamp) { - log_notice("Unknown dunder line %s", line); + /* Just a single underline, but it needs special treatment too. */ + value = startswith(line, "_BOOT_ID="); + if (value) { + r = sd_id128_from_string(value, &imp->boot_id); + if (r < 0) + return log_warning_errno(r, "Failed to parse _BOOT_ID '%s': %m", + cellescape(buf, sizeof buf, value)); + + /* store the field in the usual fashion too */ + return 0; + } + + value = startswith(line, "__"); + if (value) { + log_notice("Unknown dunder line __%s, ignoring.", cellescape(buf, sizeof buf, value)); return 1; } @@ -314,10 +339,6 @@ int journal_importer_process_data(JournalImporter *imp) { return 1; } - r = process_dunder(imp, line, n); - if (r != 0) - return r < 0 ? r : 0; - /* MESSAGE=xxx\n or COREDUMP\n @@ -328,6 +349,21 @@ int journal_importer_process_data(JournalImporter *imp) { /* chomp newline */ n--; + if (!journal_field_valid(line, sep - line, true)) { + char buf[64], *t; + + t = strndupa(line, sep - line); + log_debug("Ignoring invalid field: \"%s\"", + cellescape(buf, sizeof buf, t)); + + return 0; + } + + line[n] = '\0'; + r = process_special_field(imp, line); + if (r != 0) + return r < 0 ? r : 0; + r = iovw_put(&imp->iovw, line, n); if (r < 0) return r; diff --git a/src/basic/journal-importer.h b/src/basic/journal-importer.h index 7434a789d8..c3db3d92d0 100644 --- a/src/basic/journal-importer.h +++ b/src/basic/journal-importer.h @@ -11,6 +11,8 @@ #include <stdbool.h> #include <sys/uio.h> +#include "sd-id128.h" + #include "time-util.h" /* Make sure not to make this smaller than the maximum coredump size. @@ -45,6 +47,7 @@ typedef struct JournalImporter { int state; dual_timestamp ts; + sd_id128_t boot_id; } JournalImporter; void journal_importer_cleanup(JournalImporter *); diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 0750083b88..03117c225e 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -46,7 +46,7 @@ int acquire_random_bytes(void *p, size_t n, bool high_quality_required) { * for us. */ /* Use the getrandom() syscall unless we know we don't have it. */ - if (have_syscall != 0) { + if (have_syscall != 0 && !HAS_FEATURE_MEMORY_SANITIZER) { r = getrandom(p, n, GRND_NONBLOCK); if (r > 0) { have_syscall = true; diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 07c9938a3f..b06b50397e 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -14,6 +14,7 @@ #include <string.h> #include "alloc-util.h" +#include "escape.h" #include "gunicode.h" #include "locale-util.h" #include "macro.h" @@ -453,6 +454,20 @@ bool string_has_cc(const char *p, const char *ok) { return false; } +static int write_ellipsis(char *buf, bool unicode) { + if (unicode || is_locale_utf8()) { + buf[0] = 0xe2; /* tri-dot ellipsis: … */ + buf[1] = 0x80; + buf[2] = 0xa6; + } else { + buf[0] = '.'; + buf[1] = '.'; + buf[2] = '.'; + } + + return 3; +} + static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { size_t x, need_space; char *r; @@ -501,17 +516,7 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le assert(x <= new_length - need_space); memcpy(r, s, x); - - if (is_locale_utf8()) { - r[x+0] = 0xe2; /* tri-dot ellipsis: … */ - r[x+1] = 0x80; - r[x+2] = 0xa6; - } else { - r[x+0] = '.'; - r[x+1] = '.'; - r[x+2] = '.'; - } - + write_ellipsis(r + x, false); memcpy(r + x + 3, s + old_length - (new_length - x - need_space), new_length - x - need_space + 1); @@ -596,23 +601,56 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne */ memcpy(e, s, len); - e[len + 0] = 0xe2; /* tri-dot ellipsis: … */ - e[len + 1] = 0x80; - e[len + 2] = 0xa6; - + write_ellipsis(e + len, true); memcpy(e + len + 3, j, len2 + 1); return e; } char *ellipsize(const char *s, size_t length, unsigned percent) { - if (length == (size_t) -1) return strdup(s); return ellipsize_mem(s, strlen(s), length, percent); } +char *cellescape(char *buf, size_t len, const char *s) { + /* Escape and ellipsize s into buffer buf of size len. Only non-control ASCII + * characters are copied as they are, everything else is escaped. The result + * is different then if escaping and ellipsization was performed in two + * separate steps, because each sequence is either stored in full or skipped. + * + * This function should be used for logging about strings which expected to + * be plain ASCII in a safe way. + * + * An ellipsis will be used if s is too long. It was always placed at the + * very end. + */ + + size_t i; + const char *t = s; + + assert(len > 4 + 4 + 1); /* two chars and the terminator */ + + for (i = 0; i < len - 9; t++) { + if (!*t) + goto done; + i += cescape_char(*t, buf + i); + } + + /* We have space for one more char and terminating nul at this point */ + if (*t) { + if (*(t+1)) + i += write_ellipsis(buf + i, false); + else + i += cescape_char(*t, buf + i); + } + + done: + buf[i] = '\0'; + return buf; +} + bool nulstr_contains(const char *nulstr, const char *needle) { const char *i; diff --git a/src/basic/string-util.h b/src/basic/string-util.h index aa00724266..25980e7cc8 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -157,6 +157,10 @@ bool string_has_cc(const char *p, const char *ok) _pure_; char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); char *ellipsize(const char *s, size_t length, unsigned percent); +char *cellescape(char *buf, size_t len, const char *s); + +/* This limit is arbitrary, enough to give some idea what the string contains */ +#define CELLESCAPE_DEFAULT_LENGTH 64 bool nulstr_contains(const char *nulstr, const char *needle); diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 5d278d42cb..0601d4fa92 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -282,8 +282,11 @@ static char *format_timestamp_internal( return NULL; /* Timestamp is unset */ /* Let's not format times with years > 9999 */ - if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) - return NULL; + if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) { + assert(l >= strlen("--- XXXX-XX-XX XX:XX:XX") + 1); + strcpy(buf, "--- XXXX-XX-XX XX:XX:XX"); + return buf; + } sec = (time_t) (t / USEC_PER_SEC); /* Round down */ diff --git a/src/fuzz/fuzz-journal-remote.c b/src/fuzz/fuzz-journal-remote.c new file mode 100644 index 0000000000..432c687bc0 --- /dev/null +++ b/src/fuzz/fuzz-journal-remote.c @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fuzz.h" + +#include <sys/mman.h> + +#include "sd-journal.h" + +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "journal-remote.h" +#include "logs-show.h" +#include "memfd-util.h" +#include "strv.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_fclose_ FILE *dev_null = NULL; + RemoteServer s = {}; + char name[] = "/tmp/fuzz-journal-remote.XXXXXX.journal"; + void *mem; + int fdin; /* will be closed by journal_remote handler after EOF */ + _cleanup_close_ int fdout = -1; + sd_journal *j; + OutputMode mode; + int r; + + if (size <= 2) + return 0; + + assert_se((fdin = memfd_new_and_map("fuzz-journal-remote", size, &mem)) >= 0); + memcpy(mem, data, size); + assert_se(munmap(mem, size) == 0); + + fdout = mkostemps(name, STRLEN(".journal"), O_CLOEXEC); + assert_se(fdout >= 0); + + /* In */ + + assert_se(journal_remote_server_init(&s, name, JOURNAL_WRITE_SPLIT_NONE, false, false) >= 0); + + assert_se(journal_remote_add_source(&s, fdin, (char*) "fuzz-data", false) > 0); + + while (s.active) { + r = journal_remote_handle_raw_source(NULL, fdin, 0, &s); + assert_se(r >= 0); + } + + journal_remote_server_destroy(&s); + assert_se(close(fdin) < 0 && errno == EBADF); /* Check that the fd is closed already */ + + /* Out */ + + r = sd_journal_open_files(&j, (const char**) STRV_MAKE(name), 0); + assert_se(r >= 0); + + if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0) + assert_se(dev_null = fopen("/dev/null", "we")); + + for (mode = 0; mode < _OUTPUT_MODE_MAX; mode++) { + if (!dev_null) + log_info("/* %s */", output_mode_to_string(mode)); + r = show_journal(dev_null ?: stdout, j, mode, 0, 0, -1, 0, NULL); + assert_se(r >= 0); + + r = sd_journal_seek_head(j); + assert_se(r >= 0); + } + + sd_journal_close(j); + unlink(name); + + return 0; +} diff --git a/src/fuzz/fuzz-journal-remote.options b/src/fuzz/fuzz-journal-remote.options new file mode 100644 index 0000000000..678d526b1e --- /dev/null +++ b/src/fuzz/fuzz-journal-remote.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 65536 diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build index 7fcc824069..90cd539162 100644 --- a/src/fuzz/meson.build +++ b/src/fuzz/meson.build @@ -19,4 +19,9 @@ fuzzers += [ [libcore, libshared], [libmount]], + + [['src/fuzz/fuzz-journal-remote.c'], + [libsystemd_journal_remote, + libshared], + []], ] diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index c29b65764a..a2b7fd6bd9 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -214,7 +214,7 @@ static ssize_t request_reader_entries( return MHD_CONTENT_READER_END_WITH_ERROR; } - r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, + r = show_journal_entry(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL, NULL, NULL); if (r < 0) { log_error_errno(r, "Failed to serialize item: %m"); diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c new file mode 100644 index 0000000000..8fda9d1499 --- /dev/null +++ b/src/journal-remote/journal-remote-main.c @@ -0,0 +1,1145 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <getopt.h> +#include <unistd.h> + +#include "sd-daemon.h" + +#include "conf-parser.h" +#include "def.h" +#include "fd-util.h" +#include "fileio.h" +#include "journal-remote-write.h" +#include "journal-remote.h" +#include "process-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "strv.h" + +#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem" +#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" +#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" + +static char* arg_url = NULL; +static char* arg_getter = NULL; +static char* arg_listen_raw = NULL; +static char* arg_listen_http = NULL; +static char* arg_listen_https = NULL; +static char** arg_files = NULL; +static int arg_compress = true; +static int arg_seal = false; +static int http_socket = -1, https_socket = -1; +static char** arg_gnutls_log = NULL; + +static JournalWriteSplitMode arg_split_mode = _JOURNAL_WRITE_SPLIT_INVALID; +static char* arg_output = NULL; + +static char *arg_key = NULL; +static char *arg_cert = NULL; +static char *arg_trust = NULL; +static bool arg_trust_all = false; + +static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = { + [JOURNAL_WRITE_SPLIT_NONE] = "none", + [JOURNAL_WRITE_SPLIT_HOST] = "host", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode); +static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, + journal_write_split_mode, + JournalWriteSplitMode, + "Failed to parse split mode setting"); + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int spawn_child(const char* child, char** argv) { + pid_t child_pid; + int fd[2], r; + + if (pipe(fd) < 0) + return log_error_errno(errno, "Failed to create pager pipe: %m"); + + r = safe_fork("(remote)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &child_pid); + if (r < 0) { + safe_close_pair(fd); + return r; + } + + /* In the child */ + if (r == 0) { + safe_close(fd[0]); + + r = rearrange_stdio(STDIN_FILENO, fd[1], STDERR_FILENO); + if (r < 0) { + log_error_errno(r, "Failed to dup pipe to stdout: %m"); + _exit(EXIT_FAILURE); + } + + execvp(child, argv); + log_error_errno(errno, "Failed to exec child %s: %m", child); + _exit(EXIT_FAILURE); + } + + safe_close(fd[1]); + + r = fd_nonblock(fd[0], true); + if (r < 0) + log_warning_errno(errno, "Failed to set child pipe to non-blocking: %m"); + + return fd[0]; +} + +static int spawn_curl(const char* url) { + char **argv = STRV_MAKE("curl", + "-HAccept: application/vnd.fdo.journal", + "--silent", + "--show-error", + url); + int r; + + r = spawn_child("curl", argv); + if (r < 0) + log_error_errno(r, "Failed to spawn curl: %m"); + return r; +} + +static int spawn_getter(const char *getter) { + int r; + _cleanup_strv_free_ char **words = NULL; + + assert(getter); + r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) + return log_error_errno(r, "Failed to split getter option: %m"); + + r = spawn_child(words[0], words); + if (r < 0) + log_error_errno(r, "Failed to spawn getter %s: %m", getter); + + return r; +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int null_timer_event_handler(sd_event_source *s, + uint64_t usec, + void *userdata); +static int dispatch_http_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata); + +static int request_meta(void **connection_cls, int fd, char *hostname) { + RemoteSource *source; + Writer *writer; + int r; + + assert(connection_cls); + if (*connection_cls) + return 0; + + r = journal_remote_get_writer(journal_remote_server_global, hostname, &writer); + if (r < 0) + return log_warning_errno(r, "Failed to get writer for source %s: %m", + hostname); + + source = source_new(fd, true, hostname, writer); + if (!source) { + writer_unref(writer); + return log_oom(); + } + + log_debug("Added RemoteSource as connection metadata %p", source); + + *connection_cls = source; + return 0; +} + +static void request_meta_free(void *cls, + struct MHD_Connection *connection, + void **connection_cls, + enum MHD_RequestTerminationCode toe) { + RemoteSource *s; + + assert(connection_cls); + s = *connection_cls; + + if (s) { + log_debug("Cleaning up connection metadata %p", s); + source_free(s); + *connection_cls = NULL; + } +} + +static int process_http_upload( + struct MHD_Connection *connection, + const char *upload_data, + size_t *upload_data_size, + RemoteSource *source) { + + bool finished = false; + size_t remaining; + int r; + + assert(source); + + log_trace("%s: connection %p, %zu bytes", + __func__, connection, *upload_data_size); + + if (*upload_data_size) { + log_trace("Received %zu bytes", *upload_data_size); + + r = journal_importer_push_data(&source->importer, + upload_data, *upload_data_size); + if (r < 0) + return mhd_respond_oom(connection); + + *upload_data_size = 0; + } else + finished = true; + + for (;;) { + r = process_source(source, + journal_remote_server_global->compress, + journal_remote_server_global->seal); + if (r == -EAGAIN) + break; + else if (r < 0) { + log_warning("Failed to process data for connection %p", connection); + if (r == -E2BIG) + return mhd_respondf(connection, + r, MHD_HTTP_PAYLOAD_TOO_LARGE, + "Entry is too large, maximum is " STRINGIFY(DATA_SIZE_MAX) " bytes."); + else + return mhd_respondf(connection, + r, MHD_HTTP_UNPROCESSABLE_ENTITY, + "Processing failed: %m."); + } + } + + if (!finished) + return MHD_YES; + + /* The upload is finished */ + + remaining = journal_importer_bytes_remaining(&source->importer); + if (remaining > 0) { + log_warning("Premature EOF byte. %zu bytes lost.", remaining); + return mhd_respondf(connection, + 0, MHD_HTTP_EXPECTATION_FAILED, + "Premature EOF. %zu bytes of trailing data not processed.", + remaining); + } + + return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK."); +}; + +static int request_handler( + void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **connection_cls) { + + const char *header; + int r, code, fd; + _cleanup_free_ char *hostname = NULL; + + assert(connection); + assert(connection_cls); + assert(url); + assert(method); + + log_trace("Handling a connection %s %s %s", method, url, version); + + if (*connection_cls) + return process_http_upload(connection, + upload_data, upload_data_size, + *connection_cls); + + if (!streq(method, "POST")) + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method."); + + if (!streq(url, "/upload")) + return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found."); + + header = MHD_lookup_connection_value(connection, + MHD_HEADER_KIND, "Content-Type"); + if (!header || !streq(header, "application/vnd.fdo.journal")) + return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, + "Content-Type: application/vnd.fdo.journal is required."); + + { + const union MHD_ConnectionInfo *ci; + + ci = MHD_get_connection_info(connection, + MHD_CONNECTION_INFO_CONNECTION_FD); + if (!ci) { + log_error("MHD_get_connection_info failed: cannot get remote fd"); + return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + "Cannot check remote address."); + } + + fd = ci->connect_fd; + assert(fd >= 0); + } + + if (journal_remote_server_global->check_trust) { + r = check_permissions(connection, &code, &hostname); + if (r < 0) + return code; + } else { + r = getpeername_pretty(fd, false, &hostname); + if (r < 0) + return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, + "Cannot check remote hostname."); + } + + assert(hostname); + + r = request_meta(connection_cls, fd, hostname); + if (r == -ENOMEM) + return respond_oom(connection); + else if (r < 0) + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "%m"); + + hostname = NULL; + return MHD_YES; +} + +static int setup_microhttpd_server(RemoteServer *s, + int fd, + const char *key, + const char *cert, + const char *trust) { + struct MHD_OptionItem opts[] = { + { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, + { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, + { MHD_OPTION_LISTEN_SOCKET, fd}, + { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024}, + { MHD_OPTION_END}, + { MHD_OPTION_END}, + { MHD_OPTION_END}, + { MHD_OPTION_END}, + { MHD_OPTION_END}}; + int opts_pos = 4; + int flags = + MHD_USE_DEBUG | + MHD_USE_DUAL_STACK | + MHD_USE_EPOLL | + MHD_USE_ITC; + + const union MHD_DaemonInfo *info; + int r, epoll_fd; + MHDDaemonWrapper *d; + + assert(fd >= 0); + + r = fd_nonblock(fd, true); + if (r < 0) + return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd); + +/* MHD_OPTION_STRICT_FOR_CLIENT is introduced in microhttpd 0.9.54, + * and MHD_USE_PEDANTIC_CHECKS will be deprecated in future. + * If MHD_USE_PEDANTIC_CHECKS is '#define'd, then it is deprecated + * and we should use MHD_OPTION_STRICT_FOR_CLIENT. On the other hand, + * if MHD_USE_PEDANTIC_CHECKS is not '#define'd, then it is not + * deprecated yet and there exists an enum element with the same name. + * So we can safely use it. */ +#ifdef MHD_USE_PEDANTIC_CHECKS + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_STRICT_FOR_CLIENT, 1}; +#else + flags |= MHD_USE_PEDANTIC_CHECKS; +#endif + + if (key) { + assert(cert); + + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key}; + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert}; + + flags |= MHD_USE_TLS; + + if (trust) + opts[opts_pos++] = (struct MHD_OptionItem) + {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust}; + } + + d = new(MHDDaemonWrapper, 1); + if (!d) + return log_oom(); + + d->fd = (uint64_t) fd; + + d->daemon = MHD_start_daemon(flags, 0, + NULL, NULL, + request_handler, NULL, + MHD_OPTION_ARRAY, opts, + MHD_OPTION_END); + if (!d->daemon) { + log_error("Failed to start µhttp daemon"); + r = -EINVAL; + goto error; + } + + log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", + key ? "HTTPS" : "HTTP", fd, d); + + info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); + if (!info) { + log_error("µhttp returned NULL daemon info"); + r = -EOPNOTSUPP; + goto error; + } + + epoll_fd = info->listen_fd; + if (epoll_fd < 0) { + log_error("µhttp epoll fd is invalid"); + r = -EUCLEAN; + goto error; + } + + r = sd_event_add_io(s->events, &d->io_event, + epoll_fd, EPOLLIN, + dispatch_http_event, d); + if (r < 0) { + log_error_errno(r, "Failed to add event callback: %m"); + goto error; + } + + r = sd_event_source_set_description(d->io_event, "io_event"); + if (r < 0) { + log_error_errno(r, "Failed to set source name: %m"); + goto error; + } + + r = sd_event_add_time(s->events, &d->timer_event, + CLOCK_MONOTONIC, (uint64_t) -1, 0, + null_timer_event_handler, d); + if (r < 0) { + log_error_errno(r, "Failed to add timer_event: %m"); + goto error; + } + + r = sd_event_source_set_description(d->timer_event, "timer_event"); + if (r < 0) { + log_error_errno(r, "Failed to set source name: %m"); + goto error; + } + + r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops); + if (r < 0) { + log_oom(); + goto error; + } + + r = hashmap_put(s->daemons, &d->fd, d); + if (r < 0) { + log_error_errno(r, "Failed to add daemon to hashmap: %m"); + goto error; + } + + s->active++; + return 0; + +error: + MHD_stop_daemon(d->daemon); + free(d->daemon); + free(d); + return r; +} + +static int setup_microhttpd_socket(RemoteServer *s, + const char *address, + const char *key, + const char *cert, + const char *trust) { + int fd; + + fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC); + if (fd < 0) + return fd; + + return setup_microhttpd_server(s, fd, key, cert, trust); +} + +static int null_timer_event_handler(sd_event_source *timer_event, + uint64_t usec, + void *userdata) { + return dispatch_http_event(timer_event, 0, 0, userdata); +} + +static int dispatch_http_event(sd_event_source *event, + int fd, + uint32_t revents, + void *userdata) { + MHDDaemonWrapper *d = userdata; + int r; + MHD_UNSIGNED_LONG_LONG timeout = ULONG_LONG_MAX; + + assert(d); + + r = MHD_run(d->daemon); + if (r == MHD_NO) { + log_error("MHD_run failed!"); + // XXX: unregister daemon + return -EINVAL; + } + if (MHD_get_timeout(d->daemon, &timeout) == MHD_NO) + timeout = ULONG_LONG_MAX; + + r = sd_event_source_set_time(d->timer_event, timeout); + if (r < 0) { + log_warning_errno(r, "Unable to set event loop timeout: %m, this may result in indefinite blocking!"); + return 1; + } + + r = sd_event_source_set_enabled(d->timer_event, SD_EVENT_ON); + if (r < 0) + log_warning_errno(r, "Unable to enable timer_event: %m, this may result in indefinite blocking!"); + + return 1; /* work to do */ +} + +/********************************************************************** + ********************************************************************** + **********************************************************************/ + +static int setup_signals(RemoteServer *s) { + int r; + + assert(s); + + assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); + + r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s); + if (r < 0) + return r; + + r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s); + if (r < 0) + return r; + + return 0; +} + +static int setup_raw_socket(RemoteServer *s, const char *address) { + int fd; + + fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC); + if (fd < 0) + return fd; + + return journal_remote_add_raw_socket(s, fd); +} + +static int create_remoteserver( + RemoteServer *s, + const char* key, + const char* cert, + const char* trust) { + + int r, n, fd; + char **file; + + r = journal_remote_server_init(s, arg_output, arg_split_mode, arg_compress, arg_seal); + if (r < 0) + return r; + + setup_signals(s); + + n = sd_listen_fds(true); + if (n < 0) + return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); + else + log_debug("Received %d descriptors", n); + + if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) { + log_error("Received fewer sockets than expected"); + return -EBADFD; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + if (sd_is_socket(fd, AF_UNSPEC, 0, true)) { + log_debug("Received a listening socket (fd:%d)", fd); + + if (fd == http_socket) + r = setup_microhttpd_server(s, fd, NULL, NULL, NULL); + else if (fd == https_socket) + r = setup_microhttpd_server(s, fd, key, cert, trust); + else + r = journal_remote_add_raw_socket(s, fd); + } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) { + char *hostname; + + r = getpeername_pretty(fd, false, &hostname); + if (r < 0) + return log_error_errno(r, "Failed to retrieve remote name: %m"); + + log_debug("Received a connection socket (fd:%d) from %s", fd, hostname); + + r = journal_remote_add_source(s, fd, hostname, true); + } else { + log_error("Unknown socket passed on fd:%d", fd); + + return -EINVAL; + } + + if (r < 0) + return log_error_errno(r, "Failed to register socket (fd:%d): %m", + fd); + } + + if (arg_getter) { + log_info("Spawning getter %s...", arg_getter); + fd = spawn_getter(arg_getter); + if (fd < 0) + return fd; + + r = journal_remote_add_source(s, fd, (char*) arg_output, false); + if (r < 0) + return r; + } + + if (arg_url) { + const char *url; + char *hostname, *p; + + if (!strstr(arg_url, "/entries")) { + if (endswith(arg_url, "/")) + url = strjoina(arg_url, "entries"); + else + url = strjoina(arg_url, "/entries"); + } + else + url = strdupa(arg_url); + + log_info("Spawning curl %s...", url); + fd = spawn_curl(url); + if (fd < 0) + return fd; + + hostname = + startswith(arg_url, "https://") ?: + startswith(arg_url, "http://") ?: + arg_url; + + hostname = strdupa(hostname); + if ((p = strchr(hostname, '/'))) + *p = '\0'; + if ((p = strchr(hostname, ':'))) + *p = '\0'; + + r = journal_remote_add_source(s, fd, hostname, false); + if (r < 0) + return r; + } + + if (arg_listen_raw) { + log_debug("Listening on a socket..."); + r = setup_raw_socket(s, arg_listen_raw); + if (r < 0) + return r; + } + + if (arg_listen_http) { + r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL); + if (r < 0) + return r; + } + + if (arg_listen_https) { + r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust); + if (r < 0) + return r; + } + + STRV_FOREACH(file, arg_files) { + const char *output_name; + + if (streq(*file, "-")) { + log_debug("Using standard input as source."); + + fd = STDIN_FILENO; + output_name = "stdin"; + } else { + log_debug("Reading file %s...", *file); + + fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", *file); + output_name = *file; + } + + r = journal_remote_add_source(s, fd, (char*) output_name, false); + if (r < 0) + return r; + } + + if (s->active == 0) { + log_error("Zero sources specified"); + return -EINVAL; + } + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) { + /* In this case we know what the writer will be + called, so we can create it and verify that we can + create output as expected. */ + r = journal_remote_get_writer(s, NULL, &s->_single_writer); + if (r < 0) + return r; + } + + return 0; +} + +static int negative_fd(const char *spec) { + /* Return a non-positive number as its inverse, -EINVAL otherwise. */ + + int fd, r; + + r = safe_atoi(spec, &fd); + if (r < 0) + return r; + + if (fd > 0) + return -EINVAL; + else + return -fd; +} + +static int parse_config(void) { + const ConfigTableItem items[] = { + { "Remote", "Seal", config_parse_bool, 0, &arg_seal }, + { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode }, + { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, + { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, + { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, + {}}; + + return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-remote.conf", + CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"), + "Remote\0", config_item_table_lookup, items, + CONFIG_PARSE_WARN, NULL); +} + +static void help(void) { + printf("%s [OPTIONS...] {FILE|-}...\n\n" + "Write external journal events to journal file(s).\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --url=URL Read events from systemd-journal-gatewayd at URL\n" + " --getter=COMMAND Read events from the output of COMMAND\n" + " --listen-raw=ADDR Listen for connections at ADDR\n" + " --listen-http=ADDR Listen for HTTP connections at ADDR\n" + " --listen-https=ADDR Listen for HTTPS connections at ADDR\n" + " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" + " --compress[=BOOL] XZ-compress the output journal (default: yes)\n" + " --seal[=BOOL] Use event sealing (default: no)\n" + " --key=FILENAME SSL key in PEM format (default:\n" + " \"" PRIV_KEY_FILE "\")\n" + " --cert=FILENAME SSL certificate in PEM format (default:\n" + " \"" CERT_FILE "\")\n" + " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n" + " \"" TRUST_FILE "\")\n" + " --gnutls-log=CATEGORY...\n" + " Specify a list of gnutls logging categories\n" + " --split-mode=none|host How many output files to create\n" + "\n" + "Note: file descriptors from sd_listen_fds() will be consumed, too.\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_URL, + ARG_LISTEN_RAW, + ARG_LISTEN_HTTP, + ARG_LISTEN_HTTPS, + ARG_GETTER, + ARG_SPLIT_MODE, + ARG_COMPRESS, + ARG_SEAL, + ARG_KEY, + ARG_CERT, + ARG_TRUST, + ARG_GNUTLS_LOG, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "url", required_argument, NULL, ARG_URL }, + { "getter", required_argument, NULL, ARG_GETTER }, + { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW }, + { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP }, + { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS }, + { "output", required_argument, NULL, 'o' }, + { "split-mode", required_argument, NULL, ARG_SPLIT_MODE }, + { "compress", optional_argument, NULL, ARG_COMPRESS }, + { "seal", optional_argument, NULL, ARG_SEAL }, + { "key", required_argument, NULL, ARG_KEY }, + { "cert", required_argument, NULL, ARG_CERT }, + { "trust", required_argument, NULL, ARG_TRUST }, + { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG }, + {} + }; + + int c, r; + bool type_a, type_b; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0 /* done */; + + case ARG_VERSION: + return version(); + + case ARG_URL: + if (arg_url) { + log_error("cannot currently set more than one --url"); + return -EINVAL; + } + + arg_url = optarg; + break; + + case ARG_GETTER: + if (arg_getter) { + log_error("cannot currently use --getter more than once"); + return -EINVAL; + } + + arg_getter = optarg; + break; + + case ARG_LISTEN_RAW: + if (arg_listen_raw) { + log_error("cannot currently use --listen-raw more than once"); + return -EINVAL; + } + + arg_listen_raw = optarg; + break; + + case ARG_LISTEN_HTTP: + if (arg_listen_http || http_socket >= 0) { + log_error("cannot currently use --listen-http more than once"); + return -EINVAL; + } + + r = negative_fd(optarg); + if (r >= 0) + http_socket = r; + else + arg_listen_http = optarg; + break; + + case ARG_LISTEN_HTTPS: + if (arg_listen_https || https_socket >= 0) { + log_error("cannot currently use --listen-https more than once"); + return -EINVAL; + } + + r = negative_fd(optarg); + if (r >= 0) + https_socket = r; + else + arg_listen_https = optarg; + + break; + + case ARG_KEY: + if (arg_key) { + log_error("Key file specified twice"); + return -EINVAL; + } + + arg_key = strdup(optarg); + if (!arg_key) + return log_oom(); + + break; + + case ARG_CERT: + if (arg_cert) { + log_error("Certificate file specified twice"); + return -EINVAL; + } + + arg_cert = strdup(optarg); + if (!arg_cert) + return log_oom(); + + break; + + case ARG_TRUST: + if (arg_trust || arg_trust_all) { + log_error("Confusing trusted CA configuration"); + return -EINVAL; + } + + if (streq(optarg, "all")) + arg_trust_all = true; + else { +#if HAVE_GNUTLS + arg_trust = strdup(optarg); + if (!arg_trust) + return log_oom(); +#else + log_error("Option --trust is not available."); + return -EINVAL; +#endif + } + + break; + + case 'o': + if (arg_output) { + log_error("cannot use --output/-o more than once"); + return -EINVAL; + } + + arg_output = optarg; + break; + + case ARG_SPLIT_MODE: + arg_split_mode = journal_write_split_mode_from_string(optarg); + if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) { + log_error("Invalid split mode: %s", optarg); + return -EINVAL; + } + break; + + case ARG_COMPRESS: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --compress= parameter."); + return -EINVAL; + } + + arg_compress = !!r; + } else + arg_compress = true; + + break; + + case ARG_SEAL: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --seal= parameter."); + return -EINVAL; + } + + arg_seal = !!r; + } else + arg_seal = true; + + break; + + case ARG_GNUTLS_LOG: { +#if HAVE_GNUTLS + const char* p = optarg; + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m"); + + if (r == 0) + break; + + if (strv_push(&arg_gnutls_log, word) < 0) + return log_oom(); + + word = NULL; + } + break; +#else + log_error("Option --gnutls-log is not available."); + return -EINVAL; +#endif + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unknown option code."); + } + + if (optind < argc) + arg_files = argv + optind; + + type_a = arg_getter || !strv_isempty(arg_files); + type_b = arg_url + || arg_listen_raw + || arg_listen_http || arg_listen_https + || sd_listen_fds(false) > 0; + if (type_a && type_b) { + log_error("Cannot use file input or --getter with " + "--arg-listen-... or socket activation."); + return -EINVAL; + } + if (type_a) { + if (!arg_output) { + log_error("Option --output must be specified with file input or --getter."); + return -EINVAL; + } + + if (!IN_SET(arg_split_mode, JOURNAL_WRITE_SPLIT_NONE, _JOURNAL_WRITE_SPLIT_INVALID)) { + log_error("For active sources, only --split-mode=none is allowed."); + return -EINVAL; + } + + arg_split_mode = JOURNAL_WRITE_SPLIT_NONE; + } + + if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) + arg_split_mode = JOURNAL_WRITE_SPLIT_HOST; + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE && arg_output) { + if (is_dir(arg_output, true) > 0) { + log_error("For SplitMode=none, output must be a file."); + return -EINVAL; + } + if (!endswith(arg_output, ".journal")) { + log_error("For SplitMode=none, output file name must end with .journal."); + return -EINVAL; + } + } + + if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST + && arg_output && is_dir(arg_output, true) <= 0) { + log_error("For SplitMode=host, output must be a directory."); + return -EINVAL; + } + + log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s", + journal_write_split_mode_to_string(arg_split_mode), + strna(arg_key), + strna(arg_cert), + strna(arg_trust)); + + return 1 /* work to do */; +} + +static int load_certificates(char **key, char **cert, char **trust) { + int r; + + r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read key from file '%s': %m", + arg_key ?: PRIV_KEY_FILE); + + r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read certificate from file '%s': %m", + arg_cert ?: CERT_FILE); + + if (arg_trust_all) + log_info("Certificate checking disabled."); + else { + r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read CA certificate file '%s': %m", + arg_trust ?: TRUST_FILE); + } + + if ((arg_listen_raw || arg_listen_http) && *trust) { + log_error("Option --trust makes all non-HTTPS connections untrusted."); + return -EINVAL; + } + + return 0; +} + +int main(int argc, char **argv) { + RemoteServer s = {}; + int r; + _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL; + + log_show_color(true); + log_parse_environment(); + + r = parse_config(); + if (r < 0) + return EXIT_FAILURE; + + r = parse_argv(argc, argv); + if (r <= 0) + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + + if (arg_listen_http || arg_listen_https) { + r = setup_gnutls_logger(arg_gnutls_log); + if (r < 0) + return EXIT_FAILURE; + } + + if (arg_listen_https || https_socket >= 0) + if (load_certificates(&key, &cert, &trust) < 0) + return EXIT_FAILURE; + + if (create_remoteserver(&s, key, cert, trust) < 0) + return EXIT_FAILURE; + + r = sd_event_set_watchdog(s.events, true); + if (r < 0) + log_error_errno(r, "Failed to enable watchdog: %m"); + else + log_debug("Watchdog is %sd.", enable_disable(r > 0)); + + log_debug("%s running as pid "PID_FMT, + program_invocation_short_name, getpid_cached()); + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + while (s.active) { + r = sd_event_get_state(s.events); + if (r < 0) + break; + if (r == SD_EVENT_FINISHED) + break; + + r = sd_event_run(s.events, -1); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + break; + } + } + + sd_notifyf(false, + "STOPPING=1\n" + "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count); + log_info("Finishing after writing %" PRIu64 " entries", s.event_count); + + journal_remote_server_destroy(&s); + + free(arg_key); + free(arg_cert); + free(arg_trust); + + return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c index 6a194e7f9f..645bd7b4cf 100644 --- a/src/journal-remote/journal-remote-parse.c +++ b/src/journal-remote/journal-remote-parse.c @@ -32,7 +32,6 @@ void source_free(RemoteSource *source) { * ownership of fd, name, and writer, otherwise does not touch them. */ RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) { - RemoteSource *source; log_debug("Creating source for %sfd:%d (%s)", @@ -75,7 +74,10 @@ int process_source(RemoteSource *source, bool compress, bool seal) { assert(source->importer.iovw.iovec); r = writer_write(source->writer, &source->importer.iovw, &source->importer.ts, compress, seal); - if (r < 0) + if (r == -EBADMSG) { + log_error_errno(r, "Entry is invalid, ignoring."); + r = 0; + } else if (r < 0) log_error_errno(r, "Failed to write entry of %zu bytes: %m", iovw_size(&source->importer.iovw)); else diff --git a/src/journal-remote/journal-remote-write.c b/src/journal-remote/journal-remote-write.c index 1f88ee9562..949fdca372 100644 --- a/src/journal-remote/journal-remote-write.c +++ b/src/journal-remote/journal-remote-write.c @@ -92,13 +92,15 @@ int writer_write(Writer *w, return r; } - r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, + r = journal_file_append_entry(w->journal, ts, NULL, + iovw->iovec, iovw->count, &w->seqnum, NULL, NULL); if (r >= 0) { if (w->server) w->server->event_count += 1; - return 1; - } + return 0; + } else if (r == -EBADMSG) + return r; log_debug_errno(r, "%s: Write failed, rotating: %m", w->journal->path); r = do_rotate(&w->journal, compress, seal); @@ -108,12 +110,13 @@ int writer_write(Writer *w, log_debug("%s: Successfully rotated journal", w->journal->path); log_debug("Retrying write."); - r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count, + r = journal_file_append_entry(w->journal, ts, NULL, + iovw->iovec, iovw->count, &w->seqnum, NULL, NULL); if (r < 0) return r; if (w->server) w->server->event_count += 1; - return 1; + return 0; } diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 81f30748e7..e1810bec19 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -7,23 +7,19 @@ #include <errno.h> #include <fcntl.h> -#include <getopt.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/prctl.h> #include <sys/socket.h> -#include <unistd.h> #include <stdint.h> #include "sd-daemon.h" #include "alloc-util.h" -#include "conf-parser.h" #include "def.h" #include "escape.h" #include "fd-util.h" -#include "fileio.h" #include "journal-file.h" #include "journal-remote-write.h" #include "journal-remote.h" @@ -31,120 +27,23 @@ #include "macro.h" #include "parse-util.h" #include "process-util.h" -#include "signal-util.h" #include "socket-util.h" -#include "stat-util.h" #include "stdio-util.h" -#include "string-table.h" #include "string-util.h" #include "strv.h" #define REMOTE_JOURNAL_PATH "/var/log/journal/remote" -#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem" -#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" -#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" - -static char* arg_url = NULL; -static char* arg_getter = NULL; -static char* arg_listen_raw = NULL; -static char* arg_listen_http = NULL; -static char* arg_listen_https = NULL; -static char** arg_files = NULL; -static int arg_compress = true; -static int arg_seal = false; -static int http_socket = -1, https_socket = -1; -static char** arg_gnutls_log = NULL; - -static JournalWriteSplitMode arg_split_mode = _JOURNAL_WRITE_SPLIT_INVALID; -static char* arg_output = NULL; - -static char *arg_key = NULL; -static char *arg_cert = NULL; -static char *arg_trust = NULL; -static bool arg_trust_all = false; - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int spawn_child(const char* child, char** argv) { - pid_t child_pid; - int fd[2], r; - - if (pipe(fd) < 0) - return log_error_errno(errno, "Failed to create pager pipe: %m"); - - r = safe_fork("(remote)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &child_pid); - if (r < 0) { - safe_close_pair(fd); - return r; - } - - /* In the child */ - if (r == 0) { - safe_close(fd[0]); - - r = rearrange_stdio(STDIN_FILENO, fd[1], STDERR_FILENO); - if (r < 0) { - log_error_errno(r, "Failed to dup pipe to stdout: %m"); - _exit(EXIT_FAILURE); - } - - execvp(child, argv); - log_error_errno(errno, "Failed to exec child %s: %m", child); - _exit(EXIT_FAILURE); - } - - safe_close(fd[1]); - - r = fd_nonblock(fd[0], true); - if (r < 0) - log_warning_errno(errno, "Failed to set child pipe to non-blocking: %m"); - - return fd[0]; -} - -static int spawn_curl(const char* url) { - char **argv = STRV_MAKE("curl", - "-HAccept: application/vnd.fdo.journal", - "--silent", - "--show-error", - url); - int r; - - r = spawn_child("curl", argv); - if (r < 0) - log_error_errno(r, "Failed to spawn curl: %m"); - return r; -} - -static int spawn_getter(const char *getter) { - int r; - _cleanup_strv_free_ char **words = NULL; - - assert(getter); - r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) - return log_error_errno(r, "Failed to split getter option: %m"); - - r = spawn_child(words[0], words); - if (r < 0) - log_error_errno(r, "Failed to spawn getter %s: %m", getter); - - return r; -} - #define filename_escape(s) xescape((s), "/ ") -static int open_output(Writer *w, const char* host) { - _cleanup_free_ char *_output = NULL; - const char *output; +static int open_output(RemoteServer *s, Writer *w, const char* host) { + _cleanup_free_ char *_filename = NULL; + const char *filename; int r; - switch (arg_split_mode) { + switch (s->split_mode) { case JOURNAL_WRITE_SPLIT_NONE: - output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal"; + filename = s->output; break; case JOURNAL_WRITE_SPLIT_HOST: { @@ -156,13 +55,11 @@ static int open_output(Writer *w, const char* host) { if (!name) return log_oom(); - r = asprintf(&_output, "%s/remote-%s.journal", - arg_output ?: REMOTE_JOURNAL_PATH, - name); + r = asprintf(&_filename, "%s/remote-%s.journal", s->output, name); if (r < 0) return log_oom(); - output = _output; + filename = _filename; break; } @@ -170,18 +67,17 @@ static int open_output(Writer *w, const char* host) { assert_not_reached("what?"); } - r = journal_file_open_reliably(output, + r = journal_file_open_reliably(filename, O_RDWR|O_CREAT, 0640, - arg_compress, (uint64_t) -1, arg_seal, + s->compress, (uint64_t) -1, s->seal, &w->metrics, w->mmap, NULL, NULL, &w->journal); if (r < 0) - log_error_errno(r, "Failed to open output journal %s: %m", - output); - else - log_debug("Opened output file %s", w->journal->path); - return r; + return log_error_errno(r, "Failed to open output journal %s: %m", filename); + + log_debug("Opened output file %s", w->journal->path); + return 0; } /********************************************************************** @@ -189,26 +85,27 @@ static int open_output(Writer *w, const char* host) { **********************************************************************/ static int init_writer_hashmap(RemoteServer *s) { - static const struct hash_ops *hash_ops[] = { + static const struct hash_ops* const hash_ops[] = { [JOURNAL_WRITE_SPLIT_NONE] = NULL, [JOURNAL_WRITE_SPLIT_HOST] = &string_hash_ops, }; - assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(hash_ops)); + assert(s); + assert(s->split_mode >= 0 && s->split_mode < (int) ELEMENTSOF(hash_ops)); - s->writers = hashmap_new(hash_ops[arg_split_mode]); + s->writers = hashmap_new(hash_ops[s->split_mode]); if (!s->writers) return log_oom(); return 0; } -static int get_writer(RemoteServer *s, const char *host, Writer **writer) { +int journal_remote_get_writer(RemoteServer *s, const char *host, Writer **writer) { _cleanup_(writer_unrefp) Writer *w = NULL; const void *key; int r; - switch(arg_split_mode) { + switch(s->split_mode) { case JOURNAL_WRITE_SPLIT_NONE: key = "one and only"; break; @@ -230,13 +127,13 @@ static int get_writer(RemoteServer *s, const char *host, Writer **writer) { if (!w) return log_oom(); - if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) { + if (s->split_mode == JOURNAL_WRITE_SPLIT_HOST) { w->hashmap_key = strdup(key); if (!w->hashmap_key) return log_oom(); } - r = open_output(w, host); + r = open_output(s, w, host); if (r < 0) return r; @@ -255,7 +152,7 @@ static int get_writer(RemoteServer *s, const char *host, Writer **writer) { **********************************************************************/ /* This should go away as soon as µhttpd allows state to be passed around. */ -static RemoteServer *server; +RemoteServer *journal_remote_server_global; static int dispatch_raw_source_event(sd_event_source *event, int fd, @@ -269,13 +166,6 @@ static int dispatch_raw_connection_event(sd_event_source *event, int fd, uint32_t revents, void *userdata); -static int null_timer_event_handler(sd_event_source *s, - uint64_t usec, - void *userdata); -static int dispatch_http_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata); static int get_source_for_fd(RemoteServer *s, int fd, char *name, RemoteSource **source) { @@ -290,7 +180,7 @@ static int get_source_for_fd(RemoteServer *s, if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1)) return log_oom(); - r = get_writer(s, name, &writer); + r = journal_remote_get_writer(s, name, &writer); if (r < 0) return log_warning_errno(r, "Failed to get writer for source %s: %m", name); @@ -326,8 +216,7 @@ static int remove_source(RemoteServer *s, int fd) { return 0; } -static int add_source(RemoteServer *s, int fd, char* name, bool own_name) { - +int journal_remote_add_source(RemoteServer *s, int fd, char* name, bool own_name) { RemoteSource *source = NULL; int r; @@ -387,7 +276,7 @@ static int add_source(RemoteServer *s, int fd, char* name, bool own_name) { return r; } -static int add_raw_socket(RemoteServer *s, int fd) { +int journal_remote_add_raw_socket(RemoteServer *s, int fd) { int r; _cleanup_close_ int fd_ = fd; char name[STRLEN("raw-socket-") + DECIMAL_STR_MAX(int) + 1]; @@ -411,616 +300,63 @@ static int add_raw_socket(RemoteServer *s, int fd) { return 0; } -static int setup_raw_socket(RemoteServer *s, const char *address) { - int fd; - - fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC); - if (fd < 0) - return fd; - - return add_raw_socket(s, fd); -} - /********************************************************************** ********************************************************************** **********************************************************************/ -static int request_meta(void **connection_cls, int fd, char *hostname) { - RemoteSource *source; - Writer *writer; - int r; - - assert(connection_cls); - if (*connection_cls) - return 0; - - r = get_writer(server, hostname, &writer); - if (r < 0) - return log_warning_errno(r, "Failed to get writer for source %s: %m", - hostname); - - source = source_new(fd, true, hostname, writer); - if (!source) { - writer_unref(writer); - return log_oom(); - } - - log_debug("Added RemoteSource as connection metadata %p", source); - - *connection_cls = source; - return 0; -} - -static void request_meta_free(void *cls, - struct MHD_Connection *connection, - void **connection_cls, - enum MHD_RequestTerminationCode toe) { - RemoteSource *s; - - assert(connection_cls); - s = *connection_cls; - - if (s) { - log_debug("Cleaning up connection metadata %p", s); - source_free(s); - *connection_cls = NULL; - } -} - -static int process_http_upload( - struct MHD_Connection *connection, - const char *upload_data, - size_t *upload_data_size, - RemoteSource *source) { - - bool finished = false; - size_t remaining; - int r; - - assert(source); - - log_trace("%s: connection %p, %zu bytes", - __func__, connection, *upload_data_size); - - if (*upload_data_size) { - log_trace("Received %zu bytes", *upload_data_size); - - r = journal_importer_push_data(&source->importer, - upload_data, *upload_data_size); - if (r < 0) - return mhd_respond_oom(connection); - - *upload_data_size = 0; - } else - finished = true; - - for (;;) { - r = process_source(source, arg_compress, arg_seal); - if (r == -EAGAIN) - break; - else if (r < 0) { - log_warning("Failed to process data for connection %p", connection); - if (r == -E2BIG) - return mhd_respondf(connection, - r, MHD_HTTP_PAYLOAD_TOO_LARGE, - "Entry is too large, maximum is " STRINGIFY(DATA_SIZE_MAX) " bytes."); - else - return mhd_respondf(connection, - r, MHD_HTTP_UNPROCESSABLE_ENTITY, - "Processing failed: %m."); - } - } - - if (!finished) - return MHD_YES; - - /* The upload is finished */ - - remaining = journal_importer_bytes_remaining(&source->importer); - if (remaining > 0) { - log_warning("Premature EOF byte. %zu bytes lost.", remaining); - return mhd_respondf(connection, - 0, MHD_HTTP_EXPECTATION_FAILED, - "Premature EOF. %zu bytes of trailing data not processed.", - remaining); - } - - return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK."); -}; - -static int request_handler( - void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **connection_cls) { - - const char *header; - int r, code, fd; - _cleanup_free_ char *hostname = NULL; - - assert(connection); - assert(connection_cls); - assert(url); - assert(method); - - log_trace("Handling a connection %s %s %s", method, url, version); - - if (*connection_cls) - return process_http_upload(connection, - upload_data, upload_data_size, - *connection_cls); - - if (!streq(method, "POST")) - return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method."); - - if (!streq(url, "/upload")) - return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found."); - - header = MHD_lookup_connection_value(connection, - MHD_HEADER_KIND, "Content-Type"); - if (!header || !streq(header, "application/vnd.fdo.journal")) - return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE, - "Content-Type: application/vnd.fdo.journal is required."); - - { - const union MHD_ConnectionInfo *ci; - - ci = MHD_get_connection_info(connection, - MHD_CONNECTION_INFO_CONNECTION_FD); - if (!ci) { - log_error("MHD_get_connection_info failed: cannot get remote fd"); - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "Cannot check remote address."); - } - - fd = ci->connect_fd; - assert(fd >= 0); - } - - if (server->check_trust) { - r = check_permissions(connection, &code, &hostname); - if (r < 0) - return code; - } else { - r = getpeername_pretty(fd, false, &hostname); - if (r < 0) - return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "Cannot check remote hostname."); - } - - assert(hostname); - - r = request_meta(connection_cls, fd, hostname); - if (r == -ENOMEM) - return respond_oom(connection); - else if (r < 0) - return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "%m"); - - hostname = NULL; - return MHD_YES; -} - -static int setup_microhttpd_server(RemoteServer *s, - int fd, - const char *key, - const char *cert, - const char *trust) { - struct MHD_OptionItem opts[] = { - { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, - { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, - { MHD_OPTION_LISTEN_SOCKET, fd}, - { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024}, - { MHD_OPTION_END}, - { MHD_OPTION_END}, - { MHD_OPTION_END}, - { MHD_OPTION_END}, - { MHD_OPTION_END}}; - int opts_pos = 4; - int flags = - MHD_USE_DEBUG | - MHD_USE_DUAL_STACK | - MHD_USE_EPOLL | - MHD_USE_ITC; - - const union MHD_DaemonInfo *info; - int r, epoll_fd; - MHDDaemonWrapper *d; - - assert(fd >= 0); - - r = fd_nonblock(fd, true); - if (r < 0) - return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd); - -/* MHD_OPTION_STRICT_FOR_CLIENT is introduced in microhttpd 0.9.54, - * and MHD_USE_PEDANTIC_CHECKS will be deprecated in future. - * If MHD_USE_PEDANTIC_CHECKS is '#define'd, then it is deprecated - * and we should use MHD_OPTION_STRICT_FOR_CLIENT. On the other hand, - * if MHD_USE_PEDANTIC_CHECKS is not '#define'd, then it is not - * deprecated yet and there exists an enum element with the same name. - * So we can safely use it. */ -#ifdef MHD_USE_PEDANTIC_CHECKS - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_STRICT_FOR_CLIENT, 1}; -#else - flags |= MHD_USE_PEDANTIC_CHECKS; -#endif - - if (key) { - assert(cert); - - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key}; - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert}; - - flags |= MHD_USE_TLS; - - if (trust) - opts[opts_pos++] = (struct MHD_OptionItem) - {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust}; - } - - d = new(MHDDaemonWrapper, 1); - if (!d) - return log_oom(); - - d->fd = (uint64_t) fd; - - d->daemon = MHD_start_daemon(flags, 0, - NULL, NULL, - request_handler, NULL, - MHD_OPTION_ARRAY, opts, - MHD_OPTION_END); - if (!d->daemon) { - log_error("Failed to start µhttp daemon"); - r = -EINVAL; - goto error; - } - - log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)", - key ? "HTTPS" : "HTTP", fd, d); - - info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY); - if (!info) { - log_error("µhttp returned NULL daemon info"); - r = -EOPNOTSUPP; - goto error; - } - - epoll_fd = info->listen_fd; - if (epoll_fd < 0) { - log_error("µhttp epoll fd is invalid"); - r = -EUCLEAN; - goto error; - } - - r = sd_event_add_io(s->events, &d->io_event, - epoll_fd, EPOLLIN, - dispatch_http_event, d); - if (r < 0) { - log_error_errno(r, "Failed to add event callback: %m"); - goto error; - } - - r = sd_event_source_set_description(d->io_event, "io_event"); - if (r < 0) { - log_error_errno(r, "Failed to set source name: %m"); - goto error; - } - - r = sd_event_add_time(s->events, &d->timer_event, - CLOCK_MONOTONIC, (uint64_t) -1, 0, - null_timer_event_handler, d); - if (r < 0) { - log_error_errno(r, "Failed to add timer_event: %m"); - goto error; - } - - r = sd_event_source_set_description(d->timer_event, "timer_event"); - if (r < 0) { - log_error_errno(r, "Failed to set source name: %m"); - goto error; - } - - r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops); - if (r < 0) { - log_oom(); - goto error; - } - - r = hashmap_put(s->daemons, &d->fd, d); - if (r < 0) { - log_error_errno(r, "Failed to add daemon to hashmap: %m"); - goto error; - } - - s->active++; - return 0; - -error: - MHD_stop_daemon(d->daemon); - free(d->daemon); - free(d); - return r; -} - -static int setup_microhttpd_socket(RemoteServer *s, - const char *address, - const char *key, - const char *cert, - const char *trust) { - int fd; - - fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC); - if (fd < 0) - return fd; - - return setup_microhttpd_server(s, fd, key, cert, trust); -} - -static int null_timer_event_handler(sd_event_source *timer_event, - uint64_t usec, - void *userdata) { - return dispatch_http_event(timer_event, 0, 0, userdata); -} - -static int dispatch_http_event(sd_event_source *event, - int fd, - uint32_t revents, - void *userdata) { - MHDDaemonWrapper *d = userdata; - int r; - MHD_UNSIGNED_LONG_LONG timeout = ULONG_LONG_MAX; - - assert(d); - - r = MHD_run(d->daemon); - if (r == MHD_NO) { - log_error("MHD_run failed!"); - // XXX: unregister daemon - return -EINVAL; - } - if (MHD_get_timeout(d->daemon, &timeout) == MHD_NO) - timeout = ULONG_LONG_MAX; - - r = sd_event_source_set_time(d->timer_event, timeout); - if (r < 0) { - log_warning_errno(r, "Unable to set event loop timeout: %m, this may result in indefinite blocking!"); - return 1; - } - - r = sd_event_source_set_enabled(d->timer_event, SD_EVENT_ON); - if (r < 0) - log_warning_errno(r, "Unable to enable timer_event: %m, this may result in indefinite blocking!"); - - return 1; /* work to do */ -} +int journal_remote_server_init( + RemoteServer *s, + const char *output, + JournalWriteSplitMode split_mode, + bool compress, + bool seal) { -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static int setup_signals(RemoteServer *s) { int r; assert(s); - assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); - - r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s); - if (r < 0) - return r; - - r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s); - if (r < 0) - return r; - - return 0; -} + assert(journal_remote_server_global == NULL); + journal_remote_server_global = s; -static int negative_fd(const char *spec) { - /* Return a non-positive number as its inverse, -EINVAL otherwise. */ + s->split_mode = split_mode; + s->compress = compress; + s->seal = seal; - int fd, r; - - r = safe_atoi(spec, &fd); - if (r < 0) - return r; - - if (fd > 0) - return -EINVAL; + if (output) + s->output = output; + else if (split_mode == JOURNAL_WRITE_SPLIT_NONE) + s->output = REMOTE_JOURNAL_PATH "/remote.journal"; + else if (split_mode == JOURNAL_WRITE_SPLIT_HOST) + s->output = REMOTE_JOURNAL_PATH; else - return -fd; -} - -static int remoteserver_init(RemoteServer *s, - const char* key, - const char* cert, - const char* trust) { - int r, n, fd; - char **file; - - assert(s); - - if ((arg_listen_raw || arg_listen_http) && trust) { - log_error("Option --trust makes all non-HTTPS connections untrusted."); - return -EINVAL; - } + assert_not_reached("bad split mode"); r = sd_event_default(&s->events); if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); - setup_signals(s); - - assert(server == NULL); - server = s; - r = init_writer_hashmap(s); if (r < 0) return r; - n = sd_listen_fds(true); - if (n < 0) - return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); - else - log_debug("Received %d descriptors", n); - - if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) { - log_error("Received fewer sockets than expected"); - return -EBADFD; - } - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { - if (sd_is_socket(fd, AF_UNSPEC, 0, true)) { - log_debug("Received a listening socket (fd:%d)", fd); - - if (fd == http_socket) - r = setup_microhttpd_server(s, fd, NULL, NULL, NULL); - else if (fd == https_socket) - r = setup_microhttpd_server(s, fd, key, cert, trust); - else - r = add_raw_socket(s, fd); - } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) { - char *hostname; - - r = getpeername_pretty(fd, false, &hostname); - if (r < 0) - return log_error_errno(r, "Failed to retrieve remote name: %m"); - - log_debug("Received a connection socket (fd:%d) from %s", fd, hostname); - - r = add_source(s, fd, hostname, true); - } else { - log_error("Unknown socket passed on fd:%d", fd); - - return -EINVAL; - } - - if (r < 0) - return log_error_errno(r, "Failed to register socket (fd:%d): %m", - fd); - } - - if (arg_getter) { - log_info("Spawning getter %s...", arg_getter); - fd = spawn_getter(arg_getter); - if (fd < 0) - return fd; - - r = add_source(s, fd, (char*) arg_output, false); - if (r < 0) - return r; - } - - if (arg_url) { - const char *url; - char *hostname, *p; - - if (!strstr(arg_url, "/entries")) { - if (endswith(arg_url, "/")) - url = strjoina(arg_url, "entries"); - else - url = strjoina(arg_url, "/entries"); - } - else - url = strdupa(arg_url); - - log_info("Spawning curl %s...", url); - fd = spawn_curl(url); - if (fd < 0) - return fd; - - hostname = - startswith(arg_url, "https://") ?: - startswith(arg_url, "http://") ?: - arg_url; - - hostname = strdupa(hostname); - if ((p = strchr(hostname, '/'))) - *p = '\0'; - if ((p = strchr(hostname, ':'))) - *p = '\0'; - - r = add_source(s, fd, hostname, false); - if (r < 0) - return r; - } - - if (arg_listen_raw) { - log_debug("Listening on a socket..."); - r = setup_raw_socket(s, arg_listen_raw); - if (r < 0) - return r; - } - - if (arg_listen_http) { - r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL); - if (r < 0) - return r; - } - - if (arg_listen_https) { - r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust); - if (r < 0) - return r; - } - - STRV_FOREACH(file, arg_files) { - const char *output_name; - - if (streq(*file, "-")) { - log_debug("Using standard input as source."); - - fd = STDIN_FILENO; - output_name = "stdin"; - } else { - log_debug("Reading file %s...", *file); - - fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", *file); - output_name = *file; - } - - r = add_source(s, fd, (char*) output_name, false); - if (r < 0) - return r; - } - - if (s->active == 0) { - log_error("Zero sources specified"); - return -EINVAL; - } - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) { - /* In this case we know what the writer will be - called, so we can create it and verify that we can - create output as expected. */ - r = get_writer(s, NULL, &s->_single_writer); - if (r < 0) - return r; - } - return 0; } +#if HAVE_MICROHTTPD static void MHDDaemonWrapper_free(MHDDaemonWrapper *d) { MHD_stop_daemon(d->daemon); sd_event_source_unref(d->io_event); sd_event_source_unref(d->timer_event); free(d); } +#endif -static void server_destroy(RemoteServer *s) { +RemoteServer* journal_remote_server_destroy(RemoteServer *s) { size_t i; +#if HAVE_MICROHTTPD hashmap_free_with_destructor(s->daemons, MHDDaemonWrapper_free); +#endif assert(s->sources_size == 0 || s->sources); for (i = 0; i < s->sources_size; i++) @@ -1035,17 +371,22 @@ static void server_destroy(RemoteServer *s) { sd_event_source_unref(s->listen_event); sd_event_unref(s->events); + if (s == journal_remote_server_global) + journal_remote_server_global = NULL; + /* fds that we're listening on remain open... */ + return NULL; } /********************************************************************** ********************************************************************** **********************************************************************/ -static int handle_raw_source(sd_event_source *event, - int fd, - uint32_t revents, - RemoteServer *s) { +int journal_remote_handle_raw_source( + sd_event_source *event, + int fd, + uint32_t revents, + RemoteServer *s) { RemoteSource *source; int r; @@ -1058,7 +399,7 @@ static int handle_raw_source(sd_event_source *event, source = s->sources[fd]; assert(source->importer.fd == fd); - r = process_source(source, arg_compress, arg_seal); + r = process_source(source, s->compress, s->seal); if (journal_importer_eof(&source->importer)) { size_t remaining; @@ -1078,7 +419,7 @@ static int handle_raw_source(sd_event_source *event, return 0; } else if (r < 0) { log_debug_errno(r, "Closing connection: %m"); - remove_source(server, fd); + remove_source(s, fd); return 0; } else return 1; @@ -1092,7 +433,7 @@ static int dispatch_raw_source_until_block(sd_event_source *event, /* Make sure event stays around even if source is destroyed */ sd_event_source_ref(event); - r = handle_raw_source(event, source->importer.fd, EPOLLIN, server); + r = journal_remote_handle_raw_source(event, source->importer.fd, EPOLLIN, journal_remote_server_global); if (r != 1) /* No more data for now */ sd_event_source_set_enabled(event, SD_EVENT_OFF); @@ -1112,7 +453,7 @@ static int dispatch_raw_source_event(sd_event_source *event, assert(source->event); assert(source->buffer_event); - r = handle_raw_source(event, fd, EPOLLIN, server); + r = journal_remote_handle_raw_source(event, fd, EPOLLIN, journal_remote_server_global); if (r == 1) /* Might have more data. We need to rerun the handler * until we are sure the buffer is exhausted. */ @@ -1125,7 +466,7 @@ static int dispatch_blocking_source_event(sd_event_source *event, void *userdata) { RemoteSource *source = userdata; - return handle_raw_source(event, source->importer.fd, EPOLLIN, server); + return journal_remote_handle_raw_source(event, source->importer.fd, EPOLLIN, journal_remote_server_global); } static int accept_connection(const char* type, int fd, @@ -1191,439 +532,5 @@ static int dispatch_raw_connection_event(sd_event_source *event, if (fd2 < 0) return fd2; - return add_source(s, fd2, hostname, true); -} - -/********************************************************************** - ********************************************************************** - **********************************************************************/ - -static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = { - [JOURNAL_WRITE_SPLIT_NONE] = "none", - [JOURNAL_WRITE_SPLIT_HOST] = "host", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode); -static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, - journal_write_split_mode, - JournalWriteSplitMode, - "Failed to parse split mode setting"); - -static int parse_config(void) { - const ConfigTableItem items[] = { - { "Remote", "Seal", config_parse_bool, 0, &arg_seal }, - { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode }, - { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, - { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, - { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust }, - {}}; - - return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-remote.conf", - CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"), - "Remote\0", config_item_table_lookup, items, - CONFIG_PARSE_WARN, NULL); -} - -static void help(void) { - printf("%s [OPTIONS...] {FILE|-}...\n\n" - "Write external journal events to journal file(s).\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --url=URL Read events from systemd-journal-gatewayd at URL\n" - " --getter=COMMAND Read events from the output of COMMAND\n" - " --listen-raw=ADDR Listen for connections at ADDR\n" - " --listen-http=ADDR Listen for HTTP connections at ADDR\n" - " --listen-https=ADDR Listen for HTTPS connections at ADDR\n" - " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n" - " --compress[=BOOL] XZ-compress the output journal (default: yes)\n" - " --seal[=BOOL] Use event sealing (default: no)\n" - " --key=FILENAME SSL key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME SSL certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --gnutls-log=CATEGORY...\n" - " Specify a list of gnutls logging categories\n" - " --split-mode=none|host How many output files to create\n" - "\n" - "Note: file descriptors from sd_listen_fds() will be consumed, too.\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_URL, - ARG_LISTEN_RAW, - ARG_LISTEN_HTTP, - ARG_LISTEN_HTTPS, - ARG_GETTER, - ARG_SPLIT_MODE, - ARG_COMPRESS, - ARG_SEAL, - ARG_KEY, - ARG_CERT, - ARG_TRUST, - ARG_GNUTLS_LOG, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, ARG_URL }, - { "getter", required_argument, NULL, ARG_GETTER }, - { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW }, - { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP }, - { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS }, - { "output", required_argument, NULL, 'o' }, - { "split-mode", required_argument, NULL, ARG_SPLIT_MODE }, - { "compress", optional_argument, NULL, ARG_COMPRESS }, - { "seal", optional_argument, NULL, ARG_SEAL }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG }, - {} - }; - - int c, r; - bool type_a, type_b; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) - switch(c) { - case 'h': - help(); - return 0 /* done */; - - case ARG_VERSION: - return version(); - - case ARG_URL: - if (arg_url) { - log_error("cannot currently set more than one --url"); - return -EINVAL; - } - - arg_url = optarg; - break; - - case ARG_GETTER: - if (arg_getter) { - log_error("cannot currently use --getter more than once"); - return -EINVAL; - } - - arg_getter = optarg; - break; - - case ARG_LISTEN_RAW: - if (arg_listen_raw) { - log_error("cannot currently use --listen-raw more than once"); - return -EINVAL; - } - - arg_listen_raw = optarg; - break; - - case ARG_LISTEN_HTTP: - if (arg_listen_http || http_socket >= 0) { - log_error("cannot currently use --listen-http more than once"); - return -EINVAL; - } - - r = negative_fd(optarg); - if (r >= 0) - http_socket = r; - else - arg_listen_http = optarg; - break; - - case ARG_LISTEN_HTTPS: - if (arg_listen_https || https_socket >= 0) { - log_error("cannot currently use --listen-https more than once"); - return -EINVAL; - } - - r = negative_fd(optarg); - if (r >= 0) - https_socket = r; - else - arg_listen_https = optarg; - - break; - - case ARG_KEY: - if (arg_key) { - log_error("Key file specified twice"); - return -EINVAL; - } - - arg_key = strdup(optarg); - if (!arg_key) - return log_oom(); - - break; - - case ARG_CERT: - if (arg_cert) { - log_error("Certificate file specified twice"); - return -EINVAL; - } - - arg_cert = strdup(optarg); - if (!arg_cert) - return log_oom(); - - break; - - case ARG_TRUST: - if (arg_trust || arg_trust_all) { - log_error("Confusing trusted CA configuration"); - return -EINVAL; - } - - if (streq(optarg, "all")) - arg_trust_all = true; - else { -#if HAVE_GNUTLS - arg_trust = strdup(optarg); - if (!arg_trust) - return log_oom(); -#else - log_error("Option --trust is not available."); - return -EINVAL; -#endif - } - - break; - - case 'o': - if (arg_output) { - log_error("cannot use --output/-o more than once"); - return -EINVAL; - } - - arg_output = optarg; - break; - - case ARG_SPLIT_MODE: - arg_split_mode = journal_write_split_mode_from_string(optarg); - if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) { - log_error("Invalid split mode: %s", optarg); - return -EINVAL; - } - break; - - case ARG_COMPRESS: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --compress= parameter."); - return -EINVAL; - } - - arg_compress = !!r; - } else - arg_compress = true; - - break; - - case ARG_SEAL: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --seal= parameter."); - return -EINVAL; - } - - arg_seal = !!r; - } else - arg_seal = true; - - break; - - case ARG_GNUTLS_LOG: { -#if HAVE_GNUTLS - const char* p = optarg; - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m"); - - if (r == 0) - break; - - if (strv_push(&arg_gnutls_log, word) < 0) - return log_oom(); - - word = NULL; - } - break; -#else - log_error("Option --gnutls-log is not available."); - return -EINVAL; -#endif - } - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unknown option code."); - } - - if (optind < argc) - arg_files = argv + optind; - - type_a = arg_getter || !strv_isempty(arg_files); - type_b = arg_url - || arg_listen_raw - || arg_listen_http || arg_listen_https - || sd_listen_fds(false) > 0; - if (type_a && type_b) { - log_error("Cannot use file input or --getter with " - "--arg-listen-... or socket activation."); - return -EINVAL; - } - if (type_a) { - if (!arg_output) { - log_error("Option --output must be specified with file input or --getter."); - return -EINVAL; - } - - if (!IN_SET(arg_split_mode, JOURNAL_WRITE_SPLIT_NONE, _JOURNAL_WRITE_SPLIT_INVALID)) { - log_error("For active sources, only --split-mode=none is allowed."); - return -EINVAL; - } - - arg_split_mode = JOURNAL_WRITE_SPLIT_NONE; - } - - if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) - arg_split_mode = JOURNAL_WRITE_SPLIT_HOST; - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE && arg_output) { - if (is_dir(arg_output, true) > 0) { - log_error("For SplitMode=none, output must be a file."); - return -EINVAL; - } - if (!endswith(arg_output, ".journal")) { - log_error("For SplitMode=none, output file name must end with .journal."); - return -EINVAL; - } - } - - if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST - && arg_output && is_dir(arg_output, true) <= 0) { - log_error("For SplitMode=host, output must be a directory."); - return -EINVAL; - } - - log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s", - journal_write_split_mode_to_string(arg_split_mode), - strna(arg_key), - strna(arg_cert), - strna(arg_trust)); - - return 1 /* work to do */; -} - -static int load_certificates(char **key, char **cert, char **trust) { - int r; - - r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key from file '%s': %m", - arg_key ?: PRIV_KEY_FILE); - - r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read certificate from file '%s': %m", - arg_cert ?: CERT_FILE); - - if (arg_trust_all) - log_info("Certificate checking disabled."); - else { - r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read CA certificate file '%s': %m", - arg_trust ?: TRUST_FILE); - } - - return 0; -} - -int main(int argc, char **argv) { - RemoteServer s = {}; - int r; - _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL; - - log_show_color(true); - log_parse_environment(); - - r = parse_config(); - if (r < 0) - return EXIT_FAILURE; - - r = parse_argv(argc, argv); - if (r <= 0) - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; - - if (arg_listen_http || arg_listen_https) { - r = setup_gnutls_logger(arg_gnutls_log); - if (r < 0) - return EXIT_FAILURE; - } - - if (arg_listen_https || https_socket >= 0) - if (load_certificates(&key, &cert, &trust) < 0) - return EXIT_FAILURE; - - if (remoteserver_init(&s, key, cert, trust) < 0) - return EXIT_FAILURE; - - r = sd_event_set_watchdog(s.events, true); - if (r < 0) - log_error_errno(r, "Failed to enable watchdog: %m"); - else - log_debug("Watchdog is %sd.", enable_disable(r > 0)); - - log_debug("%s running as pid "PID_FMT, - program_invocation_short_name, getpid_cached()); - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - while (s.active) { - r = sd_event_get_state(s.events); - if (r < 0) - break; - if (r == SD_EVENT_FINISHED) - break; - - r = sd_event_run(s.events, -1); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - break; - } - } - - sd_notifyf(false, - "STOPPING=1\n" - "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count); - log_info("Finishing after writing %" PRIu64 " entries", s.event_count); - - server_destroy(&s); - - free(arg_key); - free(arg_cert); - free(arg_trust); - - return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; + return journal_remote_add_source(s, fd2, hostname, true); } diff --git a/src/journal-remote/journal-remote.h b/src/journal-remote/journal-remote.h index f8df72ec55..884434cb4d 100644 --- a/src/journal-remote/journal-remote.h +++ b/src/journal-remote/journal-remote.h @@ -12,6 +12,8 @@ #include "hashmap.h" #include "journal-remote-parse.h" #include "journal-remote-write.h" + +#if HAVE_MICROHTTPD #include "microhttpd-util.h" typedef struct MHDDaemonWrapper MHDDaemonWrapper; @@ -23,6 +25,7 @@ struct MHDDaemonWrapper { sd_event_source *io_event; sd_event_source *timer_event; }; +#endif struct RemoteServer { RemoteSource **sources; @@ -36,6 +39,33 @@ struct RemoteServer { Writer *_single_writer; uint64_t event_count; - bool check_trust; +#if HAVE_MICROHTTPD Hashmap *daemons; +#endif + const char *output; /* either the output file or directory */ + + JournalWriteSplitMode split_mode; + bool compress; + bool seal; + bool check_trust; }; +extern RemoteServer *journal_remote_server_global; + +int journal_remote_server_init( + RemoteServer *s, + const char *output, + JournalWriteSplitMode split_mode, + bool compress, + bool seal); + +int journal_remote_get_writer(RemoteServer *s, const char *host, Writer **writer); + +int journal_remote_add_source(RemoteServer *s, int fd, char* name, bool own_name); +int journal_remote_add_raw_socket(RemoteServer *s, int fd); +int journal_remote_handle_raw_source( + sd_event_source *event, + int fd, + uint32_t revents, + RemoteServer *s); + +RemoteServer* journal_remote_server_destroy(RemoteServer *s); diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c index 909905d1cd..66af9d5dcb 100644 --- a/src/journal-remote/journal-upload-journal.c +++ b/src/journal-remote/journal-upload-journal.c @@ -8,12 +8,14 @@ #include <curl/curl.h> #include <stdbool.h> +#include "sd-daemon.h" + #include "alloc-util.h" #include "journal-upload.h" #include "log.h" +#include "string-util.h" #include "utf8.h" #include "util.h" -#include "sd-daemon.h" /** * Write up to size bytes to buf. Return negative on error, and number of @@ -139,8 +141,12 @@ static ssize_t write_entry(char *buf, size_t size, Uploader *u) { continue; } - if (!utf8_is_printable_newline(u->field_data, - u->field_length, false)) { + /* We already printed the boot id from the data in + * the header, hence let's suppress it here */ + if (memory_startswith(u->field_data, u->field_length, "_BOOT_ID=")) + continue; + + if (!utf8_is_printable_newline(u->field_data, u->field_length, false)) { u->entry_state = ENTRY_BINARY_FIELD_START; continue; } diff --git a/src/journal-remote/log-generator.py b/src/journal-remote/log-generator.py index c2f945bb47..e1725b1a71 100755 --- a/src/journal-remote/log-generator.py +++ b/src/journal-remote/log-generator.py @@ -5,7 +5,8 @@ import argparse PARSER = argparse.ArgumentParser() PARSER.add_argument('n', type=int) PARSER.add_argument('--dots', action='store_true') -PARSER.add_argument('--data-size', type=int, default=4000) +PARSER.add_argument('-m', '--message-size', type=int, default=200) +PARSER.add_argument('-d', '--data-size', type=int, default=4000) PARSER.add_argument('--data-type', choices={'random', 'simple'}) OPTIONS = PARSER.parse_args() @@ -42,7 +43,9 @@ bytes = 0 counter = 0 for i in range(OPTIONS.n): - message = repr(src.read(2000)) + message = src.read(OPTIONS.message_size) + message = repr(message)[2:-1] + if OPTIONS.data_type == 'random': data = repr(src.read(OPTIONS.data_size)) else: diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index c5a3dff635..940153c5fc 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -8,15 +8,35 @@ systemd_journal_upload_sources = files(''' journal-upload-journal.c '''.split()) -systemd_journal_remote_sources = files(''' +libsystemd_journal_remote_sources = files(''' journal-remote-parse.h journal-remote-parse.c journal-remote-write.h journal-remote-write.c journal-remote.h journal-remote.c - microhttpd-util.h - microhttpd-util.c +'''.split()) + +if conf.get('HAVE_MICROHTTPD') == 1 + libsystemd_journal_remote_sources += files(''' + microhttpd-util.h + microhttpd-util.c +'''.split()) +endif + +libsystemd_journal_remote = static_library( + 'systemd-journal-remote', + libsystemd_journal_remote_sources, + include_directories : includes, + dependencies : [threads, + libmicrohttpd, + libgnutls, + libxz, + liblz4], + install : false) + +systemd_journal_remote_sources = files(''' + journal-remote-main.c '''.split()) systemd_journal_gatewayd_sources = files(''' diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index 606ca604ac..a6e85aee60 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -453,7 +453,10 @@ static int journal_file_refresh_header(JournalFile *f) { assert(f->header); r = sd_id128_get_machine(&f->header->machine_id); - if (r < 0) + if (IN_SET(r, -ENOENT, -ENOMEDIUM)) + /* We don't have a machine-id, let's continue without */ + zero(f->header->machine_id); + else if (r < 0) return r; r = sd_id128_get_boot(&boot_id); @@ -1798,6 +1801,7 @@ static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) { static int journal_file_append_entry_internal( JournalFile *f, const dual_timestamp *ts, + const sd_id128_t *boot_id, uint64_t xor_hash, const EntryItem items[], unsigned n_items, uint64_t *seqnum, @@ -1823,7 +1827,7 @@ static int journal_file_append_entry_internal( o->entry.realtime = htole64(ts->realtime); o->entry.monotonic = htole64(ts->monotonic); o->entry.xor_hash = htole64(xor_hash); - o->entry.boot_id = f->header->boot_id; + o->entry.boot_id = boot_id ? *boot_id : f->header->boot_id; #if HAVE_GCRYPT r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np); @@ -1944,7 +1948,14 @@ static int entry_item_cmp(const void *_a, const void *_b) { return 0; } -int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqnum, Object **ret, uint64_t *offset) { +int journal_file_append_entry( + JournalFile *f, + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const struct iovec iovec[], unsigned n_iovec, + uint64_t *seqnum, + Object **ret, uint64_t *offset) { + unsigned i; EntryItem *items; int r; @@ -1955,7 +1966,16 @@ int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const st assert(f->header); assert(iovec || n_iovec == 0); - if (!ts) { + if (ts) { + if (!VALID_REALTIME(ts->realtime)) { + log_debug("Invalid realtime timestamp %"PRIu64", refusing entry.", ts->realtime); + return -EBADMSG; + } + if (!VALID_MONOTONIC(ts->monotonic)) { + log_debug("Invalid monotomic timestamp %"PRIu64", refusing entry.", ts->monotonic); + return -EBADMSG; + } + } else { dual_timestamp_get(&_ts); ts = &_ts; } @@ -1986,7 +2006,7 @@ int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const st * times for rotating media. */ qsort_safe(items, n_iovec, sizeof(EntryItem), entry_item_cmp); - r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset); + r = journal_file_append_entry_internal(f, ts, boot_id, xor_hash, items, n_iovec, seqnum, ret, offset); /* If the memory mapping triggered a SIGBUS then we return an * IO error and ignore the error code passed down to us, since @@ -3563,12 +3583,13 @@ int journal_file_open_reliably( deferred_closes, template, ret); } -int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) { +int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p) { uint64_t i, n; uint64_t q, xor_hash = 0; int r; EntryItem *items; dual_timestamp ts; + const sd_id128_t *boot_id; assert(from); assert(to); @@ -3580,6 +3601,7 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 ts.monotonic = le64toh(o->entry.monotonic); ts.realtime = le64toh(o->entry.realtime); + boot_id = &o->entry.boot_id; n = journal_file_entry_n_items(o); /* alloca() can't take 0, hence let's allocate at least one */ @@ -3639,7 +3661,8 @@ int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint6 return r; } - r = journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset); + r = journal_file_append_entry_internal(to, &ts, boot_id, xor_hash, items, n, + NULL, NULL, NULL); if (mmap_cache_got_sigbus(to->mmap, to->cache_fd)) return -EIO; diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h index 6411f85c20..bfd4bbac90 100644 --- a/src/journal/journal-file.h +++ b/src/journal/journal-file.h @@ -205,7 +205,14 @@ uint64_t journal_file_entry_array_n_items(Object *o) _pure_; uint64_t journal_file_hash_table_n_items(Object *o) _pure_; int journal_file_append_object(JournalFile *f, ObjectType type, uint64_t size, Object **ret, uint64_t *offset); -int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqno, Object **ret, uint64_t *offset); +int journal_file_append_entry( + JournalFile *f, + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const struct iovec iovec[], unsigned n_iovec, + uint64_t *seqno, + Object **ret, + uint64_t *offset); int journal_file_find_data_object(JournalFile *f, const void *data, uint64_t size, Object **ret, uint64_t *offset); int journal_file_find_data_object_with_hash(JournalFile *f, const void *data, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset); @@ -229,7 +236,7 @@ int journal_file_move_to_entry_by_seqnum_for_data(JournalFile *f, uint64_t data_ int journal_file_move_to_entry_by_realtime_for_data(JournalFile *f, uint64_t data_offset, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset); int journal_file_move_to_entry_by_monotonic_for_data(JournalFile *f, uint64_t data_offset, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset); -int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset); +int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p); void journal_file_dump(JournalFile *f); void journal_file_print_header(JournalFile *f); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 26222ea28d..61d29986ce 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -2619,8 +2619,8 @@ int main(int argc, char *argv[]) { arg_utc * OUTPUT_UTC | arg_no_hostname * OUTPUT_NO_HOSTNAME; - r = output_journal(stdout, j, arg_output, 0, flags, - arg_output_fields, highlight, &ellipsized); + r = show_journal_entry(stdout, j, arg_output, 0, flags, + arg_output_fields, highlight, &ellipsized); need_seek = true; if (r == -EADDRNOTAVAIL) break; diff --git a/src/journal/journald-native.h b/src/journal/journald-native.h index 407fc4505c..8280569387 100644 --- a/src/journal/journald-native.h +++ b/src/journal/journald-native.h @@ -9,8 +9,21 @@ #include "journald-server.h" -void server_process_native_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); +void server_process_native_message( + Server *s, + const void *buffer, + size_t buffer_size, + const struct ucred *ucred, + const struct timeval *tv, + const char *label, + size_t label_len); -void server_process_native_file(Server *s, int fd, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); +void server_process_native_file( + Server *s, + int fd, + const struct ucred *ucred, + const struct timeval *tv, + const char *label, + size_t label_len); -int server_open_native_socket(Server*s); +int server_open_native_socket(Server *s); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 554cf20dec..40c6714f99 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -692,7 +692,7 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, size_t n s->last_realtime_clock = ts.realtime; - r = journal_file_append_entry(f, &ts, iovec, n, &s->seqnum, NULL, NULL); + r = journal_file_append_entry(f, &ts, NULL, iovec, n, &s->seqnum, NULL, NULL); if (r >= 0) { server_schedule_sync(s, priority); return; @@ -711,7 +711,7 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, size_t n return; log_debug("Retrying write."); - r = journal_file_append_entry(f, &ts, iovec, n, &s->seqnum, NULL, NULL); + r = journal_file_append_entry(f, &ts, NULL, iovec, n, &s->seqnum, NULL, NULL); if (r < 0) log_error_errno(r, "Failed to write entry (%zu items, %zu bytes) despite vacuuming, ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n)); else @@ -1012,7 +1012,7 @@ int server_flush_to_var(Server *s, bool require_flag_file) { goto finish; } - r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL); + r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset); if (r >= 0) continue; @@ -1031,7 +1031,7 @@ int server_flush_to_var(Server *s, bool require_flag_file) { } log_debug("Retrying write."); - r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL); + r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset); if (r < 0) { log_error_errno(r, "Can't write entry: %m"); goto finish; diff --git a/src/journal/test-journal-flush.c b/src/journal/test-journal-flush.c index cce547e5f4..43925cf9a2 100644 --- a/src/journal/test-journal-flush.c +++ b/src/journal/test-journal-flush.c @@ -44,7 +44,7 @@ int main(int argc, char *argv[]) { r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o); assert_se(r >= 0); - r = journal_file_copy_entry(f, new_journal, o, f->current_offset, NULL, NULL, NULL); + r = journal_file_copy_entry(f, new_journal, o, f->current_offset); assert_se(r >= 0); n++; diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c index e0481088ea..6c0d0f5645 100644 --- a/src/journal/test-journal-interleaving.c +++ b/src/journal/test-journal-interleaving.c @@ -66,7 +66,7 @@ static void append_number(JournalFile *f, int n, uint64_t *seqnum) { assert_se(asprintf(&p, "NUMBER=%d", n) >= 0); iovec[0].iov_base = p; iovec[0].iov_len = strlen(p); - assert_ret(journal_file_append_entry(f, &ts, iovec, 1, seqnum, NULL, NULL)); + assert_ret(journal_file_append_entry(f, &ts, NULL, iovec, 1, seqnum, NULL, NULL)); free(p); } diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c index 05cb5cb988..052ea49959 100644 --- a/src/journal/test-journal-stream.c +++ b/src/journal/test-journal-stream.c @@ -109,12 +109,12 @@ int main(int argc, char *argv[]) { iovec[1].iov_len = strlen(q); if (i % 10 == 0) - assert_se(journal_file_append_entry(three, &ts, iovec, 2, NULL, NULL, NULL) == 0); + assert_se(journal_file_append_entry(three, &ts, NULL, iovec, 2, NULL, NULL, NULL) == 0); else { if (i % 3 == 0) - assert_se(journal_file_append_entry(two, &ts, iovec, 2, NULL, NULL, NULL) == 0); + assert_se(journal_file_append_entry(two, &ts, NULL, iovec, 2, NULL, NULL, NULL) == 0); - assert_se(journal_file_append_entry(one, &ts, iovec, 2, NULL, NULL, NULL) == 0); + assert_se(journal_file_append_entry(one, &ts, NULL, iovec, 2, NULL, NULL, NULL) == 0); } free(p); diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c index 82b32554c0..04b5ccbc22 100644 --- a/src/journal/test-journal-verify.c +++ b/src/journal/test-journal-verify.c @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) { iovec.iov_base = (void*) test; iovec.iov_len = strlen(test); - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL) == 0); free(test); } diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c index 5765da4895..1acb43e689 100644 --- a/src/journal/test-journal.c +++ b/src/journal/test-journal.c @@ -23,6 +23,7 @@ static void test_non_empty(void) { static const char test[] = "TEST1=1", test2[] = "TEST2=2"; Object *o; uint64_t p; + sd_id128_t fake_boot_id; char t[] = "/tmp/journal-XXXXXX"; log_set_max_level(LOG_DEBUG); @@ -32,19 +33,20 @@ static void test_non_empty(void) { assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, (uint64_t) -1, true, NULL, NULL, NULL, NULL, &f) == 0); - dual_timestamp_get(&ts); + assert_se(dual_timestamp_get(&ts)); + assert_se(sd_id128_randomize(&fake_boot_id) == 0); iovec.iov_base = (void*) test; iovec.iov_len = strlen(test); - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL) == 0); iovec.iov_base = (void*) test2; iovec.iov_len = strlen(test2); - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL) == 0); iovec.iov_base = (void*) test; iovec.iov_len = strlen(test); - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + assert_se(journal_file_append_entry(f, &ts, &fake_boot_id, &iovec, 1, NULL, NULL, NULL) == 0); #if HAVE_GCRYPT journal_file_append_tag(f); @@ -59,6 +61,7 @@ static void test_non_empty(void) { assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1); assert_se(le64toh(o->entry.seqnum) == 3); + assert_se(sd_id128_equal(o->entry.boot_id, fake_boot_id)); assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 0); @@ -177,7 +180,7 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { iovec.iov_base = (void*) data; iovec.iov_len = data_size; - assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0); + assert_se(journal_file_append_entry(f, &ts, NULL, &iovec, 1, NULL, NULL, NULL) == 0); #if HAVE_GCRYPT journal_file_append_tag(f); diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index af2ff8353a..80548fdfcf 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -98,7 +98,7 @@ _public_ int sd_id128_get_machine(sd_id128_t *ret) { return r; if (sd_id128_is_null(saved_machine_id)) - return -EINVAL; + return -ENOMEDIUM; } *ret = saved_machine_id; diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 8cea282988..43edbb02fc 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -474,8 +474,9 @@ static int print_session_status_info(sd_bus *bus, const char *path, bool *new_li _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX]; + char since2[FORMAT_TIMESTAMP_MAX]; + const char *s1, *s2; SessionStatusInfo i = {}; int r; @@ -605,8 +606,9 @@ static int print_user_status_info(sd_bus *bus, const char *path, bool *new_line) _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX]; + char since2[FORMAT_TIMESTAMP_MAX]; + const char *s1, *s2; _cleanup_(user_status_info_clear) UserStatusInfo i = {}; int r; diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index eb68eb192b..d12c7d646b 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -556,8 +556,9 @@ static void machine_status_info_clear(MachineStatusInfo *info) { } static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX]; + char since2[FORMAT_TIMESTAMP_MAX]; + const char *s1, *s2; int ifi = -1; assert(bus); @@ -902,10 +903,11 @@ typedef struct ImageStatusInfo { } ImageStatusInfo; static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { - char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2; - char bs[FORMAT_BYTES_MAX], *s3; - char bs_exclusive[FORMAT_BYTES_MAX], *s4; + char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX]; + char ts_absolute[FORMAT_TIMESTAMP_MAX]; + char bs[FORMAT_BYTES_MAX]; + char bs_exclusive[FORMAT_BYTES_MAX]; + const char *s1, *s2, *s3, *s4; assert(bus); assert(i); diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c index a1d3ea2fb5..e150c8728e 100644 --- a/src/shared/bus-util.c +++ b/src/shared/bus-util.c @@ -697,7 +697,8 @@ int bus_print_property(const char *name, sd_bus_message *m, bool value, bool all * should it turn out to not be sufficient */ if (endswith(name, "Timestamp") || STR_IN_SET(name, "NextElapseUSecRealtime", "LastTriggerUSec")) { - char timestamp[FORMAT_TIMESTAMP_MAX], *t; + char timestamp[FORMAT_TIMESTAMP_MAX]; + const char *t; t = format_timestamp(timestamp, sizeof(timestamp), u); if (t || all) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 59ee4fe9e5..25d1611f3d 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -96,6 +96,7 @@ not_found: #endif } +#if HAVE_BLKID /* Detect RPMB and Boot partitions, which are not listed by blkid. * See https://github.com/systemd/systemd/issues/5806. */ static bool device_is_mmc_special_partition(struct udev_device *d) { @@ -115,6 +116,7 @@ static bool device_is_block(struct udev_device *d) { return streq(ss, "block"); } +#endif int dissect_image( int fd, diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index af788391ad..5af23e44d7 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -293,19 +293,13 @@ static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, Ou assert(f); assert(j); - r = -ENXIO; if (realtime) r = safe_atou64(realtime, &x); - if (r < 0) + if (!realtime || r < 0 || !VALID_REALTIME(x)) r = sd_journal_get_realtime_usec(j, &x); if (r < 0) return log_error_errno(r, "Failed to get realtime timestamp: %m"); - if (x > USEC_TIMESTAMP_FORMATTABLE_MAX) { - log_error("Timestamp cannot be printed"); - return -EINVAL; - } - if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) { const char *k; @@ -314,7 +308,7 @@ static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, Ou else k = format_timestamp(buf, sizeof(buf), x); if (!k) { - log_error("Failed to format timestamp."); + log_error("Failed to format timestamp: %"PRIu64, x); return -EINVAL; } @@ -422,7 +416,6 @@ static int output_short( 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; @@ -664,10 +657,8 @@ static int output_export( JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { const char *c; - /* We already printed the boot id, from the data in - * the header, hence let's suppress it here */ - if (length >= 9 && - startswith(data, "_BOOT_ID=")) + /* 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); @@ -839,7 +830,7 @@ static int output_json( if (!eq) continue; - n = strndup(data, eq - (const char*) data); + n = memdup_suffix0(data, eq - (const char*) data); if (!n) { r = log_oom(); goto finish; @@ -862,12 +853,10 @@ static int output_json( } } } - if (r == -EBADMSG) { log_debug_errno(r, "Skipping message we can't read: %m"); return 0; } - if (r < 0) return r; @@ -877,11 +866,13 @@ static int output_json( SD_JOURNAL_FOREACH_DATA(j, data, length) { const char *eq; - char *kk, *n; + char *kk; + _cleanup_free_ char *n = NULL; size_t m; unsigned u; - /* We already printed the boot id, from the data in the header, hence let's suppress it here */ + /* 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; @@ -890,33 +881,24 @@ static int output_json( continue; m = eq - (const char*) data; - - n = strndup(data, m); + n = memdup_suffix0(data, m); if (!n) { r = log_oom(); goto finish; } - if (output_fields && !set_get(output_fields, n)) { - free(n); + if (output_fields && !set_get(output_fields, n)) continue; - } - if (separator) { - if (mode == OUTPUT_JSON_PRETTY) - fputs(",\n\t", f); - else - fputs(", ", f); - } + if (separator) + fputs(mode == OUTPUT_JSON_PRETTY ? ",\n\t" : ", ", f); u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); - if (u == 0) { + if (u == 0) /* We already printed this, let's jump to the next */ - free(n); separator = false; - continue; - } else if (u == 1) { + else if (u == 1) { /* Field only appears once, output it directly */ json_escape(f, data, m, flags); @@ -926,12 +908,9 @@ static int output_json( hashmap_remove(h, n); free(kk); - free(n); separator = true; - continue; - } else { /* Field appears multiple times, output it as array */ json_escape(f, data, m, flags); @@ -958,7 +937,6 @@ static int output_json( hashmap_remove(h, n); free(kk); - free(n); /* Iterate data fields form the beginning */ done = false; @@ -1068,7 +1046,7 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( [OUTPUT_WITH_UNIT] = output_short, }; -int output_journal( +int show_journal_entry( FILE *f, sd_journal *j, OutputMode mode, @@ -1119,14 +1097,15 @@ static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) { return 0; } -static 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 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; @@ -1137,14 +1116,18 @@ static int show_journal(FILE *f, assert(mode >= 0); assert(mode < _OUTPUT_MODE_MAX); - /* Seek to end */ - r = sd_journal_seek_tail(j); - if (r < 0) - return log_error_errno(r, "Failed to seek to tail: %m"); + if (how_many == (unsigned) -1) + 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"); + r = sd_journal_previous_skip(j, how_many); + if (r < 0) + return log_error_errno(r, "Failed to skip previous: %m"); + } for (;;) { for (;;) { @@ -1178,7 +1161,7 @@ static int show_journal(FILE *f, line++; maybe_print_begin_newline(f, &flags); - r = output_journal(f, j, mode, n_columns, flags, NULL, NULL, ellipsized); + r = show_journal_entry(f, j, mode, n_columns, flags, NULL, NULL, ellipsized); if (r < 0) return r; } diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index 1a1874685c..68e234a0ef 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -19,7 +19,7 @@ #include "time-util.h" #include "util.h" -int output_journal( +int show_journal_entry( FILE *f, sd_journal *j, OutputMode mode, @@ -28,6 +28,15 @@ int output_journal( char **output_fields, size_t highlight[2], bool *ellipsized); +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 add_match_this_boot(sd_journal *j, const char *machine); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 60861dd791..ff9c3b9170 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -3948,8 +3948,8 @@ static void print_status_info( UnitStatusInfo *i, bool *ellipsized) { - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1, since2[FORMAT_TIMESTAMP_MAX], *s2; - const char *active_on, *active_off, *on, *off, *ss; + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX]; + const char *s1, *s2, *active_on, *active_off, *on, *off, *ss; _cleanup_free_ char *formatted_path = NULL; ExecStatusInfo *p; usec_t timestamp; @@ -4077,7 +4077,7 @@ static void print_status_info( if (endswith(i->id, ".timer")) { char tstamp1[FORMAT_TIMESTAMP_RELATIVE_MAX], tstamp2[FORMAT_TIMESTAMP_MAX]; - char *next_rel_time, *next_time; + const char *next_rel_time, *next_time; dual_timestamp nw, next = {i->next_elapse_real, i->next_elapse_monotonic}; usec_t next_elapse; @@ -4086,12 +4086,8 @@ static void print_status_info( dual_timestamp_get(&nw); next_elapse = calc_next_elapse(&nw, &next); - next_rel_time = format_timestamp_relative(tstamp1, - sizeof(tstamp1), - next_elapse); - next_time = format_timestamp(tstamp2, - sizeof(tstamp2), - next_elapse); + next_rel_time = format_timestamp_relative(tstamp1, sizeof tstamp1, next_elapse); + next_time = format_timestamp(tstamp2, sizeof tstamp2, next_elapse); if (next_time && next_rel_time) printf("%s; %s\n", next_time, next_rel_time); diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index eac12ac7af..413adfda7d 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -6,6 +6,7 @@ ***/ #include "alloc-util.h" +#include "locale-util.h" #include "macro.h" #include "string-util.h" #include "strv.h" @@ -77,6 +78,29 @@ static void test_ascii_strcasecmp_nn(void) { assert_se(ascii_strcasecmp_nn("BBbb", 4, "aaaa", 4) > 0); } +static void test_cellescape(void) { + char buf[40]; + + assert_se(streq(cellescape(buf, 10, "1"), "1")); + assert_se(streq(cellescape(buf, 10, "12"), "12")); + assert_se(streq(cellescape(buf, 10, "123"), is_locale_utf8() ? "1…" : "1...")); + + assert_se(streq(cellescape(buf, 10, "1\011"), "1\\t")); + assert_se(streq(cellescape(buf, 10, "1\020"), "1\\020")); + assert_se(streq(cellescape(buf, 10, "1\020x"), is_locale_utf8() ? "1…" : "1...")); + + assert_se(streq(cellescape(buf, 40, "1\020"), "1\\020")); + assert_se(streq(cellescape(buf, 40, "1\020x"), "1\\020x")); + + assert_se(streq(cellescape(buf, 40, "\a\b\f\n\r\t\v\\\"'"), "\\a\\b\\f\\n\\r\\t\\v\\\\\\\"\\'")); + assert_se(streq(cellescape(buf, 10, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); + assert_se(streq(cellescape(buf, 11, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); + assert_se(streq(cellescape(buf, 12, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a\\b…" : "\\a\\b...")); + + assert_se(streq(cellescape(buf, sizeof buf, "1\020"), "1\\020")); + assert_se(streq(cellescape(buf, sizeof buf, "1\020x"), "1\\020x")); +} + static void test_streq_ptr(void) { assert_se(streq_ptr(NULL, NULL)); assert_se(!streq_ptr("abc", "cdef")); @@ -422,6 +446,7 @@ int main(int argc, char *argv[]) { test_string_erase(); test_ascii_strcasecmp_n(); test_ascii_strcasecmp_nn(); + test_cellescape(); test_streq_ptr(); test_strstrip(); test_strextend(); diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index 5b8e7c7b35..00d583182d 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -120,18 +120,16 @@ static void test_parse_nsec(void) { } static void test_format_timespan_one(usec_t x, usec_t accuracy) { - char *r; char l[FORMAT_TIMESPAN_MAX]; + const char *t; usec_t y; log_info(USEC_FMT" (at accuracy "USEC_FMT")", x, accuracy); - r = format_timespan(l, sizeof(l), x, accuracy); - assert_se(r); + assert_se(t = format_timespan(l, sizeof l, x, accuracy)); + log_info(" = <%s>", t); - log_info(" = <%s>", l); - - assert_se(parse_sec(l, &y) >= 0); + assert_se(parse_sec(t, &y) >= 0); log_info(" = "USEC_FMT, y); @@ -271,13 +269,12 @@ static void test_format_timestamp(void) { } } -static void test_format_timestamp_utc_one(usec_t t, const char *result) { +static void test_format_timestamp_utc_one(usec_t val, const char *result) { char buf[FORMAT_TIMESTAMP_MAX]; + const char *t; - assert_se(!format_timestamp_utc(buf, sizeof(buf), t) == !result); - - if (result) - assert_se(streq(result, buf)); + t = format_timestamp_utc(buf, sizeof(buf), val); + assert_se(streq_ptr(t, result)); } static void test_format_timestamp_utc(void) { @@ -287,11 +284,12 @@ static void test_format_timestamp_utc(void) { #if SIZEOF_TIME_T == 8 test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Thu 9999-12-30 23:59:59 UTC"); + test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX"); #elif SIZEOF_TIME_T == 4 test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Tue 2038-01-19 03:14:07 UTC"); + test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX"); #endif - test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX+1, NULL); test_format_timestamp_utc_one(USEC_INFINITY, NULL); } diff --git a/test/fuzz-corpus/.gitattributes b/test/fuzz-corpus/.gitattributes new file mode 100644 index 0000000000..7b1b3e1835 --- /dev/null +++ b/test/fuzz-corpus/.gitattributes @@ -0,0 +1 @@ +/*/* -whitespace diff --git a/test/fuzz-corpus/journal-remote/invalid-ts.txt b/test/fuzz-corpus/journal-remote/invalid-ts.txt Binary files differnew file mode 100644 index 0000000000..bc036fdcb0 --- /dev/null +++ b/test/fuzz-corpus/journal-remote/invalid-ts.txt diff --git a/test/fuzz-corpus/journal-remote/sample.txt b/test/fuzz-corpus/journal-remote/sample.txt new file mode 100644 index 0000000000..891c0003ef --- /dev/null +++ b/test/fuzz-corpus/journal-remote/sample.txt @@ -0,0 +1,180 @@ +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12d7;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501873 +__MONOTONIC_TIMESTAMP=1753961140951 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=\x1b\r"\x9a\xea]\x90rU\xb0SX5\nY\xebi\xdac\x1f\xde\xb4\xf6\x0e\x8d/!\xd0\x9a\xe8\x8b\xc3#hN\xf4\x9c\x8e\xc5\x92>\xaa\xf8Ih\x13\xd2\xbbOa\xedK\x04\xa449\xf3f\x9e\xfc=\xc9\xc1\x0fe\xb4\xf96\xd5z\xcfQ\xcb\xb1\xb4\xe48\xb3\x9f\x1b +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483516 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000000 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12d8;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501874 +__MONOTONIC_TIMESTAMP=1753961140952 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=l\x1a\xf4^\xb1\x14\xfb@\r\xa1\x11\xda0\xe0]3Ms$\x7f06\xde\xd9\x02y\xf9@\n\xe8\x01\x83\xcb\xe0)\xed\x98*>\xa1\xc2Y\xe8IR\x95h\xa1\xbb\x16\xba\xedK\x11\xfcj\x04\xfb\x0b\x9b)p\x10\xecH\x1f\x0b\x89{\xeb'\x0e\x1d\xaa\xcbZ\x86\xe0k1 +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483517 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000001 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12d9;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501875 +__MONOTONIC_TIMESTAMP=1753961140953 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=MzV_\xbb\xc1\x14f\x84\x15\xf5\xe0\xe6\xd2\x0e6#N\xf1\x1b\xe9Z*\x8f\x8a\x13\xad\xa4%r\x02\xd1\xc4^U\xc0u!\xdfjl\x15\xb6\xcc\x93\x1dRi<\x1a\xa9/\x9c\xcb\xe8\x99\xe3\x1cN\x06\xf0\xb41a\xa7L\x99\xda\x83Q: ]\x1c\xb9Hiz\n\x94 +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483518 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000002 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12da;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501876 +__MONOTONIC_TIMESTAMP=1753961140954 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=\x8aF\xfcG\xd7\xeeZ\x86\xcb.O\xb1!,2\xbf\x86\\&\x15\xa7\xe6\xe7-\x81\xed\xf8\x7f=\xf7\x90YF\xe1\xe6\x99\x83\x84\r\xe48\x93\xc7\xdd\tJy\x86\\\xb4\xf9\xefT\r\x04\xae\x1d\x99\xfe'\x99m\xc4#\x8d\x89w\xb1\xecC\xaf\xe6\x1b\xfd\xc5\xbc\xfd\xe3w2 +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483519 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000003 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12db;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501877 +__MONOTONIC_TIMESTAMP=1753961140955 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=`\xc7\r\xb6\xc3NPjc\xa129L\xe1\x17\xa2\x96\xa8w\x0c\x07\x8f\x98\x1eS-N\xb7lt\xc5=\xd1\x93\x10_1\xdc\xa9x\xd1\x8a\n\xb1\x90\xdca\xc4\x94\x98\x92\x00\x90)d{\x96\x9e\xc2A\xbf\x81s\xf82_\xe0;\xc3\x06\x8eO\xe4\x8a5GX\xe1\xff\xea +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483520 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000004 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12dc;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501878 +__MONOTONIC_TIMESTAMP=1753961140956 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=\r\t \xdf-\xed\xd5\xde\xa1/\xa5T\x1a\xdd\xf9a\xe2\x8b()\xd5\xf2\x1b\xbcu~\xaa\x97\xc7~\x0e~2\x11\xa0\xb5\xd3\xd7^ \xea\x16\x02{\xd1\xbe\xa02\xad\x00\xba$\xf2\xd5\x7f\x9a\xf0\xf9\xf2\x14\xf0/\xb5\xd3"`\xd8\x8e\xb6w\x1bP\x96\xf1\x0c\xf0#\xd2\x12\x88 +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483521 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000005 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12dd;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501879 +__MONOTONIC_TIMESTAMP=1753961140957 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=\x1a\x15\xd3\x8d\x98\x83m\xe2\x02\xfa\x81\x98\xef\xa2\x8a\xcc\x10\xc5=q=\xd0\xd7_\x0e\x92D\xb1\xc7 \xaa\xae-\x18\xff\xb0<l5\xf1\x91-\xe8g! \xd8\xac\xadi"\xf8 \xebL\xe6-\xbf=i_@\x9b)B\xac\xa50\xf9\xf1~\xb1c^pTD\x15\xee} +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483522 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000006 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12de;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501880 +__MONOTONIC_TIMESTAMP=1753961140958 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=\xe4L \xb4\t\xf3\xfbQ\xb8\x95f{C\x1b\x91\x81\xd2!\xc0f\xa41=\xff\x84W\xf3\x0f=\x9e\x87\xd1\x9f\x86;F\x12\xd6\x1c6B\x07\x08\xdb*\xeem\x9f\xe7\xda\x81n_\x00^\xcf!\x19\x19\xe0\x9cM\x05\xf0\xe9\xe9=\xbc\xba=`inw\xc4Qq\x9cW\xe6 +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483523 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000007 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12df;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501881 +__MONOTONIC_TIMESTAMP=1753961140959 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=\xda\x80\xe0\xe5@\xa4\x94\xecL\xbd\xe4\xe5\xbd\xc8\xae\x8e\xa9k\xa4\rt\xf2\x17\xe3n!.\xe3\xab*\xe3f{H\x98\x86\xa1=U-\x8cNd+\x90\xbd\x970d\xf7\xee\xd7g\x08c\x12\xf4\x9f3\xd0&\x95\xb0\xac\x1a\xe9k\xda,}\x97`:u\xad\x9e\xfaLj\x11 +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483524 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000008 + +__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m=198603b12e0;t=4fd05c +__REALTIME_TIMESTAMP=1404101101501882 +__MONOTONIC_TIMESTAMP=1753961140960 +_BOOT_ID=f446871715504074bf7049ef0718fa93 +_TRANSPORT=syslog +PRIORITY=3 +SYSLOG_FACILITY=6 +SYSLOG_IDENTIFIER=/USR/SBIN/CRON +MESSAGE=\xc0\xb4\xefIe\xc9\xd0\xaf!y\x13\xfdT(k\x9b\xc7\x7fm;\xc2\xbb"\x81\x87\\(-\x9a\x8b\xdd\x17\xf7\x8a\x92\xbd\xdd;\x9f\x99\x87\xf2\xb7\xcf\xf6XtRC\xad\xebT\xa1\xe5\xd9p\xd70\xc1\xb0^\x88g@=\xeb\xd8\xcf\xb7bK"6 \xda\x08\x1bp\xbc\r +_UID=0 +_GID=0 +_MACHINE_ID=69121ca41d12c1b69a7960174c27b618 +_HOSTNAME=hostname +SYSLOG_PID=25721 +_PID=25721 +_SOURCE_REALTIME_TIMESTAMP=1404101101483525 +DATA=00000000000000000000000000000000000000000000000000000000000000000000000000000009 + diff --git a/test/fuzz-regressions/fuzz-journal-remote/crash-5a8f03d4c3a46fcded39527084f437e8e4b54b76 b/test/fuzz-regressions/fuzz-journal-remote/crash-5a8f03d4c3a46fcded39527084f437e8e4b54b76 Binary files differnew file mode 100644 index 0000000000..e6a7316805 --- /dev/null +++ b/test/fuzz-regressions/fuzz-journal-remote/crash-5a8f03d4c3a46fcded39527084f437e8e4b54b76 diff --git a/test/fuzz-regressions/fuzz-journal-remote/crash-96dee870ea66d03e89ac321eee28ea63a9b9aa45 b/test/fuzz-regressions/fuzz-journal-remote/crash-96dee870ea66d03e89ac321eee28ea63a9b9aa45 Binary files differnew file mode 100644 index 0000000000..535d49ea7a --- /dev/null +++ b/test/fuzz-regressions/fuzz-journal-remote/crash-96dee870ea66d03e89ac321eee28ea63a9b9aa45 |