summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2018-05-31 15:33:44 +0200
committerGitHub <noreply@github.com>2018-05-31 15:33:44 +0200
commit89544ae65827c6fc19579861961d26157b572bbb (patch)
tree6ada0a4d4eaac607ab88ffc6ed9cf9fe65707351
parent8d96289711c51c5099be2241af8b213e89d9dbd1 (diff)
parent0ab896b343afeaa70f69881626a50855dc518d68 (diff)
downloadsystemd-89544ae65827c6fc19579861961d26157b572bbb.tar.gz
Merge pull request #9014 from keszybz/fuzz-journal-remote
A fuzzer for journal-remote
-rw-r--r--man/sd_id128_get_machine.xml8
-rw-r--r--meson.build4
-rw-r--r--src/basic/escape.c4
-rw-r--r--src/basic/escape.h2
-rw-r--r--src/basic/journal-importer.c106
-rw-r--r--src/basic/journal-importer.h3
-rw-r--r--src/basic/random-util.c2
-rw-r--r--src/basic/string-util.c70
-rw-r--r--src/basic/string-util.h4
-rw-r--r--src/basic/time-util.c7
-rw-r--r--src/fuzz/fuzz-journal-remote.c75
-rw-r--r--src/fuzz/fuzz-journal-remote.options2
-rw-r--r--src/fuzz/meson.build5
-rw-r--r--src/journal-remote/journal-gatewayd.c2
-rw-r--r--src/journal-remote/journal-remote-main.c1145
-rw-r--r--src/journal-remote/journal-remote-parse.c6
-rw-r--r--src/journal-remote/journal-remote-write.c13
-rw-r--r--src/journal-remote/journal-remote.c1219
-rw-r--r--src/journal-remote/journal-remote.h32
-rw-r--r--src/journal-remote/journal-upload-journal.c12
-rwxr-xr-xsrc/journal-remote/log-generator.py7
-rw-r--r--src/journal-remote/meson.build26
-rw-r--r--src/journal/journal-file.c37
-rw-r--r--src/journal/journal-file.h11
-rw-r--r--src/journal/journalctl.c4
-rw-r--r--src/journal/journald-native.h19
-rw-r--r--src/journal/journald-server.c8
-rw-r--r--src/journal/test-journal-flush.c2
-rw-r--r--src/journal/test-journal-interleaving.c2
-rw-r--r--src/journal/test-journal-stream.c6
-rw-r--r--src/journal/test-journal-verify.c2
-rw-r--r--src/journal/test-journal.c13
-rw-r--r--src/libsystemd/sd-id128/sd-id128.c2
-rw-r--r--src/login/loginctl.c10
-rw-r--r--src/machine/machinectl.c14
-rw-r--r--src/shared/bus-util.c3
-rw-r--r--src/shared/dissect-image.c2
-rw-r--r--src/shared/logs-show.c91
-rw-r--r--src/shared/logs-show.h11
-rw-r--r--src/systemctl/systemctl.c14
-rw-r--r--src/test/test-string-util.c25
-rw-r--r--src/test/test-time-util.c22
-rw-r--r--test/fuzz-corpus/.gitattributes1
-rw-r--r--test/fuzz-corpus/journal-remote/invalid-ts.txtbin0 -> 4657 bytes
-rw-r--r--test/fuzz-corpus/journal-remote/sample.txt180
-rw-r--r--test/fuzz-regressions/fuzz-journal-remote/crash-5a8f03d4c3a46fcded39527084f437e8e4b54b76bin0 -> 7675 bytes
-rw-r--r--test/fuzz-regressions/fuzz-journal-remote/crash-96dee870ea66d03e89ac321eee28ea63a9b9aa45bin0 -> 2490 bytes
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
new file mode 100644
index 0000000000..bc036fdcb0
--- /dev/null
+++ b/test/fuzz-corpus/journal-remote/invalid-ts.txt
Binary files differ
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
new file mode 100644
index 0000000000..e6a7316805
--- /dev/null
+++ b/test/fuzz-regressions/fuzz-journal-remote/crash-5a8f03d4c3a46fcded39527084f437e8e4b54b76
Binary files differ
diff --git a/test/fuzz-regressions/fuzz-journal-remote/crash-96dee870ea66d03e89ac321eee28ea63a9b9aa45 b/test/fuzz-regressions/fuzz-journal-remote/crash-96dee870ea66d03e89ac321eee28ea63a9b9aa45
new file mode 100644
index 0000000000..535d49ea7a
--- /dev/null
+++ b/test/fuzz-regressions/fuzz-journal-remote/crash-96dee870ea66d03e89ac321eee28ea63a9b9aa45
Binary files differ