[systemd-commits] 30 commits - configure.ac .gitignore Makefile.am Makefile-man.am man/systemd-journal-remote.xml man/systemd-journal-upload.xml src/bootchart src/core src/dbus1-generator src/journal src/journal-remote src/login src/network src/resolve src/shared src/test src/timesync src/tty-ask-password-agent src/udev tmpfiles.d/systemd-remote.conf units/.gitignore units/systemd-journal-remote.service.in units/systemd-journal-remote.socket units/systemd-journal-upload.service.in

Zbigniew Jędrzejewski-Szmek zbyszek at kemper.freedesktop.org
Tue Jul 15 19:51:16 PDT 2014


 .gitignore                                          |    5 
 Makefile-man.am                                     |    4 
 Makefile.am                                         |   95 +
 configure.ac                                        |   22 
 man/systemd-journal-remote.xml                      |   68 
 man/systemd-journal-upload.xml                      |  193 ++
 src/bootchart/bootchart.c                           |    2 
 src/core/load-dropin.c                              |    2 
 src/core/load-fragment.c                            |    2 
 src/core/main.c                                     |    2 
 src/dbus1-generator/dbus1-generator.c               |    2 
 src/journal-remote/.gitignore                       |    2 
 src/journal-remote/Makefile                         |    1 
 src/journal-remote/browse.html                      |  544 +++++++
 src/journal-remote/journal-gatewayd.c               | 1054 +++++++++++++
 src/journal-remote/journal-remote-parse.c           |  493 ++++++
 src/journal-remote/journal-remote-parse.h           |   68 
 src/journal-remote/journal-remote-write.c           |  169 ++
 src/journal-remote/journal-remote-write.h           |   74 
 src/journal-remote/journal-remote.c                 | 1550 ++++++++++++++++++++
 src/journal-remote/journal-remote.conf.in           |    5 
 src/journal-remote/journal-remote.h                 |   30 
 src/journal-remote/journal-upload-journal.c         |  402 +++++
 src/journal-remote/journal-upload.c                 |  826 ++++++++++
 src/journal-remote/journal-upload.conf.in           |    5 
 src/journal-remote/journal-upload.h                 |   68 
 src/journal-remote/log-generator.py                 |   68 
 src/journal-remote/microhttpd-util.c                |  303 +++
 src/journal-remote/microhttpd-util.h                |   55 
 src/journal/browse.html                             |  544 -------
 src/journal/coredump.c                              |    6 
 src/journal/journal-file.c                          |    6 
 src/journal/journal-gatewayd.c                      | 1054 -------------
 src/journal/journal-remote-parse.c                  |  439 -----
 src/journal/journal-remote-parse.h                  |   61 
 src/journal/journal-remote-write.c                  |  124 -
 src/journal/journal-remote-write.h                  |   51 
 src/journal/journal-remote.c                        | 1239 ---------------
 src/journal/journalctl.c                            |    6 
 src/journal/journald-native.c                       |   21 
 src/journal/journald-native.h                       |    4 
 src/journal/journald-server.c                       |    2 
 src/journal/microhttpd-util.c                       |  268 ---
 src/journal/microhttpd-util.h                       |   54 
 src/journal/test-compress-benchmark.c               |    8 
 src/login/logind.c                                  |    2 
 src/network/networkd-netdev.c                       |    2 
 src/network/networkd-network.c                      |    6 
 src/resolve/resolved-dns-packet.c                   |    8 
 src/resolve/resolved-manager.c                      |    5 
 src/shared/conf-parser.c                            |   12 
 src/shared/conf-parser.h                            |    8 
 src/shared/install.c                                |    2 
 src/shared/sleep-config.c                           |    2 
 src/shared/socket-label.c                           |    2 
 src/shared/socket-util.c                            |   50 
 src/shared/socket-util.h                            |    3 
 src/test/test-socket-util.c                         |   55 
 src/timesync/timesyncd.c                            |    2 
 src/tty-ask-password-agent/tty-ask-password-agent.c |    2 
 src/udev/net/link-config.c                          |    6 
 tmpfiles.d/systemd-remote.conf                      |   11 
 units/.gitignore                                    |    8 
 units/systemd-journal-remote.service.in             |   24 
 units/systemd-journal-remote.socket                 |   15 
 units/systemd-journal-upload.service.in             |   22 
 66 files changed, 6310 insertions(+), 3938 deletions(-)

New commits:
commit 4e0296a943e85316ecce0324248f9543887b8a9a
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Tue Jul 15 22:47:03 2014 -0400

    resolve: avoid use of uninitalized variable

diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index 2a66692..a503b01 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -594,7 +594,7 @@ fail:
 }
 
 int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
-        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr;
         size_t saved_rindex, offset;
         uint16_t rdlength;
         const void *d;
@@ -603,11 +603,11 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
         assert(p);
         assert(ret);
 
-        saved_rindex = p->rindex;
-
         rr = dns_resource_record_new();
         if (!rr)
-                goto fail;
+                return -ENOMEM;
+
+        saved_rindex = p->rindex;
 
         r = dns_packet_read_key(p, &rr->key, NULL);
         if (r < 0)

commit da2e1c71662c348d31f0fc39e42963283c550a28
Author: Marc-Antoine Perennou <Marc-Antoine at Perennou.com>
Date:   Wed Jul 16 10:13:06 2014 +0900

    test-compress-benchmark: silence warnings
    
    and btw make it pass for 32bits where size_t != uint64_t

diff --git a/src/journal/test-compress-benchmark.c b/src/journal/test-compress-benchmark.c
index 0a23bd1..a346447 100644
--- a/src/journal/test-compress-benchmark.c
+++ b/src/journal/test-compress-benchmark.c
@@ -42,12 +42,12 @@ static char* make_buf(size_t count) {
 
 static void test_compress_decompress(const char* label,
                                      compress_t compress, decompress_t decompress) {
-        usec_t n, n2;
+        usec_t n, n2 = 0;
         float dt;
 
         _cleanup_free_ char *text, *buf;
         _cleanup_free_ void *buf2 = NULL;
-        size_t buf2_allocated = 0;
+        uint64_t buf2_allocated = 0;
         size_t skipped = 0, compressed = 0, total = 0;
 
         text = make_buf(MAX_SIZE);
@@ -57,7 +57,7 @@ static void test_compress_decompress(const char* label,
         n = now(CLOCK_MONOTONIC);
 
         for (size_t i = 1; i <= MAX_SIZE; i += (i < 2048 ? 1 : 217)) {
-                size_t j = 0, k = 0;
+                uint64_t j = 0, k = 0;
                 int r;
 
                 r = compress(text, i, buf, &j);
@@ -72,7 +72,7 @@ static void test_compress_decompress(const char* label,
 
                 assert(j > 0);
                 if (j >= i)
-                        log_error("%s \"compressed\" %zu -> %zu", label, i, j);
+                        log_error("%s \"compressed\" %zu -> " PRIu64, label, i, j);
 
                 r = decompress(buf, j, &buf2, &buf2_allocated, &k, 0);
                 assert(r == 0);

commit 92b10cbccdeef3896f45dc340eb7779c78577ede
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Fri Jul 11 23:20:08 2014 -0400

    journal-remote: avoid copying input data
    
    Instead of copying fields into new memory allocations, simply keep pointers
    into the receive buffer. Data in this buffer is only copied when there is not
    enough space for new data and a large chunk of the buffer contains old data.

diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c
index cdc920e..dfb87d4 100644
--- a/src/journal-remote/journal-remote-parse.c
+++ b/src/journal-remote/journal-remote-parse.c
@@ -70,24 +70,38 @@ RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) {
         return source;
 }
 
+static char* realloc_buffer(RemoteSource *source, size_t size) {
+        char *b, *old = source->buf;
+
+        b = GREEDY_REALLOC(source->buf, source->size, size);
+        if (!b)
+                return NULL;
+
+        iovw_rebase(&source->iovw, old, source->buf);
+
+        return b;
+}
+
 static int get_line(RemoteSource *source, char **line, size_t *size) {
-        ssize_t n, remain;
+        ssize_t n;
         char *c = NULL;
-        char *newbuf = NULL;
-        size_t newsize = 0;
 
         assert(source);
         assert(source->state == STATE_LINE);
+        assert(source->offset <= source->filled);
         assert(source->filled <= source->size);
         assert(source->buf == NULL || source->size > 0);
         assert(source->fd >= 0);
 
         while (true) {
-                if (source->buf)
-                        c = memchr(source->buf + source->scanned, '\n',
-                                   source->filled - source->scanned);
-                if (c != NULL)
-                        break;
+                if (source->buf) {
+                        size_t start = MAX(source->scanned, source->offset);
+
+                        c = memchr(source->buf + start, '\n',
+                                   source->filled - start);
+                        if (c != NULL)
+                                break;
+                }
 
                 source->scanned = source->filled;
                 if (source->scanned >= DATA_SIZE_MAX) {
@@ -100,15 +114,12 @@ static int get_line(RemoteSource *source, char **line, size_t *size) {
                         return -EWOULDBLOCK;
 
                 if (source->size - source->filled < LINE_CHUNK &&
-                    !GREEDY_REALLOC(source->buf, source->size,
-                                    MIN(source->filled + LINE_CHUNK, DATA_SIZE_MAX)))
+                    !realloc_buffer(source,
+                                    MIN(source->filled + LINE_CHUNK, ENTRY_SIZE_MAX)))
                                 return log_oom();
 
                 assert(source->size - source->filled >= LINE_CHUNK ||
-                       source->size == DATA_SIZE_MAX);
-
-                // FIXME: the buffer probably needs to be bigger than DATA_SIZE_MAX
-                // to accomodate such big fields.
+                       source->size == ENTRY_SIZE_MAX);
 
                 n = read(source->fd, source->buf + source->filled,
                          source->size - source->filled);
@@ -123,23 +134,9 @@ static int get_line(RemoteSource *source, char **line, size_t *size) {
                 source->filled += n;
         }
 
-        *line = source->buf;
-        *size = c + 1 - source->buf;
-
-        /* Check if something remains */
-        remain = source->buf + source->filled - c - 1;
-        assert(remain >= 0);
-        if (remain) {
-                newsize = MAX(remain, LINE_CHUNK);
-                newbuf = malloc(newsize);
-                if (!newbuf)
-                        return log_oom();
-                memcpy(newbuf, c + 1, remain);
-        }
-        source->buf = newbuf;
-        source->size = newsize;
-        source->filled = remain;
-        source->scanned = 0;
+        *line = source->buf + source->offset;
+        *size = c + 1 - source->buf - source->offset;
+        source->offset += *size;
 
         return 1;
 }
@@ -148,8 +145,7 @@ int push_data(RemoteSource *source, const char *data, size_t size) {
         assert(source);
         assert(source->state != STATE_EOF);
 
-        if (!GREEDY_REALLOC(source->buf, source->size,
-                            source->filled + size)) {
+        if (!realloc_buffer(source, source->filled + size)) {
                 log_error("Failed to store received data of size %zu "
                           "(in addition to existing %zu bytes with %zu filled): %s",
                           size, source->size, source->filled, strerror(ENOMEM));
@@ -163,28 +159,27 @@ int push_data(RemoteSource *source, const char *data, size_t size) {
 }
 
 static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
-        int n;
-        char *newbuf = NULL;
-        size_t newsize = 0, remain;
 
         assert(source);
         assert(source->state == STATE_DATA_START ||
                source->state == STATE_DATA ||
                source->state == STATE_DATA_FINISH);
         assert(size <= DATA_SIZE_MAX);
+        assert(source->offset <= source->filled);
         assert(source->filled <= source->size);
-        assert(source->scanned <= source->filled);
         assert(source->buf != NULL || source->size == 0);
         assert(source->buf == NULL || source->size > 0);
         assert(source->fd >= 0);
         assert(data);
 
-        while(source->filled < size) {
+        while (source->filled - source->offset < size) {
+                int n;
+
                 if (source->passive_fd)
                         /* we have to wait for some data to come to us */
                         return -EWOULDBLOCK;
 
-                if (!GREEDY_REALLOC(source->buf, source->size, size))
+                if (!realloc_buffer(source, source->offset + size))
                         return log_oom();
 
                 n = read(source->fd, source->buf + source->filled,
@@ -200,29 +195,15 @@ static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
                 source->filled += n;
         }
 
-        *data = source->buf;
-
-        /* Check if something remains */
-        assert(size <= source->filled);
-        remain = source->filled - size;
-        if (remain) {
-                newsize = MAX(remain, LINE_CHUNK);
-                newbuf = malloc(newsize);
-                if (!newbuf)
-                        return log_oom();
-                memcpy(newbuf, source->buf + size, remain);
-        }
-        source->buf = newbuf;
-        source->size = newsize;
-        source->filled = remain;
-        source->scanned = 0;
+        *data = source->buf + source->offset;
+        source->offset += size;
 
         return 1;
 }
 
 static int get_data_size(RemoteSource *source) {
         int r;
-        _cleanup_free_ void *data = NULL;
+        void *data;
 
         assert(source);
         assert(source->state == STATE_DATA_START);
@@ -260,7 +241,7 @@ static int get_data_data(RemoteSource *source, void **data) {
 
 static int get_data_newline(RemoteSource *source) {
         int r;
-        _cleanup_free_ char *data = NULL;
+        char *data;
 
         assert(source);
         assert(source->state == STATE_DATA_FINISH);
@@ -350,15 +331,12 @@ int process_data(RemoteSource *source) {
 
                 if (n == 1) {
                         log_debug("Received empty line, event is ready");
-                        free(line);
                         return 1;
                 }
 
                 r = process_dunder(source, line, n);
-                if (r != 0) {
-                        free(line);
+                if (r != 0)
                         return r < 0 ? r : 0;
-                }
 
                 /* MESSAGE=xxx\n
                    or
@@ -377,7 +355,6 @@ int process_data(RemoteSource *source) {
                 r = iovw_put(&source->iovw, line, n);
                 if (r < 0) {
                         log_error("Failed to put line in iovect");
-                        free(line);
                         return r;
                 }
 
@@ -450,6 +427,7 @@ int process_data(RemoteSource *source) {
 }
 
 int process_source(RemoteSource *source, bool compress, bool seal) {
+        size_t remain, target;
         int r;
 
         assert(source);
@@ -480,5 +458,36 @@ int process_source(RemoteSource *source, bool compress, bool seal) {
 
  freeing:
         iovw_free_contents(&source->iovw);
+
+        /* possibly reset buffer position */
+        remain = source->filled - source->offset;
+
+        if (remain == 0) /* no brainer */
+                source->offset = source->scanned = source->filled = 0;
+        else if (source->offset > source->size - source->filled &&
+                 source->offset > remain) {
+                memcpy(source->buf, source->buf + source->offset, remain);
+                source->offset = source->scanned = 0;
+                source->filled = remain;
+        }
+
+        target = source->size;
+        while (target > 16 * LINE_CHUNK && remain < target / 2)
+                target /= 2;
+        if (target < source->size) {
+                char *tmp;
+
+                tmp = realloc(source->buf, target);
+                if (tmp)
+                        log_warning("Failed to reallocate buffer to (smaller) size %zu",
+                                    target);
+                else {
+                        log_debug("Reallocated buffer from %zu to %zu bytes",
+                                  source->size, target);
+                        source->buf = tmp;
+                        source->size = target;
+                }
+        }
+
         return r;
 }
diff --git a/src/journal-remote/journal-remote-parse.h b/src/journal-remote/journal-remote-parse.h
index 07d6ddb..8499f4e 100644
--- a/src/journal-remote/journal-remote-parse.h
+++ b/src/journal-remote/journal-remote-parse.h
@@ -38,10 +38,11 @@ typedef struct RemoteSource {
         bool passive_fd;
 
         char *buf;
-        size_t size;
-        size_t scanned;
-        size_t filled;
-        size_t data_size;
+        size_t size;       /* total size of the buffer */
+        size_t offset;     /* offset to the beginning of live data in the buffer */
+        size_t scanned;    /* number of bytes since the beginning of data without a newline */
+        size_t filled;     /* total number of bytes in the buffer */
+        size_t data_size;  /* size of the binary data chunk being processed */
 
         struct iovec_wrapper iovw;
 
diff --git a/src/journal-remote/journal-remote-write.c b/src/journal-remote/journal-remote-write.c
index cdd06f9..bec4cb1 100644
--- a/src/journal-remote/journal-remote-write.c
+++ b/src/journal-remote/journal-remote-write.c
@@ -31,8 +31,6 @@ int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
 }
 
 void iovw_free_contents(struct iovec_wrapper *iovw) {
-        for (size_t j = 0; j < iovw->count; j++)
-                free(iovw->iovec[j].iov_base);
         free(iovw->iovec);
         iovw->iovec = NULL;
         iovw->size_bytes = iovw->count = 0;
@@ -41,12 +39,19 @@ void iovw_free_contents(struct iovec_wrapper *iovw) {
 size_t iovw_size(struct iovec_wrapper *iovw) {
         size_t n = 0, i;
 
-        for(i = 0; i < iovw->count; i++)
+        for (i = 0; i < iovw->count; i++)
                 n += iovw->iovec[i].iov_len;
 
         return n;
 }
 
+void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) {
+        size_t i;
+
+        for (i = 0; i < iovw->count; i++)
+                iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new;
+}
+
 /**********************************************************************
  **********************************************************************
  **********************************************************************/
diff --git a/src/journal-remote/journal-remote-write.h b/src/journal-remote/journal-remote-write.h
index 9c5a641..aa381c6 100644
--- a/src/journal-remote/journal-remote-write.h
+++ b/src/journal-remote/journal-remote-write.h
@@ -36,6 +36,7 @@ struct iovec_wrapper {
 int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len);
 void iovw_free_contents(struct iovec_wrapper *iovw);
 size_t iovw_size(struct iovec_wrapper *iovw);
+void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new);
 
 typedef struct Writer {
         JournalFile *journal;

commit 874bc134ac6504c45e94174e37af13ff21a6bfe2
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Mon Jul 14 16:53:23 2014 -0400

    Clear up confusion wrt. ENTRY_SIZE_MAX and DATA_SIZE_MAX
    
    Define DATA_SIZE_MAX to mean the maximum size of a single
    field, and ENTRY_SIZE_MAX to mean the size of the whole
    entry, with some rough calculation of overhead over the payload.
    
    Check if entries are not too big when processing native journal
    messages.

diff --git a/src/journal/coredump.c b/src/journal/coredump.c
index cd612b7..4ac1a41 100644
--- a/src/journal/coredump.c
+++ b/src/journal/coredump.c
@@ -61,8 +61,8 @@
 #define JOURNAL_SIZE_MAX ((size_t) (767LU*1024LU*1024LU))
 
 /* Make sure to not make this larger than the maximum journal entry
- * size. See ENTRY_SIZE_MAX in journald-native.c. */
-assert_cc(JOURNAL_SIZE_MAX <= ENTRY_SIZE_MAX);
+ * size. See DATA_SIZE_MAX in journald-native.c. */
+assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX);
 
 enum {
         INFO_PID,
diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c
index c54f647..6674f3b 100644
--- a/src/journal/journald-native.c
+++ b/src/journal/journald-native.c
@@ -82,7 +82,7 @@ void server_process_native_message(
         struct iovec *iovec = NULL;
         unsigned n = 0, j, tn = (unsigned) -1;
         const char *p;
-        size_t remaining, m = 0;
+        size_t remaining, m = 0, entry_size = 0;
         int priority = LOG_INFO;
         char *identifier = NULL, *message = NULL;
         pid_t object_pid = 0;
@@ -106,9 +106,17 @@ void server_process_native_message(
 
                 if (e == p) {
                         /* Entry separator */
+
+                        if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */
+                                log_debug("Entry is too big with %u properties and %zu bytes, ignoring.",
+                                          n, entry_size);
+                                continue;
+                        }
+
                         server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid);
                         n = 0;
                         priority = LOG_INFO;
+                        entry_size = 0;
 
                         p++;
                         remaining--;
@@ -146,6 +154,7 @@ void server_process_native_message(
                                 iovec[n].iov_base = (char*) p;
                                 iovec[n].iov_len = l;
                                 n++;
+                                entry_size += iovec[n].iov_len;
 
                                 /* We need to determine the priority
                                  * of this entry for the rate limiting
@@ -214,7 +223,7 @@ void server_process_native_message(
                         l = le64toh(l_le);
 
                         if (l > DATA_SIZE_MAX) {
-                                log_debug("Received binary data block too large, ignoring.");
+                                log_debug("Received binary data block of %zu bytes is too large, ignoring.", l);
                                 break;
                         }
 
@@ -238,6 +247,7 @@ void server_process_native_message(
                                 iovec[n].iov_base = k;
                                 iovec[n].iov_len = (e - p) + 1 + l;
                                 n++;
+                                entry_size += iovec[n].iov_len;
                         } else
                                 free(k);
 
@@ -251,6 +261,13 @@ void server_process_native_message(
 
         tn = n++;
         IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal");
+        entry_size += strlen("_TRANSPORT=journal");
+
+        if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */
+                log_debug("Entry is too big with %u properties and %zu bytes, ignoring.",
+                          n, entry_size);
+                goto finish;
+        }
 
         if (message) {
                 if (s->forward_to_syslog)
diff --git a/src/journal/journald-native.h b/src/journal/journald-native.h
index 97808e7..e82a5b8 100644
--- a/src/journal/journald-native.h
+++ b/src/journal/journald-native.h
@@ -25,7 +25,7 @@
 
 /* Make sure not to make this smaller than the maximum coredump
  * size. See COREDUMP_MAX in coredump.c */
-#define ENTRY_SIZE_MAX (1024*1024*768u)
+#define ENTRY_SIZE_MAX (1024*1024*770u)
 #define DATA_SIZE_MAX (1024*1024*768u)
 
 bool valid_user_field(const char *p, size_t l, bool allow_protected);

commit 93c0969cf91c4e4973806181a6098b66a4e8e2f8
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Fri Jul 11 23:17:57 2014 -0400

    µhttp-util: fix compilation without gnutls

diff --git a/src/journal-remote/microhttpd-util.c b/src/journal-remote/microhttpd-util.c
index 5335493..55c45f4 100644
--- a/src/journal-remote/microhttpd-util.c
+++ b/src/journal-remote/microhttpd-util.c
@@ -297,7 +297,7 @@ int check_permissions(struct MHD_Connection *connection, int *code, char **hostn
 }
 
 #else
-int check_permissions(struct MHD_Connection *connection, int *code) {
+int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
         return -EPERM;
 }
 #endif

commit a8ca47227d1ab716ba928d8b9334b655ca5a840c
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Thu Jul 10 15:03:28 2014 -0400

    man: describe new filename rules for journal-remote

diff --git a/man/systemd-journal-remote.xml b/man/systemd-journal-remote.xml
index 4107714..4065ced 100644
--- a/man/systemd-journal-remote.xml
+++ b/man/systemd-journal-remote.xml
@@ -62,12 +62,14 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
     <para>
       <filename>systemd-journal-remote</filename> is a command to
       receive serialized journal events and store them to the journal.
-      Input streams must be in the
+      Input streams are in the
       <ulink url="http://www.freedesktop.org/wiki/Software/systemd/export">
         Journal Export Format
       </ulink>,
       i.e. like the output from
-      <command>journalctl --output=export</command>.
+      <command>journalctl --output=export</command>. For transport over
+      the network, this serialized stream is usually carried over an
+      HTTPS connection.
     </para>
   </refsect1>
 
@@ -184,17 +186,18 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
     <title>Sinks</title>
 
     <para>The location of the output journal can be specified
-    with <option>-o</option> or <option>--output=</option>.
+    with <option>-o</option> or <option>--output=</option>. For "active"
+    sources, this option is required.
     </para>
 
     <variablelist>
       <varlistentry>
         <term><option>--output=<replaceable>FILE</replaceable></option></term>
 
-        <listitem><para>Will write to this journal. The filename must
-        end with <filename>.journal</filename>. The file will be
-        created if it does not exist. If necessary (journal file
-        full, or corrupted), the file will be renamed following normal
+        <listitem><para>Will write to this journal file. The filename
+        must end with <filename>.journal</filename>. The file will be
+        created if it does not exist. If necessary (journal file full,
+        or corrupted), the file will be renamed following normal
         journald rules and a new journal file will be created in its
         stead.</para></listitem>
       </varlistentry>
@@ -211,24 +214,18 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
       </varlistentry>
     </variablelist>
 
-    <para>If <option>--output=</option> is not used, the output directory
-    <filename>/var/log/journal/<replaceable>machine-id</replaceable>/</filename>
-    will be used, where <replaceable>machine-id</replaceable> is the
-    identifier of the current system (see
-    <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
-    In case the output file is not specified, journal files will be
-    created underneath the selected directory. Files will be called
-    <filename>remote-<replaceable>variable</replaceable>.journal</filename>,
-    where the <replaceable>variable</replaceable> part is generated
-    based on what passive and active sources are specified. It is
-    recommended to give a full output filename.</para>
-
-    <para>In case of "active" sources, if the hostname is known, it
-    will be used in the <replaceable>variable</replaceable> part.
-    Otherwise, local address and port number will be used, or
-    <literal>stdin</literal> for events passed over standard
-    input, and <literal>multiple</literal> if more than one source
-    is specified.</para>
+    <para>If <option>--output=</option> is not used, the output
+    directory <filename>/var/log/journal/remote/</filename> will be
+    used.  In case the output file is not specified, journal files
+    will be created underneath the selected directory. Files will be
+    called
+    <filename>remote-<replaceable>hostname</replaceable>.journal</filename>,
+    where the <replaceable>hostname</replaceable> part is is the
+    escaped hostname of the source endpoint of the connection, or the
+    numerical address if the hostname cannot be determined.</para>
+
+    <para>In case of "active" sources, the output file name must
+    always be given explicitly.</para>
   </refsect1>
 
   <refsect1>

commit 330427e271c37400f091bf4570b5d8fa96574d36
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Thu Jul 10 14:50:50 2014 -0400

    man: document systemd-journal-upload

diff --git a/Makefile-man.am b/Makefile-man.am
index 3462c2a..4339e50 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -1247,7 +1247,8 @@ endif
 if HAVE_MICROHTTPD
 MANPAGES += \
 	man/systemd-journal-gatewayd.service.8 \
-	man/systemd-journal-remote.8
+	man/systemd-journal-remote.8 \
+	man/systemd-journal-upload.8
 MANPAGES_ALIAS += \
 	man/systemd-journal-gatewayd.8 \
 	man/systemd-journal-gatewayd.socket.8
@@ -1621,6 +1622,7 @@ EXTRA_DIST += \
 	man/systemd-initctl.service.xml \
 	man/systemd-journal-gatewayd.service.xml \
 	man/systemd-journal-remote.xml \
+	man/systemd-journal-upload.xml \
 	man/systemd-journald.service.xml \
 	man/systemd-localed.service.xml \
 	man/systemd-logind.service.xml \
diff --git a/man/systemd-journal-remote.xml b/man/systemd-journal-remote.xml
index a6e67e5..4107714 100644
--- a/man/systemd-journal-remote.xml
+++ b/man/systemd-journal-remote.xml
@@ -44,14 +44,14 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
 
   <refnamediv>
     <refname>systemd-journal-remote</refname>
-    <refpurpose>Stream journal messages over the network</refpurpose>
+    <refpurpose>Receive journal messages over the network</refpurpose>
   </refnamediv>
 
   <refsynopsisdiv>
     <cmdsynopsis>
       <command>systemd-journal-remote</command>
       <arg choice="opt" rep="repeat">OPTIONS</arg>
-      <arg choice="opt" rep="norepeat">-o/--output=DIR|FILE</arg>
+      <arg choice="opt" rep="norepeat">-o/--output=<replaceable>DIR</replaceable>|<replaceable>FILE</replaceable></arg>
       <arg choice="opt" rep="repeat">SOURCES</arg>
     </cmdsynopsis>
   </refsynopsisdiv>
@@ -320,9 +320,10 @@ systemd-journal-remote --url http://some.host:19531/
   <refsect1>
     <title>See Also</title>
     <para>
+      <citerefentry><refentrytitle>systemd-journal-upload</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
-      <citerefentry><refentrytitle>systemd-journal-gatewayd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+      <citerefentry><refentrytitle>systemd-journal-gatewayd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
     </para>
   </refsect1>
 </refentry>
diff --git a/man/systemd-journal-upload.xml b/man/systemd-journal-upload.xml
new file mode 100644
index 0000000..ca251c6
--- /dev/null
+++ b/man/systemd-journal-upload.xml
@@ -0,0 +1,193 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+This file is part of systemd.
+
+Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+systemd is free software; you can redistribute it and/or modify it
+under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+systemd is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-journal-upload" conditional='HAVE_MICROHTTPD'
+          xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>systemd-journal-upload</title>
+    <productname>systemd</productname>
+
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Zbigniew</firstname>
+        <surname>Jędrzejewski-Szmek</surname>
+        <email>zbyszek at in.waw.pl</email>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-journal-upload</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-journal-upload</refname>
+    <refpurpose>Send journal messages over the network</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>systemd-journal-upload</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="opt" rep="norepeat">-u/--url=<replaceable>URL</replaceable></arg>
+      <arg choice="opt" rep="repeat">SOURCES</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para>
+      <command>systemd-journal-upload</command> will upload journal
+      entries to the URL specified with <option>--url</option>. Unless
+      limited by one of the options specified below, all journal
+      entries accessible to the user the program is running as will be
+      uploaded, and then the program will wait and send new entries
+      as they become available.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>-u</option></term>
+        <term><option>--url=<optional>https://</optional><replaceable>URL</replaceable></option></term>
+        <term><option>--url=<optional>http://</optional><replaceable>URL</replaceable></option></term>
+
+        <listitem><para>Upload to the specified
+        address. <replaceable>URL</replaceable> may specify either
+        just the hostname or both the protocol and
+        hostname. <constant>https</constant> is the default.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--system</option></term>
+        <term><option>--user</option></term>
+
+        <listitem><para>Limit uploaded entries to entries from system
+        services and the kernel, or to entries from services of
+        current user. This has the same meaning as
+        <option>--system</option> and <option>--user</option> options
+        for
+        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>. If
+        neither is specified, all accessible entries are uploaded.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-m</option></term>
+        <term><option>--merge</option></term>
+
+        <listitem><para>Upload entries interleaved from all available
+        journals, including other machines. This has the same meaning
+        as <option>--merge</option> option for
+        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-D</option></term>
+        <term><option>--directory=<replaceable>DIR</replaceable></option></term>
+
+        <listitem><para>Takes a directory path as argument. Upload
+        entries from the specified journal directory
+        <replaceable>DIR</replaceable> instead of the default runtime
+        and system journal paths. This has the same meaning as
+        <option>--directory</option> option for
+        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--file=<replaceable>GLOB</replaceable></option></term>
+
+        <listitem><para>Takes a file glob as an argument. Upload
+        entries from the specified journal files matching
+        <replaceable>GLOB</replaceable> instead of the default runtime
+        and system journal paths. May be specified multiple times, in
+        which case files will be suitably interleaved. This has the same meaning as
+        <option>--file</option> option for
+        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--cursor=</option></term>
+
+        <listitem><para>Upload entries from the location in the
+        journal specified by the passed cursor. This has the same
+        meaning as <option>--cursor</option> option for
+        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--after-cursor=</option></term>
+
+        <listitem><para>Upload entries from the location in the
+        journal <emphasis>after</emphasis> the location specified by
+        the this cursor.  This has the same meaning as
+        <option>--after-cursor</option> option for
+        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+        </para></listitem>
+      </varlistentry>
+
+
+      <varlistentry>
+        <term><option>--save-state</option><optional>=<replaceable>PATH</replaceable></optional></term>
+
+        <listitem><para>Upload entries from the location in the
+        journal <emphasis>after</emphasis> the location specified by
+        the cursor saved in file at <replaceable>PATH</replaceable>
+        (<filename>/var/lib/systemd/journal-upload/state</filename> by default).
+        After an entry is successfully uploaded, update this file
+        with the cursor of that entry.
+        </para></listitem>
+      </varlistentry>
+
+      <xi:include href="standard-options.xml" xpointer="help" />
+      <xi:include href="standard-options.xml" xpointer="version" />
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Exit status</title>
+
+    <para>On success, 0 is returned; otherwise, a non-zero
+    failure code is returned.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-journal-gatewayd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+</refentry>

commit 29fc0ddcd737af906986d4029579d4dfe838ba02
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Tue Jul 15 22:22:05 2014 -0400

    journal-upload: add config file

diff --git a/Makefile.am b/Makefile.am
index cc1d423..a492a1f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3531,6 +3531,15 @@ nodist_systemunit_DATA += \
 
 EXTRA_DIST += \
 	units/systemd-journal-upload.service.in
+
+nodist_pkgsysconf_DATA += \
+	src/journal-remote/journal-upload.conf
+
+EXTRA_DIST += \
+	src/journal-remote/journal-upload.conf.in
+
+CLEANFILES += \
+	src/journal-remote/journal-upload.conf
 endif
 
 # using _CFLAGS = in the conditional below would suppress AM_CFLAGS
diff --git a/src/journal-remote/.gitignore b/src/journal-remote/.gitignore
index 8112c3c..06847b6 100644
--- a/src/journal-remote/.gitignore
+++ b/src/journal-remote/.gitignore
@@ -1 +1,2 @@
 /journal-remote.conf
+/journal-upload.conf
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index c5a3e19..a381ec5 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -31,8 +31,13 @@
 #include "util.h"
 #include "build.h"
 #include "fileio.h"
+#include "conf-parser.h"
 #include "journal-upload.h"
 
+#define KEY_FILE   CERTIFICATE_ROOT "/private/journal-upload.pem"
+#define CERT_FILE  CERTIFICATE_ROOT "/certs/journal-upload.pem"
+#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
+
 static const char* arg_url;
 
 static void close_fd_input(Uploader *u);
@@ -214,17 +219,17 @@ int start_upload(Uploader *u,
                             "systemd-journal-upload " PACKAGE_STRING,
                             LOG_WARNING, );
 
-                if (arg_key) {
+                if (arg_key || startswith(u->url, "https://")) {
                         assert(arg_cert);
 
-                        easy_setopt(curl, CURLOPT_SSLKEY, arg_key,
+                        easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: KEY_FILE,
                                     LOG_ERR, return -EXFULL);
-                        easy_setopt(curl, CURLOPT_SSLCERT, arg_cert,
+                        easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
                                     LOG_ERR, return -EXFULL);
                 }
 
-                if (arg_trust)
-                        easy_setopt(curl, CURLOPT_CAINFO, arg_trust,
+                if (arg_trust || startswith(u->url, "https://"))
+                        easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
                                     LOG_ERR, return -EXFULL);
 
                 if (arg_key || arg_trust)
@@ -483,6 +488,25 @@ static int perform_upload(Uploader *u) {
         return update_cursor_state(u);
 }
 
+static int parse_config(void) {
+        const ConfigTableItem items[] = {
+                { "Upload",  "URL",                    config_parse_string, 0, &arg_url    },
+                { "Upload",  "ServerKeyFile",          config_parse_path,   0, &arg_key    },
+                { "Upload",  "ServerCertificateFile",  config_parse_path,   0, &arg_cert   },
+                { "Upload",  "TrustedCertificateFile", config_parse_path,   0, &arg_trust  },
+                {}};
+        int r;
+
+        r = config_parse(NULL, PKGSYSCONFDIR "/journal-upload.conf", NULL,
+                         "Upload\0",
+                         config_item_table_lookup, items,
+                         false, false, NULL);
+        if (r < 0)
+                log_error("Failed to parse configuration file: %s", strerror(-r));
+
+        return r;
+}
+
 static void help(void) {
         printf("%s -u URL {FILE|-}...\n\n"
                "Upload journal events to a remote server.\n\n"
@@ -723,6 +747,10 @@ int main(int argc, char **argv) {
         log_show_color(true);
         log_parse_environment();
 
+        r = parse_config();
+        if (r <= 0)
+                goto finish;
+
         r = parse_argv(argc, argv);
         if (r <= 0)
                 goto finish;
diff --git a/src/journal-remote/journal-upload.conf.in b/src/journal-remote/journal-upload.conf.in
new file mode 100644
index 0000000..c567068
--- /dev/null
+++ b/src/journal-remote/journal-upload.conf.in
@@ -0,0 +1,5 @@
+[Upload]
+# URL=
+# ServerKeyFile=@CERTIFICATEROOT@/private/journal-upload.pem
+# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-upload.pem
+# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem

commit 5bc891206dd8eb4e4df58f502b0184b8426caf22
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Thu Jul 10 01:39:49 2014 -0400

    journal-remote: let user specify just the main part of the url
    
    We can append /upload ourselves.

diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index 95be9a0..c5a3e19 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -396,7 +396,13 @@ static int setup_uploader(Uploader *u, const char *url, const char *state_file)
         memzero(u, sizeof(Uploader));
         u->input = -1;
 
-        u->url = url;
+        if (!startswith(url, "http://") && !startswith(url, "https://"))
+                url = strappenda("https://", url);
+
+        u->url = strappend(url, "/upload");
+        if (!u->url)
+                return log_oom();
+
         u->state_file = state_file;
 
         r = sd_event_default(&u->events);
@@ -424,6 +430,8 @@ static void destroy_uploader(Uploader *u) {
         free(u->last_cursor);
         free(u->current_cursor);
 
+        free(u->url);
+
         u->input_event = sd_event_source_unref(u->input_event);
 
         close_fd_input(u);
diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h
index 9ccad10..3b46fa8 100644
--- a/src/journal-remote/journal-upload.h
+++ b/src/journal-remote/journal-upload.h
@@ -23,7 +23,7 @@ typedef struct Uploader {
         sd_event *events;
         sd_event_source *sigint_event, *sigterm_event;
 
-        const char *url;
+        char *url;
         CURL *easy;
         bool uploading;
         char error[CURL_ERROR_SIZE];

commit 9ff48d0982fcb97923955685fe9fa4e0e67cb238
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Wed Jul 2 00:15:37 2014 -0400

    journal-remote: rework fd and writer reference handling

diff --git a/Makefile.am b/Makefile.am
index 9845836..cc1d423 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3455,6 +3455,7 @@ systemd_journal_remote_SOURCES = \
 	src/journal-remote/journal-remote-parse.c \
 	src/journal-remote/journal-remote-write.h \
 	src/journal-remote/journal-remote-write.c \
+	src/journal-remote/journal-remote.h \
 	src/journal-remote/journal-remote.c
 
 systemd_journal_remote_LDADD = \
diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c
index 90d50a7..cdc920e 100644
--- a/src/journal-remote/journal-remote-parse.c
+++ b/src/journal-remote/journal-remote-parse.c
@@ -28,19 +28,48 @@ void source_free(RemoteSource *source) {
         if (!source)
                 return;
 
-        if (source->fd >= 0) {
+        if (source->fd >= 0 && !source->passive_fd) {
                 log_debug("Closing fd:%d (%s)", source->fd, source->name);
                 safe_close(source->fd);
         }
+
         free(source->name);
         free(source->buf);
         iovw_free_contents(&source->iovw);
 
+        log_debug("Writer ref count %u", source->writer->n_ref);
+        writer_unref(source->writer);
+
         sd_event_source_unref(source->event);
 
         free(source);
 }
 
+/**
+ * Initialize zero-filled source with given values. On success, takes
+ * ownerhship of fd 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)",
+                  passive_fd ? "passive " : "", fd, name);
+
+        assert(fd >= 0);
+
+        source = new0(RemoteSource, 1);
+        if (!source)
+                return NULL;
+
+        source->fd = fd;
+        source->passive_fd = passive_fd;
+        source->name = name;
+        source->writer = writer;
+
+        return source;
+}
+
 static int get_line(RemoteSource *source, char **line, size_t *size) {
         ssize_t n, remain;
         char *c = NULL;
@@ -51,6 +80,7 @@ static int get_line(RemoteSource *source, char **line, size_t *size) {
         assert(source->state == STATE_LINE);
         assert(source->filled <= source->size);
         assert(source->buf == NULL || source->size > 0);
+        assert(source->fd >= 0);
 
         while (true) {
                 if (source->buf)
@@ -65,7 +95,7 @@ static int get_line(RemoteSource *source, char **line, size_t *size) {
                         return -E2BIG;
                 }
 
-                if (source->fd < 0)
+                if (source->passive_fd)
                         /* we have to wait for some data to come to us */
                         return -EWOULDBLOCK;
 
@@ -146,10 +176,11 @@ static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
         assert(source->scanned <= source->filled);
         assert(source->buf != NULL || source->size == 0);
         assert(source->buf == NULL || source->size > 0);
+        assert(source->fd >= 0);
         assert(data);
 
         while(source->filled < size) {
-                if (source->fd < 0)
+                if (source->passive_fd)
                         /* we have to wait for some data to come to us */
                         return -EWOULDBLOCK;
 
@@ -418,11 +449,11 @@ int process_data(RemoteSource *source) {
         }
 }
 
-int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal) {
+int process_source(RemoteSource *source, bool compress, bool seal) {
         int r;
 
         assert(source);
-        assert(writer);
+        assert(source->writer);
 
         r = process_data(source);
         if (r <= 0)
@@ -440,7 +471,7 @@ int process_source(RemoteSource *source, Writer *writer, bool compress, bool sea
         assert(source->iovw.iovec);
         assert(source->iovw.count);
 
-        r = writer_write(writer, &source->iovw, &source->ts, compress, seal);
+        r = writer_write(source->writer, &source->iovw, &source->ts, compress, seal);
         if (r < 0)
                 log_error("Failed to write entry of %zu bytes: %s",
                           iovw_size(&source->iovw), strerror(-r));
diff --git a/src/journal-remote/journal-remote-parse.h b/src/journal-remote/journal-remote-parse.h
index 5b7f236..07d6ddb 100644
--- a/src/journal-remote/journal-remote-parse.h
+++ b/src/journal-remote/journal-remote-parse.h
@@ -35,6 +35,7 @@ typedef enum {
 typedef struct RemoteSource {
         char *name;
         int fd;
+        bool passive_fd;
 
         char *buf;
         size_t size;
@@ -47,9 +48,13 @@ typedef struct RemoteSource {
         source_state state;
         dual_timestamp ts;
 
+        Writer *writer;
+
         sd_event_source *event;
 } RemoteSource;
 
+RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer);
+
 static inline size_t source_non_empty(RemoteSource *source) {
         assert(source);
 
@@ -59,4 +64,4 @@ static inline size_t source_non_empty(RemoteSource *source) {
 void source_free(RemoteSource *source);
 int process_data(RemoteSource *source);
 int push_data(RemoteSource *source, const char *data, size_t size);
-int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal);
+int process_source(RemoteSource *source, bool compress, bool seal);
diff --git a/src/journal-remote/journal-remote-write.c b/src/journal-remote/journal-remote-write.c
index 3b00ff5..cdd06f9 100644
--- a/src/journal-remote/journal-remote-write.c
+++ b/src/journal-remote/journal-remote-write.c
@@ -19,6 +19,7 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include "journal-remote.h"
 #include "journal-remote-write.h"
 
 int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
@@ -64,32 +65,67 @@ static int do_rotate(JournalFile **f, bool compress, bool seal) {
         return r;
 }
 
-int writer_init(Writer *s) {
-        assert(s);
+Writer* writer_new(RemoteServer *server) {
+        Writer *w;
 
-        s->journal = NULL;
+        w = new0(Writer, 1);
+        if (!w)
+                return NULL;
 
-        memset(&s->metrics, 0xFF, sizeof(s->metrics));
+        memset(&w->metrics, 0xFF, sizeof(w->metrics));
 
-        s->mmap = mmap_cache_new();
-        if (!s->mmap)
-                return log_oom();
+        w->mmap = mmap_cache_new();
+        if (!w->mmap) {
+                free(w);
+                return NULL;
+        }
 
-        s->seqnum = 0;
+        w->n_ref = 1;
+        w->server = server;
 
-        return 0;
+        return w;
 }
 
-int writer_close(Writer *s) {
-        if (s->journal) {
-                journal_file_close(s->journal);
-                log_debug("Journal has been closed.");
+Writer* writer_free(Writer *w) {
+        if (!w)
+                return NULL;
+
+        if (w->journal) {
+                log_debug("Closing journal file %s.", w->journal->path);
+                journal_file_close(w->journal);
         }
-        if (s->mmap)
-                mmap_cache_unref(s->mmap);
-        return 0;
+
+        if (w->server) {
+                w->server->event_count += w->seqnum;
+                if (w->hashmap_key)
+                        hashmap_remove(w->server->writers, w->hashmap_key);
+        }
+
+        free(w->hashmap_key);
+
+        if (w->mmap)
+                mmap_cache_unref(w->mmap);
+
+        free(w);
+
+        return NULL;
+}
+
+Writer* writer_unref(Writer *w) {
+        if (w && (-- w->n_ref <= 0))
+                writer_free(w);
+
+        return NULL;
 }
 
+Writer* writer_ref(Writer *w) {
+        if (w)
+                assert_se(++ w->n_ref >= 2);
+
+        return w;
+}
+
+
 int writer_write(Writer *s,
                  struct iovec_wrapper *iovw,
                  dual_timestamp *ts,
diff --git a/src/journal-remote/journal-remote-write.h b/src/journal-remote/journal-remote-write.h
index 2ea5e67..9c5a641 100644
--- a/src/journal-remote/journal-remote-write.h
+++ b/src/journal-remote/journal-remote-write.h
@@ -25,6 +25,8 @@
 
 #include "journal-file.h"
 
+typedef struct RemoteServer RemoteServer;
+
 struct iovec_wrapper {
         struct iovec *iovec;
         size_t size_bytes;
@@ -38,12 +40,25 @@ size_t iovw_size(struct iovec_wrapper *iovw);
 typedef struct Writer {
         JournalFile *journal;
         JournalMetrics metrics;
+
         MMapCache *mmap;
+        RemoteServer *server;
+        char *hashmap_key;
+
         uint64_t seqnum;
+
+        int n_ref;
 } Writer;
 
-int writer_init(Writer *s);
-int writer_close(Writer *s);
+Writer* writer_new(RemoteServer* server);
+Writer* writer_free(Writer *w);
+
+Writer* writer_ref(Writer *w);
+Writer* writer_unref(Writer *w);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Writer*, writer_unref);
+#define _cleanup_writer_unref_ _cleanup_(writer_unrefp)
+
 int writer_write(Writer *s,
                  struct iovec_wrapper *iovw,
                  dual_timestamp *ts,
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index d37fac3..e127b0b 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -21,7 +21,6 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -33,7 +32,6 @@
 #include <getopt.h>
 
 #include "sd-daemon.h"
-#include "sd-event.h"
 #include "journal-file.h"
 #include "journald-native.h"
 #include "socket-util.h"
@@ -41,17 +39,15 @@
 #include "build.h"
 #include "macro.h"
 #include "strv.h"
-#include "hashmap.h"
 #include "fileio.h"
 #include "conf-parser.h"
-#include "microhttpd-util.h"
 #include "siphash24.h"
 
 #ifdef HAVE_GNUTLS
 #include <gnutls/gnutls.h>
 #endif
 
-#include "journal-remote-parse.h"
+#include "journal-remote.h"
 #include "journal-remote-write.h"
 
 #define REMOTE_JOURNAL_PATH "/var/log/journal/remote"
@@ -71,7 +67,7 @@ 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_NONE;
+static JournalWriteSplitMode arg_split_mode = JOURNAL_WRITE_SPLIT_HOST;
 static char* arg_output = NULL;
 
 static char *arg_key = NULL;
@@ -170,7 +166,7 @@ static int spawn_getter(const char *getter, const char *url) {
         return r;
 }
 
-#define filename_escape(s) xescape((s), "./ ")
+#define filename_escape(s) xescape((s), "/ ")
 
 static int open_output(Writer *w, const char* host) {
         _cleanup_free_ char *_output = NULL;
@@ -223,26 +219,78 @@ static int open_output(Writer *w, const char* host) {
  **********************************************************************
  **********************************************************************/
 
-typedef struct MHDDaemonWrapper {
-        uint64_t fd;
-        struct MHD_Daemon *daemon;
+static int init_writer_hashmap(RemoteServer *s) {
+        static const struct {
+                hash_func_t hash_func;
+                compare_func_t compare_func;
+        } functions[] = {
+                [JOURNAL_WRITE_SPLIT_NONE] = {trivial_hash_func,
+                                              trivial_compare_func},
+                [JOURNAL_WRITE_SPLIT_HOST] = {string_hash_func,
+                                              string_compare_func},
+        };
+
+        assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(functions));
 
-        sd_event_source *event;
-} MHDDaemonWrapper;
+        s->writers = hashmap_new(functions[arg_split_mode].hash_func,
+                                 functions[arg_split_mode].compare_func);
+        if (!s->writers)
+                return log_oom();
+
+        return 0;
+}
+
+static int get_writer(RemoteServer *s, const char *host,
+                      Writer **writer) {
+        const void *key;
+        _cleanup_writer_unref_ Writer *w = NULL;
+        int r;
+
+        switch(arg_split_mode) {
+        case JOURNAL_WRITE_SPLIT_NONE:
+                key = "one and only";
+                break;
+
+        case JOURNAL_WRITE_SPLIT_HOST:
+                assert(host);
+                key = host;
+                break;
+
+        default:
+                assert_not_reached("what split mode?");
+        }
+
+        w = hashmap_get(s->writers, key);
+        if (w)
+                writer_ref(w);
+        else {
+                w = writer_new(s);
+                if (!w)
+                        return log_oom();
+
+                if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) {
+                        w->hashmap_key = strdup(key);
+                        if (!w->hashmap_key)
+                                return log_oom();
+                }
 
-typedef struct RemoteServer {
-        RemoteSource **sources;
-        size_t sources_size;
-        size_t active;
+                r = open_output(w, host);
+                if (r < 0)
+                        return r;
 
-        sd_event *events;
-        sd_event_source *sigterm_event, *sigint_event, *listen_event;
+                r = hashmap_put(s->writers, w->hashmap_key ?: key, w);
+                if (r < 0)
+                        return r;
+        }
 
-        Hashmap *writers;
+        *writer = w;
+        w = NULL;
+        return 0;
+}
 
-        bool check_trust;
-        Hashmap *daemons;
-} RemoteServer;
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
 
 /* This should go away as soon as µhttpd allows state to be passed around. */
 static RemoteServer *server;
@@ -260,18 +308,31 @@ static int dispatch_http_event(sd_event_source *event,
                                uint32_t revents,
                                void *userdata);
 
-static int get_source_for_fd(RemoteServer *s, int fd, RemoteSource **source) {
+static int get_source_for_fd(RemoteServer *s,
+                             int fd, char *name, RemoteSource **source) {
+        Writer *writer;
+        int r;
+
         assert(fd >= 0);
         assert(source);
 
         if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
                 return log_oom();
 
+        r = get_writer(s, name, &writer);
+        if (r < 0) {
+                log_warning("Failed to get writer for source %s: %s",
+                            name, strerror(-r));
+                return r;
+        }
+
         if (s->sources[fd] == NULL) {
-                s->sources[fd] = new0(RemoteSource, 1);
-                if (!s->sources[fd])
+                s->sources[fd] = source_new(fd, false, name, writer);
+                if (!s->sources[fd]) {
+                        writer_unref(writer);
                         return log_oom();
-                s->sources[fd]->fd = -1;
+                }
+
                 s->active++;
         }
 
@@ -296,36 +357,28 @@ static int remove_source(RemoteServer *s, int fd) {
         return 0;
 }
 
-static int add_source(RemoteServer *s, int fd, const char* _name) {
+static int add_source(RemoteServer *s, int fd, char* name, bool own_name) {
 
         RemoteSource *source;
-        char *name;
         int r;
 
         assert(s);
         assert(fd >= 0);
-        assert(_name);
+        assert(name);
 
-        log_debug("Creating source for fd:%d (%s)", fd, _name);
-
-        name = strdup(_name);
-        if (!name)
-                return log_oom();
+        if (!own_name) {
+                name = strdup(name);
+                if (!name)
+                        return log_oom();
+        }
 
-        r = get_source_for_fd(s, fd, &source);
+        r = get_source_for_fd(s, fd, name, &source);
         if (r < 0) {
-                log_error("Failed to create source for fd:%d (%s)", fd, name);
-                free(name);
+                log_error("Failed to create source for fd:%d (%s): %s",
+                          fd, name, strerror(-r));
                 return r;
         }
 
-        assert(source);
-        assert(source->fd < 0);
-        assert(!source->name);
-
-        source->fd = fd;
-        source->name = name;
-
         r = sd_event_add_io(s->events, &source->event,
                             fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI,
                             dispatch_raw_source_event, s);
@@ -371,97 +424,29 @@ static int setup_raw_socket(RemoteServer *s, const char *address) {
  **********************************************************************
  **********************************************************************/
 
-static int init_writer_hashmap(RemoteServer *s) {
-        static const struct {
-                hash_func_t hash_func;
-                compare_func_t compare_func;
-        } functions[] = {
-                [JOURNAL_WRITE_SPLIT_NONE] = {trivial_hash_func,
-                                              trivial_compare_func},
-                [JOURNAL_WRITE_SPLIT_HOST] = {string_hash_func,
-                                              string_compare_func},
-        };
-
-        assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(functions));
-
-        s->writers = hashmap_new(functions[arg_split_mode].hash_func,
-                                 functions[arg_split_mode].compare_func);
-        if (!s->writers)
-                return log_oom();
-
-        return 0;
-}
-
-static int get_writer(RemoteServer *s, const char *host, sd_id128_t *machine,
-                      Writer **writer) {
-        const void *key;
-        Writer *w;
-        int r;
-
-        switch(arg_split_mode) {
-        case JOURNAL_WRITE_SPLIT_NONE:
-                key = "one and only";
-                break;
-
-        case JOURNAL_WRITE_SPLIT_HOST:
-                assert(host);
-                key = host;
-                break;
-
-        default:
-                assert_not_reached("what split mode?");
-        }
-
-        w = hashmap_get(s->writers, key);
-        if (!w) {
-                w = new0(Writer, 1);
-                if (!w)
-                        return -ENOMEM;
-
-                r = writer_init(w);
-                if (r < 0) {
-                        free(w);
-                        return r;
-                }
-
-                r = hashmap_put(s->writers, key, w);
-                if (r < 0) {
-                        writer_close(w);
-                        free(w);
-                        return r;
-                }
-
-                r = open_output(w, host);
-                if (r < 0)
-                        return r;
-        }
-
-        *writer = w;
-        return 0;
-}
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
 static RemoteSource *request_meta(void **connection_cls, int fd, char *hostname) {
         RemoteSource *source;
+        Writer *writer;
+        int r;
 
         assert(connection_cls);
-        if (*connection_cls) {
-                free(hostname);
+        if (*connection_cls)
                 return *connection_cls;
+
+        r = get_writer(server, hostname, &writer);
+        if (r < 0) {
+                log_warning("Failed to get writer for source %s: %s",
+                            hostname, strerror(-r));
+                return NULL;
         }
 
-        source = new0(RemoteSource, 1);
+        source = source_new(fd, true, hostname, writer);
         if (!source) {
-                free(hostname);
+                log_oom();
+                writer_unref(writer);
                 return NULL;
         }
 
-        source->fd = -1; /* fd */
-        source->name = hostname;
-
         log_debug("Added RemoteSource as connection metadata %p", source);
 
         *connection_cls = source;
@@ -488,26 +473,15 @@ static int process_http_upload(
                 size_t *upload_data_size,
                 RemoteSource *source) {
 
-        Writer *w;
-        int r;
         bool finished = false;
         size_t remaining;
+        int r;
 
         assert(source);
 
         log_debug("request_handler_upload: connection %p, %zu bytes",
                   connection, *upload_data_size);
 
-        r = get_writer(server, source->name, NULL, &w);
-        if (r < 0) {
-                log_warning("Failed to get writer for source %s: %s",
-                            source->name, strerror(-r));
-                return mhd_respondf(connection,
-                                    MHD_HTTP_SERVICE_UNAVAILABLE,
-                                    "Failed to get writer for connection: %s.\n",
-                                    strerror(-r));
-        }
-
         if (*upload_data_size) {
                 log_debug("Received %zu bytes", *upload_data_size);
 
@@ -520,8 +494,7 @@ static int process_http_upload(
                 finished = true;
 
         while (true) {
-
-                r = process_source(source, w, arg_compress, arg_seal);
+                r = process_source(source, arg_compress, arg_seal);
                 if (r == -EAGAIN || r == -EWOULDBLOCK)
                         break;
                 else if (r < 0) {
@@ -566,7 +539,7 @@ static int request_handler(
 
         const char *header;
         int r, code, fd;
-        char *hostname;
+        _cleanup_free_ char *hostname = NULL;
 
         assert(connection);
         assert(connection_cls);
@@ -627,6 +600,7 @@ static int request_handler(
 
         if (!request_meta(connection_cls, fd, hostname))
                 return respond_oom(connection);
+        hostname = NULL;
         return MHD_YES;
 }
 
@@ -871,7 +845,7 @@ static int remoteserver_init(RemoteServer *s,
                         else
                                 r = add_raw_socket(s, fd);
                 } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
-                        _cleanup_free_ char *hostname = NULL;
+                        char *hostname;
 
                         r = getnameinfo_pretty(fd, &hostname);
                         if (r < 0) {
@@ -881,7 +855,9 @@ static int remoteserver_init(RemoteServer *s,
 
                         log_info("Received a connection socket (fd:%d) from %s", fd, hostname);
 
-                        r = add_source(s, fd, hostname);
+                        r = add_source(s, fd, hostname, true);
+                        if (r < 0)
+                                free(hostname);
                 } else {
                         log_error("Unknown socket passed on fd:%d", fd);
 
@@ -917,7 +893,7 @@ static int remoteserver_init(RemoteServer *s,
                         startswith(arg_url, "http://") ?:
                         arg_url;
 
-                r = add_source(s, fd, hostname);
+                r = add_source(s, fd, (char*) hostname, false);
                 if (r < 0)
                         return r;
 
@@ -966,7 +942,7 @@ static int remoteserver_init(RemoteServer *s,
                         output_name = *file;
                 }
 
-                r = add_source(s, fd, output_name);
+                r = add_source(s, fd, (char*) output_name, false);
                 if (r < 0)
                         return r;
         }
@@ -987,9 +963,7 @@ static int remoteserver_init(RemoteServer *s,
                 /* 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. */
-                Writer *w;
-
-                r = get_writer(s, NULL, NULL, &w);
+                r = get_writer(s, NULL, &s->_single_writer);
                 if (r < 0)
                         return r;
         }
@@ -997,26 +971,10 @@ static int remoteserver_init(RemoteServer *s,
         return 0;
 }
 
-static int server_destroy(RemoteServer *s, uint64_t *event_count) {
-        int r;
+static void server_destroy(RemoteServer *s) {
         size_t i;
-        Writer *w;
         MHDDaemonWrapper *d;
 
-        *event_count = 0;
-
-        while ((w = hashmap_steal_first(s->writers))) {
-                log_info("seqnum %"PRIu64, w->seqnum);
-                *event_count += w->seqnum;
-
-                r = writer_close(w);
-                if (r < 0)
-                        log_warning("Failed to close writer: %s", strerror(-r));
-                free(w);
-        }
-
-        hashmap_free(s->writers);
-
         while ((d = hashmap_steal_first(s->daemons))) {
                 MHD_stop_daemon(d->daemon);
                 sd_event_source_unref(d->event);
@@ -1028,17 +986,17 @@ static int server_destroy(RemoteServer *s, uint64_t *event_count) {
         assert(s->sources_size == 0 || s->sources);
         for (i = 0; i < s->sources_size; i++)
                 remove_source(s, i);
-
         free(s->sources);
 
+        writer_unref(s->_single_writer);
+        hashmap_free(s->writers);
+
         sd_event_source_unref(s->sigterm_event);
         sd_event_source_unref(s->sigint_event);
         sd_event_source_unref(s->listen_event);
         sd_event_unref(s->events);
 
         /* fds that we're listening on remain open... */
-
-        return r;
 }
 
 /**********************************************************************
@@ -1050,7 +1008,6 @@ static int dispatch_raw_source_event(sd_event_source *event,
                                      uint32_t revents,
                                      void *userdata) {
 
-        Writer *w;
         RemoteServer *s = userdata;
         RemoteSource *source;
         int r;
@@ -1059,14 +1016,7 @@ static int dispatch_raw_source_event(sd_event_source *event,
         source = s->sources[fd];
         assert(source->fd == fd);
 
-        r = get_writer(s, source->name, NULL, &w);
-        if (r < 0) {
-                log_warning("Failed to get writer for source %s: %s",
-                            source->name, strerror(-r));
-                return r;
-        }
-
-        r = process_source(source, w, arg_compress, arg_seal);
+        r = process_source(source, arg_compress, arg_seal);
         if (source->state == STATE_EOF) {
                 size_t remaining;
 
@@ -1145,7 +1095,7 @@ static int dispatch_raw_connection_event(sd_event_source *event,
                                          uint32_t revents,
                                          void *userdata) {
         RemoteServer *s = userdata;
-        int fd2;
+        int fd2, r;
         SocketAddress addr = {
                 .size = sizeof(union sockaddr_union),
                 .type = SOCK_STREAM,
@@ -1156,7 +1106,10 @@ static int dispatch_raw_connection_event(sd_event_source *event,
         if (fd2 < 0)
                 return fd2;
 
-        return add_source(s, fd2, hostname);
+        r = add_source(s, fd2, hostname, true);
+        if (r < 0)
+                free(hostname);
+        return r;
 }
 
 /**********************************************************************
@@ -1537,9 +1490,8 @@ static int setup_gnutls_logger(char **categories) {
 
 int main(int argc, char **argv) {
         RemoteServer s = {};
-        int r, r2;
+        int r;
         _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL;
-        uint64_t entry_count;
 
         log_show_color(true);
         log_parse_environment();
@@ -1585,8 +1537,8 @@ int main(int argc, char **argv) {
                 }
         }
 
-        r2 = server_destroy(&s, &entry_count);
-        log_info("Finishing after writing %" PRIu64 " entries", entry_count);
+        server_destroy(&s);
+        log_info("Finishing after writing %" PRIu64 " entries", s.event_count);
 
         sd_notify(false, "STATUS=Shutting down...");
 
@@ -1594,5 +1546,5 @@ int main(int argc, char **argv) {
         free(arg_cert);
         free(arg_trust);
 
-        return r >= 0 && r2 >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+        return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 }
diff --git a/src/journal-remote/journal-remote.h b/src/journal-remote/journal-remote.h
new file mode 100644
index 0000000..0422cea
--- /dev/null
+++ b/src/journal-remote/journal-remote.h
@@ -0,0 +1,30 @@
+#include <inttypes.h>
+
+#include "sd-event.h"
+#include "hashmap.h"
+#include "microhttpd-util.h"
+
+#include "journal-remote-parse.h"
+
+typedef struct MHDDaemonWrapper {
+        uint64_t fd;
+        struct MHD_Daemon *daemon;
+
+        sd_event_source *event;
+} MHDDaemonWrapper;
+
+typedef struct RemoteServer {
+        RemoteSource **sources;
+        size_t sources_size;
+        size_t active;
+
+        sd_event *events;
+        sd_event_source *sigterm_event, *sigint_event, *listen_event;
+
+        Hashmap *writers;
+        Writer *_single_writer;
+        uint64_t event_count;
+
+        bool check_trust;
+        Hashmap *daemons;
+} RemoteServer;

commit a83f403760cb63b1bf7787e9ff325ffb6d891d39
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Tue Jul 1 23:18:44 2014 -0400

    journal-remote: improve some messages

diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c
index 14af55c..90d50a7 100644
--- a/src/journal-remote/journal-remote-parse.c
+++ b/src/journal-remote/journal-remote-parse.c
@@ -203,7 +203,7 @@ static int get_data_size(RemoteSource *source) {
 
         source->data_size = le64toh( *(uint64_t *) data );
         if (source->data_size > DATA_SIZE_MAX) {
-                log_error("Stream declares field with size %zu > %u == DATA_SIZE_MAX",
+                log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u",
                           source->data_size, DATA_SIZE_MAX);
                 return -EINVAL;
         }
@@ -429,8 +429,8 @@ int process_source(RemoteSource *source, Writer *writer, bool compress, bool sea
                 return r;
 
         /* We have a full event */
-        log_info("Received a full event from source@%p fd:%d (%s)",
-                 source, source->fd, source->name);
+        log_debug("Received a full event from source@%p fd:%d (%s)",
+                  source, source->fd, source->name);
 
         if (!source->iovw.count) {
                 log_warning("Entry with no payload, skipping");
diff --git a/src/journal-remote/journal-remote-write.c b/src/journal-remote/journal-remote-write.c
index 449636c..3b00ff5 100644
--- a/src/journal-remote/journal-remote-write.c
+++ b/src/journal-remote/journal-remote-write.c
@@ -114,10 +114,12 @@ int writer_write(Writer *s,
         if (r >= 0)
                 return 1;
 
-        log_info("%s: Write failed, rotating", s->journal->path);
+        log_debug("%s: Write failed, rotating: %s", s->journal->path, strerror(-r));
         r = do_rotate(&s->journal, compress, seal);
         if (r < 0)
                 return r;
+        else
+                log_info("%s: Successfully rotated journal", s->journal->path);
 
         log_debug("Retrying write.");
         r = journal_file_append_entry(s->journal, ts, iovw->iovec, iovw->count,
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 827916d..d37fac3 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -500,8 +500,8 @@ static int process_http_upload(
 
         r = get_writer(server, source->name, NULL, &w);
         if (r < 0) {
-                log_warning("Failed to get writer for source %s (%s): %s",
-                            source->name, source->name, strerror(-r));
+                log_warning("Failed to get writer for source %s: %s",
+                            source->name, strerror(-r));
                 return mhd_respondf(connection,
                                     MHD_HTTP_SERVICE_UNAVAILABLE,
                                     "Failed to get writer for connection: %s.\n",
@@ -509,7 +509,7 @@ static int process_http_upload(
         }
 
         if (*upload_data_size) {
-                log_info("Received %zu bytes", *upload_data_size);
+                log_debug("Received %zu bytes", *upload_data_size);
 
                 r = push_data(source, upload_data, *upload_data_size);
                 if (r < 0)
@@ -951,7 +951,7 @@ static int remoteserver_init(RemoteServer *s,
 
         STRV_FOREACH(file, arg_files) {
                 if (streq(*file, "-")) {
-                        log_info("Reading standard input...");
+                        log_info("Using standard input as source.");
 
                         fd = STDIN_FILENO;
                         output_name = "stdin";
@@ -1061,8 +1061,8 @@ static int dispatch_raw_source_event(sd_event_source *event,
 
         r = get_writer(s, source->name, NULL, &w);
         if (r < 0) {
-                log_warning("Failed to get writer for source %s (%s): %s",
-                            source->name, source->name, strerror(-r));
+                log_warning("Failed to get writer for source %s: %s",
+                            source->name, strerror(-r));
                 return r;
         }
 
@@ -1077,7 +1077,7 @@ static int dispatch_raw_source_event(sd_event_source *event,
                 if (remaining > 0)
                         log_warning("Premature EOF. %zu bytes lost.", remaining);
                 remove_source(s, source->fd);
-                log_info("%zd active source remaining", s->active);
+                log_info("%zd active sources remaining", s->active);
                 return 0;
         } else if (r == -E2BIG) {
                 log_error("Entry too big, skipped");
@@ -1195,7 +1195,7 @@ static int parse_config(void) {
 
 static void help(void) {
         printf("%s [OPTIONS...] {FILE|-}...\n\n"
-               "Write external journal events to a journal file.\n\n"
+               "Write external journal events to journal file(s).\n\n"
                "Options:\n"
                "  --url=URL            Read events from systemd-journal-gatewayd at URL\n"
                "  --getter=COMMAND     Read events from the output of COMMAND\n"
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index 680073f..95be9a0 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -479,7 +479,7 @@ static void help(void) {
         printf("%s -u URL {FILE|-}...\n\n"
                "Upload journal events to a remote server.\n\n"
                "Options:\n"
-               "  --url=URL                Upload to this address\n"
+               "  -u --url=URL             Upload to this address\n"
                "  --key=FILENAME           Specify key in PEM format\n"
                "  --cert=FILENAME          Specify certificate in PEM format\n"
                "  --trust=FILENAME         Specify CA certificate in PEM format\n"

commit 4a0a6ac03864998c83918175609275df712a5a05
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Tue Jul 1 23:07:45 2014 -0400

    Fix problem with allocating large buffers and log leftovers

diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c
index a08ca2f..14af55c 100644
--- a/src/journal-remote/journal-remote-parse.c
+++ b/src/journal-remote/journal-remote-parse.c
@@ -22,7 +22,7 @@
 #include "journal-remote-parse.h"
 #include "journald-native.h"
 
-#define LINE_CHUNK 1024u
+#define LINE_CHUNK 8*1024u
 
 void source_free(RemoteSource *source) {
         if (!source)
@@ -71,13 +71,17 @@ static int get_line(RemoteSource *source, char **line, size_t *size) {
 
                 if (source->size - source->filled < LINE_CHUNK &&
                     !GREEDY_REALLOC(source->buf, source->size,
-                                    MAX(source->filled + LINE_CHUNK, DATA_SIZE_MAX)))
+                                    MIN(source->filled + LINE_CHUNK, DATA_SIZE_MAX)))
                                 return log_oom();
 
-                assert(source->size - source->filled >= LINE_CHUNK);
+                assert(source->size - source->filled >= LINE_CHUNK ||
+                       source->size == DATA_SIZE_MAX);
+
+                // FIXME: the buffer probably needs to be bigger than DATA_SIZE_MAX
+                // to accomodate such big fields.
 
                 n = read(source->fd, source->buf + source->filled,
-                         MAX(source->size, DATA_SIZE_MAX) - source->filled);
+                         source->size - source->filled);
                 if (n < 0) {
                         if (errno != EAGAIN && errno != EWOULDBLOCK)
                                 log_error("read(%d, ..., %zd): %m", source->fd,
diff --git a/src/journal-remote/journal-remote-parse.h b/src/journal-remote/journal-remote-parse.h
index b02b5a6..5b7f236 100644
--- a/src/journal-remote/journal-remote-parse.h
+++ b/src/journal-remote/journal-remote-parse.h
@@ -50,10 +50,10 @@ typedef struct RemoteSource {
         sd_event_source *event;
 } RemoteSource;
 
-static inline int source_non_empty(RemoteSource *source) {
+static inline size_t source_non_empty(RemoteSource *source) {
         assert(source);
 
-        return source->filled > 0;
+        return source->filled;
 }
 
 void source_free(RemoteSource *source);
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 6925fe2..827916d 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -491,6 +491,7 @@ static int process_http_upload(
         Writer *w;
         int r;
         bool finished = false;
+        size_t remaining;
 
         assert(source);
 
@@ -542,10 +543,12 @@ static int process_http_upload(
 
         /* The upload is finished */
 
-        if (source_non_empty(source)) {
-                log_warning("EOF reached with incomplete data");
-                return mhd_respond(connection, MHD_HTTP_EXPECTATION_FAILED,
-                                   "Trailing data not processed.");
+        remaining = source_non_empty(source);
+        if (remaining > 0) {
+                log_warning("Premature EOFbyte. %zu bytes lost.", remaining);
+                return mhd_respondf(connection, MHD_HTTP_EXPECTATION_FAILED,
+                                    "Premature EOF. %zu bytes of trailing data not processed.",
+                                    remaining);
         }
 
         return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n");
@@ -1003,6 +1006,7 @@ static int server_destroy(RemoteServer *s, uint64_t *event_count) {
         *event_count = 0;
 
         while ((w = hashmap_steal_first(s->writers))) {
+                log_info("seqnum %"PRIu64, w->seqnum);
                 *event_count += w->seqnum;
 
                 r = writer_close(w);
@@ -1064,10 +1068,14 @@ static int dispatch_raw_source_event(sd_event_source *event,
 
         r = process_source(source, w, arg_compress, arg_seal);
         if (source->state == STATE_EOF) {
+                size_t remaining;
+
                 log_info("EOF reached with source fd:%d (%s)",
                          source->fd, source->name);
-                if (source_non_empty(source))
-                        log_warning("EOF reached with incomplete data");
+
+                remaining = source_non_empty(source);
+                if (remaining > 0)
+                        log_warning("Premature EOF. %zu bytes lost.", remaining);
                 remove_source(s, source->fd);
                 log_info("%zd active source remaining", s->active);
                 return 0;

commit e9f3d2d508bfd9fb5b54e82994bda365a71eb864
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Tue Jul 15 21:03:11 2014 -0400

    Constify ConfigTableItem tables

diff --git a/src/bootchart/bootchart.c b/src/bootchart/bootchart.c
index 01a5bf1..8e849da 100644
--- a/src/bootchart/bootchart.c
+++ b/src/bootchart/bootchart.c
@@ -132,7 +132,7 @@ static void parse_conf(void) {
                 return;
 
         r = config_parse(NULL, BOOTCHART_CONF, f,
-                         NULL, config_item_table_lookup, (void*) items, true, false, NULL);
+                         NULL, config_item_table_lookup, items, true, false, NULL);
         if (r < 0)
                 log_warning("Failed to parse configuration file: %s", strerror(-r));
 
diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c
index 6ff592b..66547cf 100644
--- a/src/core/load-dropin.c
+++ b/src/core/load-dropin.c
@@ -187,7 +187,7 @@ int unit_load_dropin(Unit *u) {
         STRV_FOREACH(f, u->dropin_paths) {
                 config_parse(u->id, *f, NULL,
                              UNIT_VTABLE(u)->sections, config_item_perf_lookup,
-                             (void*) load_fragment_gperf_lookup, false, false, u);
+                             load_fragment_gperf_lookup, false, false, u);
         }
 
         u->dropin_mtime = now(CLOCK_REALTIME);
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index b6894d2..54010b8 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -3370,7 +3370,7 @@ static int load_from_path(Unit *u, const char *path) {
                 /* Now, parse the file contents */
                 r = config_parse(u->id, filename, f, UNIT_VTABLE(u)->sections,
                                  config_item_perf_lookup,
-                                 (void*) load_fragment_gperf_lookup, false, true, u);
+                                 load_fragment_gperf_lookup, false, true, u);
                 if (r < 0)
                         return r;
         }
diff --git a/src/core/main.c b/src/core/main.c
index a21a959..d1fb265 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -696,7 +696,7 @@ static int parse_config_file(void) {
                 return 0;
         }
 
-        r = config_parse(NULL, fn, f, "Manager\0", config_item_table_lookup, (void*) items, false, false, NULL);
+        r = config_parse(NULL, fn, f, "Manager\0", config_item_table_lookup, items, false, false, NULL);
         if (r < 0)
                 log_warning("Failed to parse configuration file: %s", strerror(-r));
 
diff --git a/src/dbus1-generator/dbus1-generator.c b/src/dbus1-generator/dbus1-generator.c
index 95962c7..ba29530 100644
--- a/src/dbus1-generator/dbus1-generator.c
+++ b/src/dbus1-generator/dbus1-generator.c
@@ -156,7 +156,7 @@ static int create_dbus_files(
 static int add_dbus(const char *path, const char *fname, const char *type) {
         _cleanup_free_ char *name = NULL, *exec = NULL, *user = NULL, *service = NULL;
 
-        ConfigTableItem table[] = {
+        const ConfigTableItem table[] = {
                 { "D-BUS Service", "Name", config_parse_string, 0, &name },
                 { "D-BUS Service", "Exec", config_parse_string, 0, &exec },
                 { "D-BUS Service", "User", config_parse_string, 0, &user },
diff --git a/src/journal/coredump.c b/src/journal/coredump.c
index 1b8c2e8..cd612b7 100644
--- a/src/journal/coredump.c
+++ b/src/journal/coredump.c
@@ -120,7 +120,7 @@ static int parse_config(void) {
                         NULL,
                         "Coredump\0",
                         config_item_table_lookup,
-                        (void*) items,
+                        items,
                         false,
                         false,
                         NULL);
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index f8da4da..097af24 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -1347,7 +1347,7 @@ static int server_parse_config_file(Server *s) {
         }
 
         r = config_parse(NULL, fn, f, "Journal\0", config_item_perf_lookup,
-                         (void*) journald_gperf_lookup, false, false, s);
+                         journald_gperf_lookup, false, false, s);
         if (r < 0)
                 log_warning("Failed to parse configuration file: %s", strerror(-r));
 
diff --git a/src/login/logind.c b/src/login/logind.c
index 7a7bd97..a188184 100644
--- a/src/login/logind.c
+++ b/src/login/logind.c
@@ -1113,7 +1113,7 @@ static int manager_parse_config_file(Manager *m) {
         }
 
         r = config_parse(NULL, fn, f, "Login\0", config_item_perf_lookup,
-                         (void*) logind_gperf_lookup, false, false, m);
+                         logind_gperf_lookup, false, false, m);
         if (r < 0)
                 log_warning("Failed to parse configuration file: %s", strerror(-r));
 
diff --git a/src/network/networkd-netdev.c b/src/network/networkd-netdev.c
index a978317..7d12af3 100644
--- a/src/network/networkd-netdev.c
+++ b/src/network/networkd-netdev.c
@@ -525,7 +525,7 @@ static int netdev_load_one(Manager *manager, const char *filename) {
 
         r = config_parse(NULL, filename, file,
                          "Match\0NetDev\0VLAN\0MACVLAN\0VXLAN\0Tunnel\0Peer\0Tun\0Tap\0Bond\0",
-                         config_item_perf_lookup, (void*) network_netdev_gperf_lookup,
+                         config_item_perf_lookup, network_netdev_gperf_lookup,
                          false, false, netdev);
         if (r < 0) {
                 log_warning("Could not parse config file %s: %s", filename, strerror(-r));
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 0375194..252c9a0 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -93,8 +93,10 @@ static int network_load_one(Manager *manager, const char *filename) {
         network->dhcp_routes = true;
         network->dhcp_sendhost = true;
 
-        r = config_parse(NULL, filename, file, "Match\0Network\0Address\0Route\0DHCP\0DHCPv4\0", config_item_perf_lookup,
-                        (void*) network_network_gperf_lookup, false, false, network);
+        r = config_parse(NULL, filename, file,
+                         "Match\0Network\0Address\0Route\0DHCP\0DHCPv4\0",
+                         config_item_perf_lookup, network_network_gperf_lookup,
+                         false, false, network);
         if (r < 0) {
                 log_warning("Could not parse config file %s: %s", filename, strerror(-r));
                 return r;
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index dc6deeb..8718ea4 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -377,10 +377,9 @@ int manager_parse_config_file(Manager *m) {
 
         assert(m);
 
-        r = config_parse(NULL,
-                         "/etc/systemd/resolved.conf", NULL,
+        r = config_parse(NULL, "/etc/systemd/resolved.conf", NULL,
                          "Resolve\0",
-                         config_item_perf_lookup, (void*) resolved_gperf_lookup,
+                         config_item_perf_lookup, resolved_gperf_lookup,
                          false, false, m);
         if (r < 0)
                 log_warning("Failed to parse configuration file: %s", strerror(-r));
diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c
index 158e9ef..1f40986 100644
--- a/src/shared/conf-parser.c
+++ b/src/shared/conf-parser.c
@@ -76,7 +76,7 @@ int log_syntax_internal(const char *unit, int level,
 }
 
 int config_item_table_lookup(
-                void *table,
+                const void *table,
                 const char *section,
                 const char *lvalue,
                 ConfigParserCallback *func,
@@ -84,7 +84,7 @@ int config_item_table_lookup(
                 void **data,
                 void *userdata) {
 
-        ConfigTableItem *t;
+        const ConfigTableItem *t;
 
         assert(table);
         assert(lvalue);
@@ -110,7 +110,7 @@ int config_item_table_lookup(
 }
 
 int config_item_perf_lookup(
-                void *table,
+                const void *table,
                 const char *section,
                 const char *lvalue,
                 ConfigParserCallback *func,
@@ -154,7 +154,7 @@ static int next_assignment(const char *unit,
                            const char *filename,
                            unsigned line,
                            ConfigItemLookup lookup,
-                           void *table,
+                           const void *table,
                            const char *section,
                            unsigned section_line,
                            const char *lvalue,
@@ -199,7 +199,7 @@ static int parse_line(const char* unit,
                       unsigned line,
                       const char *sections,
                       ConfigItemLookup lookup,
-                      void *table,
+                      const void *table,
                       bool relaxed,
                       bool allow_include,
                       char **section,
@@ -323,7 +323,7 @@ int config_parse(const char *unit,
                  FILE *f,
                  const char *sections,
                  ConfigItemLookup lookup,
-                 void *table,
+                 const void *table,
                  bool relaxed,
                  bool allow_include,
                  void *userdata) {
diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h
index 9d166de..3e2c784 100644
--- a/src/shared/conf-parser.h
+++ b/src/shared/conf-parser.h
@@ -65,7 +65,7 @@ typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lv
 
 /* Prototype for a generic high-level lookup function */
 typedef int (*ConfigItemLookup)(
-                void *table,
+                const void *table,
                 const char *section,
                 const char *lvalue,
                 ConfigParserCallback *func,
@@ -75,18 +75,18 @@ typedef int (*ConfigItemLookup)(
 
 /* Linear table search implementation of ConfigItemLookup, based on
  * ConfigTableItem arrays */
-int config_item_table_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
+int config_item_table_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
 
 /* gperf implementation of ConfigItemLookup, based on gperf
  * ConfigPerfItem tables */
-int config_item_perf_lookup(void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
+int config_item_perf_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata);
 
 int config_parse(const char *unit,
                  const char *filename,
                  FILE *f,
                  const char *sections,  /* nulstr */
                  ConfigItemLookup lookup,
-                 void *table,
+                 const void *table,
                  bool relaxed,
                  bool allow_include,
                  void *userdata);
diff --git a/src/shared/install.c b/src/shared/install.c
index 190c554..a080d8f 100644
--- a/src/shared/install.c
+++ b/src/shared/install.c
@@ -1076,7 +1076,7 @@ static int unit_file_load(
                 return -ENOMEM;
         }
 
-        r = config_parse(NULL, path, f, NULL, config_item_table_lookup, (void*) items, true, true, info);
+        r = config_parse(NULL, path, f, NULL, config_item_table_lookup, items, true, true, info);
         if (r < 0)
                 return r;
 
diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c
index 867e4ed..4b2b0fe 100644
--- a/src/shared/sleep-config.c
+++ b/src/shared/sleep-config.c
@@ -57,7 +57,7 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
                          "Failed to open configuration file " PKGSYSCONFDIR "/sleep.conf: %m");
         else {
                 r = config_parse(NULL, PKGSYSCONFDIR "/sleep.conf", f, "Sleep\0",
-                                 config_item_table_lookup, (void*) items, false, false, NULL);
+                                 config_item_table_lookup, items, false, false, NULL);
                 if (r < 0)
                         log_warning("Failed to parse configuration file: %s", strerror(-r));
         }
diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c
index 78ef5f7..67a434a 100644
--- a/src/timesync/timesyncd.c
+++ b/src/timesync/timesyncd.c
@@ -1111,7 +1111,7 @@ static int manager_parse_config_file(Manager *m) {
         }
 
         r = config_parse(NULL, fn, f, "Time\0", config_item_perf_lookup,
-                         (void*) timesyncd_gperf_lookup, false, false, m);
+                         timesyncd_gperf_lookup, false, false, m);
         if (r < 0)
                 log_warning("Failed to parse configuration file: %s", strerror(-r));
 
diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c
index 55a2215..0398a9d 100644
--- a/src/tty-ask-password-agent/tty-ask-password-agent.c
+++ b/src/tty-ask-password-agent/tty-ask-password-agent.c
@@ -272,7 +272,7 @@ static int parse_password(const char *filename, char **wall) {
                 return -errno;
         }
 
-        r = config_parse(NULL, filename, f, NULL, config_item_table_lookup, (void*) items, true, false, NULL);
+        r = config_parse(NULL, filename, f, NULL, config_item_table_lookup, items, true, false, NULL);
         if (r < 0) {
                 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
                 goto finish;
diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c
index 5a45c53..fe916a4 100644
--- a/src/udev/net/link-config.c
+++ b/src/udev/net/link-config.c
@@ -174,8 +174,10 @@ static int load_link(link_config_ctx *ctx, const char *filename) {
         link->wol = _WOL_INVALID;
         link->duplex = _DUP_INVALID;
 
-        r = config_parse(NULL, filename, file, "Match\0Link\0Ethernet\0", config_item_perf_lookup,
-                         (void*) link_config_gperf_lookup, false, false, link);
+        r = config_parse(NULL, filename, file,
+                         "Match\0Link\0Ethernet\0",
+                         config_item_perf_lookup, link_config_gperf_lookup,
+                         false, false, link);
         if (r < 0) {
                 log_warning("Could not parse config file %s: %s", filename, strerror(-r));
                 return r;

commit 8201af08fa09c2bd0f005fbe262f27e2c5bd2d86
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Jun 22 13:36:31 2014 -0400

    journal-remote: allow splitting incoming logs by source host
    
    Previously existing scheme where the file name would be based on
    the source was just too ugly and unpredicatable. Now there are
    only two options:
      1. just one file (until rotation),
      2. one file per source host, using the hostname as filename part.
    For the cases where the source is specified by the user, only
    option one is allowed, and the full of the file must be specified.

diff --git a/man/systemd-journal-remote.xml b/man/systemd-journal-remote.xml
index 972c322..a6e67e5 100644
--- a/man/systemd-journal-remote.xml
+++ b/man/systemd-journal-remote.xml
@@ -253,6 +253,20 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
       </varlistentry>
 
       <varlistentry>
+        <term><option>--split-mode</option></term>
+
+        <listitem><para>One of <constant>none</constant> or
+        <constant>host</constant>. For the first, only one output
+        journal file is used. For the latter, a separate output file
+        is used, based on the hostname of the other endpoint of a
+        connection.</para>
+
+        <para>In case of "active" sources, the output file name must
+        always be given explicitly and only <constant>none</constant>
+        is allowed.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><option>--compress</option></term>
         <term><option>--no-compress</option></term>
 
diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c
index db07700..6cfe229 100644
--- a/src/journal-remote/journal-gatewayd.c
+++ b/src/journal-remote/journal-gatewayd.c
@@ -834,7 +834,7 @@ static int request_handler(
         }
 
         if (trust_pem) {
-                r = check_permissions(connection, &code);
+                r = check_permissions(connection, &code, NULL);
                 if (r < 0)
                         return code;
         }
diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c
index fe21bd3..a08ca2f 100644
--- a/src/journal-remote/journal-remote-parse.c
+++ b/src/journal-remote/journal-remote-parse.c
@@ -30,11 +30,14 @@ void source_free(RemoteSource *source) {
 
         if (source->fd >= 0) {
                 log_debug("Closing fd:%d (%s)", source->fd, source->name);
-                close(source->fd);
+                safe_close(source->fd);
         }
         free(source->name);
         free(source->buf);
         iovw_free_contents(&source->iovw);
+
+        sd_event_source_unref(source->event);
+
         free(source);
 }
 
diff --git a/src/journal-remote/journal-remote-parse.h b/src/journal-remote/journal-remote-parse.h
index 2b6c24e..b02b5a6 100644
--- a/src/journal-remote/journal-remote-parse.h
+++ b/src/journal-remote/journal-remote-parse.h
@@ -33,7 +33,7 @@ typedef enum {
 } source_state;
 
 typedef struct RemoteSource {
-        char* name;
+        char *name;
         int fd;
 
         char *buf;
diff --git a/src/journal-remote/journal-remote-write.h b/src/journal-remote/journal-remote-write.h
index 8798216..2ea5e67 100644
--- a/src/journal-remote/journal-remote-write.h
+++ b/src/journal-remote/journal-remote-write.h
@@ -49,3 +49,10 @@ int writer_write(Writer *s,
                  dual_timestamp *ts,
                  bool compress,
                  bool seal);
+
+typedef enum JournalWriteSplitMode {
+        JOURNAL_WRITE_SPLIT_NONE,
+        JOURNAL_WRITE_SPLIT_HOST,
+        _JOURNAL_WRITE_SPLIT_MAX,
+        _JOURNAL_WRITE_SPLIT_INVALID = -1
+} JournalWriteSplitMode;
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 22f067a..6925fe2 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -41,9 +41,11 @@
 #include "build.h"
 #include "macro.h"
 #include "strv.h"
+#include "hashmap.h"
 #include "fileio.h"
 #include "conf-parser.h"
 #include "microhttpd-util.h"
+#include "siphash24.h"
 
 #ifdef HAVE_GNUTLS
 #include <gnutls/gnutls.h>
@@ -52,13 +54,12 @@
 #include "journal-remote-parse.h"
 #include "journal-remote-write.h"
 
-#define REMOTE_JOURNAL_PATH "/var/log/journal/" SD_ID128_FORMAT_STR "/remote-%s.journal"
+#define REMOTE_JOURNAL_PATH "/var/log/journal/remote"
 
 #define 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_output = NULL;
 static char* arg_url = NULL;
 static char* arg_getter = NULL;
 static char* arg_listen_raw = NULL;
@@ -70,6 +71,9 @@ 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_NONE;
+static char* arg_output = NULL;
+
 static char *arg_key = NULL;
 static char *arg_cert = NULL;
 static char *arg_trust = NULL;
@@ -130,7 +134,7 @@ static int spawn_child(const char* child, char** argv) {
         return fd[0];
 }
 
-static int spawn_curl(char* url) {
+static int spawn_curl(const char* url) {
         char **argv = STRV_MAKE("curl",
                                 "-HAccept: application/vnd.fdo.journal",
                                 "--silent",
@@ -144,7 +148,7 @@ static int spawn_curl(char* url) {
         return r;
 }
 
-static int spawn_getter(char *getter, char *url) {
+static int spawn_getter(const char *getter, const char *url) {
         int r;
         _cleanup_strv_free_ char **words = NULL;
 
@@ -153,6 +157,12 @@ static int spawn_getter(char *getter, char *url) {
         if (!words)
                 return log_oom();
 
+        r = strv_extend(&words, url);
+        if (r < 0) {
+                log_error("Failed to create command line: %s", strerror(-r));
+                return r;
+        }
+
         r = spawn_child(words[0], words);
         if (r < 0)
                 log_error("Failed to spawn getter %s: %m", getter);
@@ -160,62 +170,52 @@ static int spawn_getter(char *getter, char *url) {
         return r;
 }
 
-static int open_output(Writer *s, const char* url) {
-        _cleanup_free_ char *name, *output = NULL;
-        char *c;
+#define filename_escape(s) xescape((s), "./ ")
+
+static int open_output(Writer *w, const char* host) {
+        _cleanup_free_ char *_output = NULL;
+        const char *output;
         int r;
 
-        assert(url);
-        name = strdup(url);
-        if (!name)
-                return log_oom();
+        switch (arg_split_mode) {
+        case JOURNAL_WRITE_SPLIT_NONE:
+                output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal";
+                break;
 
-        for(c = name; *c; c++) {
-                if (*c == '/' || *c == ':' || *c == ' ')
-                        *c = '~';
-                else if (*c == '?') {
-                        *c = '\0';
-                        break;
-                }
-        }
+        case JOURNAL_WRITE_SPLIT_HOST: {
+                _cleanup_free_ char *name;
 
-        if (!arg_output) {
-                sd_id128_t machine;
-                r = sd_id128_get_machine(&machine);
-                if (r < 0) {
-                        log_error("failed to determine machine ID128: %s", strerror(-r));
-                        return r;
-                }
+                assert(host);
 
-                r = asprintf(&output, REMOTE_JOURNAL_PATH,
-                             SD_ID128_FORMAT_VAL(machine), name);
+                name = filename_escape(host);
+                if (!name)
+                        return log_oom();
+
+                r = asprintf(&_output, "%s/remote-%s.journal",
+                             arg_output ?: REMOTE_JOURNAL_PATH,
+                             name);
                 if (r < 0)
                         return log_oom();
-        } else {
-                r = is_dir(arg_output, true);
-                if (r > 0) {
-                        r = asprintf(&output,
-                                     "%s/remote-%s.journal", arg_output, name);
-                        if (r < 0)
-                                return log_oom();
-                } else {
-                        output = strdup(arg_output);
-                        if (!output)
-                                return log_oom();
-                }
+
+                output = _output;
+                break;
+        }
+
+        default:
+                assert_not_reached("what?");
         }
 
         r = journal_file_open_reliably(output,
                                        O_RDWR|O_CREAT, 0640,
                                        arg_compress, arg_seal,
-                                       &s->metrics,
-                                       s->mmap,
-                                       NULL, &s->journal);
+                                       &w->metrics,
+                                       w->mmap,
+                                       NULL, &w->journal);
         if (r < 0)
                 log_error("Failed to open output journal %s: %s",
-                          arg_output, strerror(-r));
+                          output, strerror(-r));
         else
-                log_info("Opened output file %s", s->journal->path);
+                log_info("Opened output file %s", w->journal->path);
         return r;
 }
 
@@ -238,7 +238,7 @@ typedef struct RemoteServer {
         sd_event *events;
         sd_event_source *sigterm_event, *sigint_event, *listen_event;
 
-        Writer writer;
+        Hashmap *writers;
 
         bool check_trust;
         Hashmap *daemons;
@@ -287,47 +287,48 @@ static int remove_source(RemoteServer *s, int fd) {
 
         source = s->sources[fd];
         if (source) {
+                /* this closes fd too */
                 source_free(source);
                 s->sources[fd] = NULL;
                 s->active--;
         }
 
-        close(fd);
-
         return 0;
 }
 
-static int add_source(RemoteServer *s, int fd, const char* name) {
-        RemoteSource *source = NULL;
-        _cleanup_free_ char *realname = NULL;
+static int add_source(RemoteServer *s, int fd, const char* _name) {
+
+        RemoteSource *source;
+        char *name;
         int r;
 
         assert(s);
         assert(fd >= 0);
+        assert(_name);
 
-        if (name) {
-                realname = strdup(name);
-                if (!realname)
-                        return log_oom();
-        } else {
-                r = asprintf(&realname, "fd:%d", fd);
-                if (r < 0)
-                        return log_oom();
-        }
+        log_debug("Creating source for fd:%d (%s)", fd, _name);
 
-        log_debug("Creating source for fd:%d (%s)", fd, realname);
+        name = strdup(_name);
+        if (!name)
+                return log_oom();
 
         r = get_source_for_fd(s, fd, &source);
         if (r < 0) {
-                log_error("Failed to create source for fd:%d (%s)", fd, realname);
+                log_error("Failed to create source for fd:%d (%s)", fd, name);
+                free(name);
                 return r;
         }
+
         assert(source);
         assert(source->fd < 0);
+        assert(!source->name);
+
         source->fd = fd;
+        source->name = name;
 
         r = sd_event_add_io(s->events, &source->event,
-                            fd, EPOLLIN, dispatch_raw_source_event, s);
+                            fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI,
+                            dispatch_raw_source_event, s);
         if (r < 0) {
                 log_error("Failed to register event source for fd:%d: %s",
                           fd, strerror(-r));
@@ -344,7 +345,8 @@ static int add_source(RemoteServer *s, int fd, const char* name) {
 static int add_raw_socket(RemoteServer *s, int fd) {
         int r;
 
-        r = sd_event_add_io(s->events, &s->listen_event, fd, EPOLLIN,
+        r = sd_event_add_io(s->events, &s->listen_event,
+                            fd, EPOLLIN,
                             dispatch_raw_connection_event, s);
         if (r < 0) {
                 close(fd);
@@ -369,17 +371,96 @@ static int setup_raw_socket(RemoteServer *s, const char *address) {
  **********************************************************************
  **********************************************************************/
 
-static RemoteSource *request_meta(void **connection_cls) {
+static int init_writer_hashmap(RemoteServer *s) {
+        static const struct {
+                hash_func_t hash_func;
+                compare_func_t compare_func;
+        } functions[] = {
+                [JOURNAL_WRITE_SPLIT_NONE] = {trivial_hash_func,
+                                              trivial_compare_func},
+                [JOURNAL_WRITE_SPLIT_HOST] = {string_hash_func,
+                                              string_compare_func},
+        };
+
+        assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(functions));
+
+        s->writers = hashmap_new(functions[arg_split_mode].hash_func,
+                                 functions[arg_split_mode].compare_func);
+        if (!s->writers)
+                return log_oom();
+
+        return 0;
+}
+
+static int get_writer(RemoteServer *s, const char *host, sd_id128_t *machine,
+                      Writer **writer) {
+        const void *key;
+        Writer *w;
+        int r;
+
+        switch(arg_split_mode) {
+        case JOURNAL_WRITE_SPLIT_NONE:
+                key = "one and only";
+                break;
+
+        case JOURNAL_WRITE_SPLIT_HOST:
+                assert(host);
+                key = host;
+                break;
+
+        default:
+                assert_not_reached("what split mode?");
+        }
+
+        w = hashmap_get(s->writers, key);
+        if (!w) {
+                w = new0(Writer, 1);
+                if (!w)
+                        return -ENOMEM;
+
+                r = writer_init(w);
+                if (r < 0) {
+                        free(w);
+                        return r;
+                }
+
+                r = hashmap_put(s->writers, key, w);
+                if (r < 0) {
+                        writer_close(w);
+                        free(w);
+                        return r;
+                }
+
+                r = open_output(w, host);
+                if (r < 0)
+                        return r;
+        }
+
+        *writer = w;
+        return 0;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static RemoteSource *request_meta(void **connection_cls, int fd, char *hostname) {
         RemoteSource *source;
 
         assert(connection_cls);
-        if (*connection_cls)
+        if (*connection_cls) {
+                free(hostname);
                 return *connection_cls;
+        }
 
         source = new0(RemoteSource, 1);
-        if (!source)
+        if (!source) {
+                free(hostname);
                 return NULL;
-        source->fd = -1;
+        }
+
+        source->fd = -1; /* fd */
+        source->name = hostname;
 
         log_debug("Added RemoteSource as connection metadata %p", source);
 
@@ -407,14 +488,25 @@ static int process_http_upload(
                 size_t *upload_data_size,
                 RemoteSource *source) {
 
-        bool finished = false;
+        Writer *w;
         int r;
+        bool finished = false;
 
         assert(source);
 
         log_debug("request_handler_upload: connection %p, %zu bytes",
                   connection, *upload_data_size);
 
+        r = get_writer(server, source->name, NULL, &w);
+        if (r < 0) {
+                log_warning("Failed to get writer for source %s (%s): %s",
+                            source->name, source->name, strerror(-r));
+                return mhd_respondf(connection,
+                                    MHD_HTTP_SERVICE_UNAVAILABLE,
+                                    "Failed to get writer for connection: %s.\n",
+                                    strerror(-r));
+        }
+
         if (*upload_data_size) {
                 log_info("Received %zu bytes", *upload_data_size);
 
@@ -427,7 +519,8 @@ static int process_http_upload(
                 finished = true;
 
         while (true) {
-                r = process_source(source, &server->writer, arg_compress, arg_seal);
+
+                r = process_source(source, w, arg_compress, arg_seal);
                 if (r == -EAGAIN || r == -EWOULDBLOCK)
                         break;
                 else if (r < 0) {
@@ -469,7 +562,8 @@ static int request_handler(
                 void **connection_cls) {
 
         const char *header;
-        int r ,code;
+        int r, code, fd;
+        char *hostname;
 
         assert(connection);
         assert(connection_cls);
@@ -498,13 +592,37 @@ static int request_handler(
                                    "Content-Type: application/vnd.fdo.journal"
                                    " is required.\n");
 
+        {
+                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");
+                        return code;
+                }
+
+                fd = ci->connect_fd;
+                assert(fd >= 0);
+        }
+
         if (server->check_trust) {
-                r = check_permissions(connection, &code);
+                r = check_permissions(connection, &code, &hostname);
                 if (r < 0)
                         return code;
+        } else {
+                r = getnameinfo_pretty(fd, &hostname);
+                if (r < 0) {
+                        return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           "Cannot check remote hostname");
+                }
         }
 
-        if (!request_meta(connection_cls))
+        assert(hostname);
+
+        if (!request_meta(connection_cls, fd, hostname))
                 return respond_oom(connection);
         return MHD_YES;
 }
@@ -592,7 +710,8 @@ static int setup_microhttpd_server(RemoteServer *s,
         }
 
         r = sd_event_add_io(s->events, &d->event,
-                            epoll_fd, EPOLLIN, dispatch_http_event, d);
+                            epoll_fd, EPOLLIN,
+                            dispatch_http_event, d);
         if (r < 0) {
                 log_error("Failed to add event callback: %s", strerror(-r));
                 goto error;
@@ -749,9 +868,17 @@ static int remoteserver_init(RemoteServer *s,
                         else
                                 r = add_raw_socket(s, fd);
                 } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
-                        log_info("Received a connection socket (fd:%d)", fd);
+                        _cleanup_free_ char *hostname = NULL;
+
+                        r = getnameinfo_pretty(fd, &hostname);
+                        if (r < 0) {
+                                log_error("Failed to retrieve remote name: %s", strerror(-r));
+                                return r;
+                        }
+
+                        log_info("Received a connection socket (fd:%d) from %s", fd, hostname);
 
-                        r = add_source(s, fd, NULL);
+                        r = add_source(s, fd, hostname);
                 } else {
                         log_error("Unknown socket passed on fd:%d", fd);
 
@@ -768,13 +895,9 @@ static int remoteserver_init(RemoteServer *s,
         }
 
         if (arg_url) {
-                _cleanup_free_ char *url = NULL;
-                _cleanup_strv_free_ char **urlv = strv_new(arg_url, "/entries", NULL);
-                if (!urlv)
-                        return log_oom();
-                url = strv_join(urlv, "");
-                if (!url)
-                        return log_oom();
+                const char *url, *hostname;
+
+                url = strappenda(arg_url, "/entries");
 
                 if (arg_getter) {
                         log_info("Spawning getter %s...", url);
@@ -786,7 +909,12 @@ static int remoteserver_init(RemoteServer *s,
                 if (fd < 0)
                         return fd;
 
-                r = add_source(s, fd, arg_url);
+                hostname =
+                        startswith(arg_url, "https://") ?:
+                        startswith(arg_url, "http://") ?:
+                        arg_url;
+
+                r = add_source(s, fd, hostname);
                 if (r < 0)
                         return r;
 
@@ -848,20 +976,42 @@ static int remoteserver_init(RemoteServer *s,
         if (!!n + !!arg_url + !!arg_listen_raw + !!arg_files)
                 output_name = "multiple";
 
-        r = writer_init(&s->writer);
+        r = init_writer_hashmap(s);
         if (r < 0)
                 return r;
 
-        r = open_output(&s->writer, output_name);
-        return r;
+        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. */
+                Writer *w;
+
+                r = get_writer(s, NULL, NULL, &w);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
 }
 
-static int server_destroy(RemoteServer *s) {
+static int server_destroy(RemoteServer *s, uint64_t *event_count) {
         int r;
         size_t i;
+        Writer *w;
         MHDDaemonWrapper *d;
 
-        r = writer_close(&s->writer);
+        *event_count = 0;
+
+        while ((w = hashmap_steal_first(s->writers))) {
+                *event_count += w->seqnum;
+
+                r = writer_close(w);
+                if (r < 0)
+                        log_warning("Failed to close writer: %s", strerror(-r));
+                free(w);
+        }
+
+        hashmap_free(s->writers);
 
         while ((d = hashmap_steal_first(s->daemons))) {
                 MHD_stop_daemon(d->daemon);
@@ -896,6 +1046,7 @@ static int dispatch_raw_source_event(sd_event_source *event,
                                      uint32_t revents,
                                      void *userdata) {
 
+        Writer *w;
         RemoteServer *s = userdata;
         RemoteSource *source;
         int r;
@@ -904,7 +1055,14 @@ static int dispatch_raw_source_event(sd_event_source *event,
         source = s->sources[fd];
         assert(source->fd == fd);
 
-        r = process_source(source, &s->writer, arg_compress, arg_seal);
+        r = get_writer(s, source->name, NULL, &w);
+        if (r < 0) {
+                log_warning("Failed to get writer for source %s (%s): %s",
+                            source->name, source->name, strerror(-r));
+                return r;
+        }
+
+        r = process_source(source, w, arg_compress, arg_seal);
         if (source->state == STATE_EOF) {
                 log_info("EOF reached with source fd:%d (%s)",
                          source->fd, source->name);
@@ -912,15 +1070,22 @@ static int dispatch_raw_source_event(sd_event_source *event,
                         log_warning("EOF reached with incomplete data");
                 remove_source(s, source->fd);
                 log_info("%zd active source remaining", s->active);
+                return 0;
         } else if (r == -E2BIG) {
                 log_error("Entry too big, skipped");
-                r = 1;
-        }
-
-        return r;
+                return 1;
+        } else if (r == -EAGAIN) {
+                return 0;
+        } else if (r < 0) {
+                log_info("Closing connection: %s", strerror(-r));
+                remove_source(server, fd);
+                return 0;
+        } else
+                return 1;
 }
 
-static int accept_connection(const char* type, int fd, SocketAddress *addr) {
+static int accept_connection(const char* type, int fd,
+                             SocketAddress *addr, char **hostname) {
         int fd2, r;
 
         log_debug("Accepting new %s connection on fd:%d", type, fd);
@@ -933,7 +1098,8 @@ static int accept_connection(const char* type, int fd, SocketAddress *addr) {
         switch(socket_address_family(addr)) {
         case AF_INET:
         case AF_INET6: {
-                char* _cleanup_free_ a = NULL;
+                _cleanup_free_ char *a = NULL;
+                char *b;
 
                 r = socket_address_print(addr, &a);
                 if (r < 0) {
@@ -942,11 +1108,19 @@ static int accept_connection(const char* type, int fd, SocketAddress *addr) {
                         return r;
                 }
 
+                r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b);
+                if (r < 0) {
+                        close(fd2);
+                        return r;
+                }
+
                 log_info("Accepted %s %s connection from %s",
                          type,
                          socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
                          a);
 
+                *hostname = b;
+
                 return fd2;
         };
         default:
@@ -968,23 +1142,36 @@ static int dispatch_raw_connection_event(sd_event_source *event,
                 .size = sizeof(union sockaddr_union),
                 .type = SOCK_STREAM,
         };
+        char *hostname;
 
-        fd2 = accept_connection("raw", fd, &addr);
+        fd2 = accept_connection("raw", fd, &addr, &hostname);
         if (fd2 < 0)
                 return fd2;
 
-        return add_source(s, fd2, NULL);
+        return add_source(s, fd2, hostname);
 }
 
 /**********************************************************************
  **********************************************************************
  **********************************************************************/
 
+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",  "ServerKeyFile",          config_parse_path,       0, &arg_key        },
-                { "Remote",  "ServerCertificateFile",  config_parse_path,       0, &arg_cert       },
-                { "Remote",  "TrustedCertificateFile", config_parse_path,       0, &arg_trust      },
+                { "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      },
                 {}};
         int r;
 
@@ -1033,6 +1220,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_LISTEN_HTTP,
                 ARG_LISTEN_HTTPS,
                 ARG_GETTER,
+                ARG_SPLIT_MODE,
                 ARG_COMPRESS,
                 ARG_NO_COMPRESS,
                 ARG_SEAL,
@@ -1052,6 +1240,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "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",     no_argument,       NULL, ARG_COMPRESS     },
                 { "no-compress",  no_argument,       NULL, ARG_NO_COMPRESS  },
                 { "seal",         no_argument,       NULL, ARG_SEAL         },
@@ -1064,6 +1253,7 @@ static int parse_argv(int argc, char *argv[]) {
         };
 
         int c, r;
+        bool type_a, type_b;
 
         assert(argc >= 0);
         assert(argv);
@@ -1187,6 +1377,14 @@ static int parse_argv(int argc, char *argv[]) {
                         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:
                         arg_compress = true;
                         break;
@@ -1233,6 +1431,43 @@ static int parse_argv(int argc, char *argv[]) {
         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;
+                }
+
+                arg_split_mode = JOURNAL_WRITE_SPLIT_NONE;
+        }
+
+        if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE
+            && arg_output && is_dir(arg_output, true) > 0) {
+                log_error("For SplitMode=none, output must be a file.");
+                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 */;
 }
 
@@ -1296,6 +1531,7 @@ int main(int argc, char **argv) {
         RemoteServer s = {};
         int r, r2;
         _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL;
+        uint64_t entry_count;
 
         log_show_color(true);
         log_parse_environment();
@@ -1312,8 +1548,9 @@ int main(int argc, char **argv) {
         if (r < 0)
                 return EXIT_FAILURE;
 
-        if (load_certificates(&key, &cert, &trust) < 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;
@@ -1340,8 +1577,8 @@ int main(int argc, char **argv) {
                 }
         }
 
-        log_info("Finishing after writing %" PRIu64 " entries", s.writer.seqnum);
-        r2 = server_destroy(&s);
+        r2 = server_destroy(&s, &entry_count);
+        log_info("Finishing after writing %" PRIu64 " entries", entry_count);
 
         sd_notify(false, "STATUS=Shutting down...");
 
diff --git a/src/journal-remote/journal-remote.conf.in b/src/journal-remote/journal-remote.conf.in
index a06c7e0..3e32f34 100644
--- a/src/journal-remote/journal-remote.conf.in
+++ b/src/journal-remote/journal-remote.conf.in
@@ -1,4 +1,5 @@
 [Remote]
+# SplitMode=host
 # ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem
 # ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem
 # TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index 264f915..680073f 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -297,9 +297,19 @@ static int dispatch_fd_input(sd_event_source *event,
         Uploader *u = userp;
 
         assert(u);
-        assert(revents & EPOLLIN);
         assert(fd >= 0);
 
+        if (revents & EPOLLHUP) {
+                log_debug("Received HUP");
+                close_fd_input(u);
+                return 0;
+        }
+
+        if (!(revents & EPOLLIN)) {
+                log_warning("Unexpected poll event %"PRIu32".", revents);
+                return -EINVAL;
+        }
+
         if (u->uploading) {
                 log_warning("dispatch_fd_input called when uploading, ignoring.");
                 return 0;
diff --git a/src/journal-remote/microhttpd-util.c b/src/journal-remote/microhttpd-util.c
index d046686..5335493 100644
--- a/src/journal-remote/microhttpd-util.c
+++ b/src/journal-remote/microhttpd-util.c
@@ -243,7 +243,7 @@ static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
         return 0;
 }
 
-int check_permissions(struct MHD_Connection *connection, int *code) {
+int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
         const union MHD_ConnectionInfo *ci;
         gnutls_session_t session;
         gnutls_x509_crt_t client_cert;
@@ -282,6 +282,11 @@ int check_permissions(struct MHD_Connection *connection, int *code) {
 
         log_info("Connection from %s", buf);
 
+        if (hostname) {
+                *hostname = buf;
+                buf = NULL;
+        }
+
         r = verify_cert_authorized(session);
         if (r < 0) {
                 log_warning("Client is not authorized");
diff --git a/src/journal-remote/microhttpd-util.h b/src/journal-remote/microhttpd-util.h
index 4186da8..c43d7f7 100644
--- a/src/journal-remote/microhttpd-util.h
+++ b/src/journal-remote/microhttpd-util.h
@@ -41,7 +41,7 @@ int mhd_respond(struct MHD_Connection *connection,
 
 int mhd_respond_oom(struct MHD_Connection *connection);
 
-int check_permissions(struct MHD_Connection *connection, int *code);
+int check_permissions(struct MHD_Connection *connection, int *code, char **hostname);
 
 #ifdef HAVE_GNUTLS
 void log_func_gnutls(int level, const char *message);

commit 24739b7b757d22a4b629d410a768e4e85fb02282
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Mon Jun 30 01:11:32 2014 -0400

    Allow addresses to be specified for --listen-... args
    
    Hostnames still aren't accepted.

diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 5b991e9..22f067a 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -698,10 +698,7 @@ static int fd_fd(const char *spec) {
         if (r < 0)
                 return r;
 
-        if (fd >= 0)
-                return -ENOENT;
-
-        return -fd;
+        return -1;
 }
 
 
@@ -1118,14 +1115,8 @@ static int parse_argv(int argc, char *argv[]) {
                         r = fd_fd(optarg);
                         if (r >= 0)
                                 http_socket = r;
-                        else if (r == -ENOENT)
+                        else
                                 arg_listen_http = optarg;
-                        else {
-                                log_error("Invalid port/fd specification %s: %s",
-                                          optarg, strerror(-r));
-                                return -EINVAL;
-                        }
-
                         break;
 
                 case ARG_LISTEN_HTTPS:
@@ -1137,13 +1128,8 @@ static int parse_argv(int argc, char *argv[]) {
                         r = fd_fd(optarg);
                         if (r >= 0)
                                 https_socket = r;
-                        else if (r == -ENOENT)
+                        else
                                 arg_listen_https = optarg;
-                        else {
-                                log_error("Invalid port/fd specification %s: %s",
-                                          optarg, strerror(-r));
-                                return -EINVAL;
-                        }
 
                         break;
 

commit d3b7fd541b97a946bc0c351357d0f2d277f119cd
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Mon Jun 30 00:57:15 2014 -0400

    Add simple generator of fake journal export stream

diff --git a/src/journal-remote/log-generator.py b/src/journal-remote/log-generator.py
new file mode 100755
index 0000000..9a8fb07
--- /dev/null
+++ b/src/journal-remote/log-generator.py
@@ -0,0 +1,68 @@
+#!/usr/bin/python
+from __future__ import print_function
+import sys
+import argparse
+
+PARSER = argparse.ArgumentParser()
+PARSER.add_argument('n', type=int)
+PARSER.add_argument('--dots', action='store_true')
+OPTIONS = PARSER.parse_args()
+
+template = """\
+__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c
+__REALTIME_TIMESTAMP={realtime_ts}
+__MONOTONIC_TIMESTAMP={monotonic_ts}
+_BOOT_ID=f446871715504074bf7049ef0718fa93
+_TRANSPORT=syslog
+PRIORITY={priority}
+SYSLOG_FACILITY={facility}
+SYSLOG_IDENTIFIER=/USR/SBIN/CRON
+MESSAGE={message}
+_UID=0
+_GID=0
+_MACHINE_ID=69121ca41d12c1b69a7960174c27b618
+_HOSTNAME=hostname
+SYSLOG_PID=25721
+_PID=25721
+_SOURCE_REALTIME_TIMESTAMP={source_realtime_ts}
+DATA={data}
+"""
+
+m  = 0x198603b12d7
+realtime_ts = 1404101101501873
+monotonic_ts = 1753961140951
+source_realtime_ts = 1404101101483516
+priority = 3
+facility = 6
+
+src = open('/dev/urandom', 'rb')
+
+bytes = 0
+
+for i in range(OPTIONS.n):
+    message = repr(src.read(2000))
+    data = repr(src.read(4000))
+
+    entry = template.format(m=m,
+                            realtime_ts=realtime_ts,
+                            monotonic_ts=monotonic_ts,
+                            source_realtime_ts=source_realtime_ts,
+                            priority=priority,
+                            facility=facility,
+                            message=message,
+                            data=data)
+    m += 1
+    realtime_ts += 1
+    monotonic_ts += 1
+    source_realtime_ts += 1
+
+    bytes += len(entry)
+
+    print(entry)
+
+    if OPTIONS.dots:
+        print('.', file=sys.stderr, end='', flush=True)
+
+if OPTIONS.dots:
+        print(file=sys.stderr)
+print('Wrote {} bytes'.format(bytes), file=sys.stderr)

commit ef274c6444d5c5aa93ffd58c7ec6c12295bf55dc
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Jun 29 22:06:48 2014 -0400

    shared/socket-label: fix error message
    
    Was: Failed to listen on [::]:2000: Success

diff --git a/src/shared/socket-label.c b/src/shared/socket-label.c
index eb09779..83ea1a9 100644
--- a/src/shared/socket-label.c
+++ b/src/shared/socket-label.c
@@ -166,7 +166,7 @@ int make_socket_fd(int log_level, const char* address, int flags) {
                 }
 
                 if (fd < 0)
-                        log_error("Failed to listen on %s: %s", p, strerror(-r));
+                        log_error("Failed to listen on %s: %s", p, strerror(-fd));
                 else
                         log_full(log_level, "Listening on %s", p);
         }

commit b31f535c9a7af9b5f5c4f5d4e52be9159af72127
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Jun 29 19:24:56 2014 -0400

    shared/socket-util: add function to query remote address

diff --git a/src/shared/socket-util.c b/src/shared/socket-util.c
index 92564e3..0a0726d 100644
--- a/src/shared/socket-util.c
+++ b/src/shared/socket-util.c
@@ -31,6 +31,7 @@
 #include <sys/stat.h>
 #include <stddef.h>
 #include <sys/ioctl.h>
+#include <netdb.h>
 
 #include "macro.h"
 #include "util.h"
@@ -574,13 +575,12 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_
 
 int getpeername_pretty(int fd, char **ret) {
         union sockaddr_union sa;
-        socklen_t salen;
+        socklen_t salen = sizeof(sa);
         int r;
 
         assert(fd >= 0);
         assert(ret);
 
-        salen = sizeof(sa);
         if (getpeername(fd, &sa.sa, &salen) < 0)
                 return -errno;
 
@@ -608,12 +608,11 @@ int getpeername_pretty(int fd, char **ret) {
 
 int getsockname_pretty(int fd, char **ret) {
         union sockaddr_union sa;
-        socklen_t salen;
+        socklen_t salen = sizeof(sa);
 
         assert(fd >= 0);
         assert(ret);
 
-        salen = sizeof(sa);
         if (getsockname(fd, &sa.sa, &salen) < 0)
                 return -errno;
 
@@ -625,6 +624,49 @@ int getsockname_pretty(int fd, char **ret) {
         return sockaddr_pretty(&sa.sa, salen, false, ret);
 }
 
+int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) {
+        int r;
+        char host[NI_MAXHOST], *ret;
+
+        assert(_ret);
+
+        r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0,
+                        NI_IDN|NI_IDN_USE_STD3_ASCII_RULES);
+        if (r != 0) {
+                _cleanup_free_ char *sockname = NULL;
+                int saved_errno = errno;
+
+                r = sockaddr_pretty(&sa->sa, salen, true, &sockname);
+                if (r < 0)
+                        log_error("sockadd_pretty() failed: %s", strerror(-r));
+                else
+                        log_error("getnameinfo(%s) failed: %s", sockname, strerror(-r));
+                return -saved_errno;
+        }
+
+        ret = strdup(host);
+        if (!ret)
+                return log_oom();
+
+        *_ret = ret;
+        return 0;
+}
+
+int getnameinfo_pretty(int fd, char **ret) {
+        union sockaddr_union sa;
+        socklen_t salen = sizeof(sa);
+
+        assert(fd >= 0);
+        assert(ret);
+
+        if (getsockname(fd, &sa.sa, &salen) < 0) {
+                log_error("getsockname(%d) failed: %m", fd);
+                return -errno;
+        }
+
+        return socknameinfo_pretty(&sa, salen, ret);
+}
+
 int socket_address_unlink(SocketAddress *a) {
         assert(a);
 
diff --git a/src/shared/socket-util.h b/src/shared/socket-util.h
index ec85d94..9883ab0 100644
--- a/src/shared/socket-util.h
+++ b/src/shared/socket-util.h
@@ -101,6 +101,9 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_
 int getpeername_pretty(int fd, char **ret);
 int getsockname_pretty(int fd, char **ret);
 
+int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret);
+int getnameinfo_pretty(int fd, char **ret);
+
 const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_;
 SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_;
 
diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c
index ed183e8..96812e5 100644
--- a/src/test/test-socket-util.c
+++ b/src/test/test-socket-util.c
@@ -22,6 +22,7 @@
 #include "util.h"
 #include "macro.h"
 #include "log.h"
+#include "async.h"
 
 static void test_socket_address_parse(void) {
         SocketAddress a;
@@ -212,6 +213,58 @@ static void test_in_addr_prefix_next(void) {
 
 }
 
+static void *connect_thread(void *arg) {
+        union sockaddr_union *sa = arg;
+        _cleanup_close_ int fd = -1;
+
+        fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+        assert(fd >= 0);
+
+        assert_se(connect(fd, &sa->sa, sizeof(sa->in)) == 0);
+
+        return NULL;
+}
+
+static void test_nameinfo_pretty(void) {
+        _cleanup_free_ char *stdin = NULL, *localhost = NULL;
+
+        union sockaddr_union s = {
+                .in.sin_family = AF_INET,
+                .in.sin_port = 0,
+                .in.sin_addr.s_addr = htonl(INADDR_ANY),
+        };
+        int r;
+
+        union sockaddr_union c = {};
+        socklen_t slen = sizeof(c.in), clen = sizeof(c.in);
+
+        _cleanup_close_ int sfd = -1, cfd = -1, afd = -1;
+        r = getnameinfo_pretty(STDIN_FILENO, &stdin);
+        log_info("No connection remote: %s", strerror(-r));
+
+        assert_se(r < 0);
+
+        sfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0);
+        assert(sfd >= 0);
+
+        assert_se(bind(sfd, &s.sa, sizeof(s.in)) == 0);
+
+        /* find out the port number */
+        assert_se(getsockname(sfd, &s.sa, &slen) == 0);
+
+        assert_se(listen(sfd, 1) == 0);
+
+        assert_se(asynchronous_job(connect_thread, &s) == 0);
+
+        log_debug("Accepting new connection on fd:%d", sfd);
+        cfd = accept4(sfd, &c.sa, &clen, SOCK_CLOEXEC);
+        assert(cfd >= 0);
+
+        r = getnameinfo_pretty(cfd, &localhost);
+        log_info("Connection from %s", localhost);
+        assert(r == 0);
+}
+
 int main(int argc, char *argv[]) {
 
         log_set_max_level(LOG_DEBUG);
@@ -224,5 +277,7 @@ int main(int argc, char *argv[]) {
         test_in_addr_prefix_intersect();
         test_in_addr_prefix_next();
 
+        test_nameinfo_pretty();
+
         return 0;
 }

commit ad95fd1d2b9c6344864857c2ba7634fd87753f8e
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Mar 30 23:08:02 2014 -0400

    journal-remote: add units and read certs from default locations

diff --git a/Makefile.am b/Makefile.am
index 7fefa58..9845836 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -133,8 +133,8 @@ polkitpolicy_in_files =
 polkitpolicy_files =
 dist_udevrules_DATA =
 nodist_udevrules_DATA =
-nodist_pkgsysconf_DATA =
 dist_pkgsysconf_DATA =
+nodist_pkgsysconf_DATA =
 dist_pkgdata_DATA =
 dist_dbuspolicy_DATA =
 dist_dbussystemservice_DATA =
@@ -164,6 +164,7 @@ AM_CPPFLAGS = \
 	-DSYSTEM_SYSVRCND_PATH=\"$(SYSTEM_SYSVRCND_PATH)\" \
 	-DUSER_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/user\" \
 	-DUSER_DATA_UNIT_PATH=\"$(userunitdir)\" \
+	-DCERTIFICATE_ROOT=\"$(CERTIFICATEROOT)\" \
 	-DCATALOG_DATABASE=\"$(catalogstatedir)/database\" \
 	-DSYSTEMD_CGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \
 	-DSYSTEMD_BINARY_PATH=\"$(rootlibexecdir)/systemd\" \
@@ -1818,6 +1819,7 @@ nodist_systemunit_DATA += \
 dist_tmpfiles_DATA = \
 	tmpfiles.d/systemd.conf \
 	tmpfiles.d/systemd-nologin.conf \
+	tmpfiles.d/systemd-remote.conf \
 	tmpfiles.d/tmp.conf \
 	tmpfiles.d/x11.conf \
 	tmpfiles.d/var.conf \
@@ -3473,7 +3475,34 @@ systemd_journal_remote_LDADD += \
 if HAVE_GNUTLS
 systemd_journal_remote_LDADD += \
 	$(GNUTLS_LIBS)
+
+# systemd-journal-remote make sense mostly with full crypto stack
+dist_systemunit_DATA += \
+	units/systemd-journal-remote.socket
+
+nodist_systemunit_DATA += \
+	units/systemd-journal-remote.service
+
+EXTRA_DIST += \
+	units/systemd-journal-remote.service.in
+
+journal-remote-install-hook: journal-install-hook
+	-$(MKDIR_P) $(DESTDIR)/var/log/journal/remote
+	-chown 0:0 $(DESTDIR)/var/log/journal/remote
+	-chmod 755 $(DESTDIR)/var/log/journal/remote
+
+INSTALL_EXEC_HOOKS += journal-remote-install-hook
+
 endif
+
+nodist_pkgsysconf_DATA += \
+	src/journal-remote/journal-remote.conf
+
+EXTRA_DIST += \
+	src/journal-remote/journal-remote.conf.in
+
+CLEANFILES += \
+	src/journal-remote/journal-remote.conf
 endif
 
 if HAVE_LIBCURL
@@ -3495,6 +3524,12 @@ systemd_journal_upload_LDADD = \
 	libsystemd-journal-internal.la \
 	libsystemd-shared.la \
 	$(LIBCURL_LIBS)
+
+nodist_systemunit_DATA += \
+	units/systemd-journal-upload.service
+
+EXTRA_DIST += \
+	units/systemd-journal-upload.service.in
 endif
 
 # using _CFLAGS = in the conditional below would suppress AM_CFLAGS
@@ -3663,6 +3698,7 @@ journal-install-hook:
 	-setfacl -nm g:wheel:rx,d:g:wheel:rx $(DESTDIR)/var/log/journal/
 
 journal-uninstall-hook:
+	-rmdir $(DESTDIR)/var/log/journal/remote
 	-rmdir $(DESTDIR)/var/log/journal/
 
 INSTALL_EXEC_HOOKS += journal-install-hook
@@ -5300,6 +5336,7 @@ substitutions = \
        '|sysctldir=$(sysctldir)|' \
        '|systemgeneratordir=$(systemgeneratordir)|' \
        '|usergeneratordir=$(usergeneratordir)|' \
+       '|CERTIFICATEROOT=$(CERTIFICATEROOT)|' \
        '|PACKAGE_VERSION=$(PACKAGE_VERSION)|' \
        '|PACKAGE_NAME=$(PACKAGE_NAME)|' \
        '|PACKAGE_URL=$(PACKAGE_URL)|' \
@@ -5352,6 +5389,9 @@ sysctl.d/%: sysctl.d/%.in
 %.pc: %.pc.in
 	$(SED_PROCESS)
 
+%.conf: %.conf.in
+	$(SED_PROCESS)
+
 src/core/macros.%: src/core/macros.%.in
 	$(SED_PROCESS)
 
diff --git a/configure.ac b/configure.ac
index 6e972e3..94aacc9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -498,6 +498,14 @@ AC_ARG_WITH([debug-tty],
 
 AC_SUBST(DEBUGTTY)
 
+AC_ARG_WITH([certificate-root],
+        AS_HELP_STRING([--with-certificate-root=PATH],
+                [Specify the prefix for TLS certificates [/etc/ssl]]),
+        [CERTIFICATEROOT="$withval"],
+        [CERTIFICATEROOT="/etc/ssl"])
+
+AC_SUBST(CERTIFICATEROOT)
+
 # ------------------------------------------------------------------------------
 have_xz=no
 AC_ARG_ENABLE(xz, AS_HELP_STRING([--disable-xz], [Disable optional XZ support]))
@@ -1377,6 +1385,7 @@ AC_MSG_RESULT([
         TTY GID:                 ${TTY_GID}
         Maximum System UID:      ${SYSTEM_UID_MAX}
         Maximum System GID:      ${SYSTEM_GID_MAX}
+        Certificate root:        ${CERTIFICATEROOT}
 
         CFLAGS:                  ${OUR_CFLAGS} ${CFLAGS}
         CPPFLAGS:                ${OUR_CPPFLAGS} ${CPPFLAGS}
diff --git a/src/journal-remote/.gitignore b/src/journal-remote/.gitignore
new file mode 100644
index 0000000..8112c3c
--- /dev/null
+++ b/src/journal-remote/.gitignore
@@ -0,0 +1 @@
+/journal-remote.conf
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 063d6df..5b991e9 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -42,6 +42,7 @@
 #include "macro.h"
 #include "strv.h"
 #include "fileio.h"
+#include "conf-parser.h"
 #include "microhttpd-util.h"
 
 #ifdef HAVE_GNUTLS
@@ -53,6 +54,10 @@
 
 #define REMOTE_JOURNAL_PATH "/var/log/journal/" SD_ID128_FORMAT_STR "/remote-%s.journal"
 
+#define 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_output = NULL;
 static char* arg_url = NULL;
 static char* arg_getter = NULL;
@@ -65,9 +70,10 @@ static int arg_seal = false;
 static int http_socket = -1, https_socket = -1;
 static char** arg_gnutls_log = NULL;
 
-static char *key_pem = NULL;
-static char *cert_pem = NULL;
-static char *trust_pem = NULL;
+static char *arg_key = NULL;
+static char *arg_cert = NULL;
+static char *arg_trust = NULL;
+static bool arg_trust_all = false;
 
 /**********************************************************************
  **********************************************************************
@@ -234,6 +240,7 @@ typedef struct RemoteServer {
 
         Writer writer;
 
+        bool check_trust;
         Hashmap *daemons;
 } RemoteServer;
 
@@ -491,7 +498,7 @@ static int request_handler(
                                    "Content-Type: application/vnd.fdo.journal"
                                    " is required.\n");
 
-        if (trust_pem) {
+        if (server->check_trust) {
                 r = check_permissions(connection, &code);
                 if (r < 0)
                         return code;
@@ -502,7 +509,11 @@ static int request_handler(
         return MHD_YES;
 }
 
-static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
+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},
@@ -530,17 +541,19 @@ static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
                 return r;
         }
 
-        if (https) {
+        if (key) {
+                assert(cert);
+
                 opts[opts_pos++] = (struct MHD_OptionItem)
-                        {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
+                        {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key};
                 opts[opts_pos++] = (struct MHD_OptionItem)
-                        {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
+                        {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert};
 
                 flags |= MHD_USE_SSL;
 
-                if (trust_pem)
+                if (trust)
                         opts[opts_pos++] = (struct MHD_OptionItem)
-                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
+                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust};
         }
 
         d = new(MHDDaemonWrapper, 1);
@@ -561,7 +574,7 @@ static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
         }
 
         log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)",
-                  https ? "HTTPS" : "HTTP", fd, d);
+                  key ? "HTTPS" : "HTTP", fd, d);
 
 
         info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
@@ -609,14 +622,16 @@ error:
 
 static int setup_microhttpd_socket(RemoteServer *s,
                                    const char *address,
-                                   bool https) {
+                                   const char *key,
+                                   const char *cert,
+                                   const char *trust) {
         int fd;
 
         fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
         if (fd < 0)
                 return fd;
 
-        return setup_microhttpd_server(s, fd, https);
+        return setup_microhttpd_server(s, fd, key, cert, trust);
 }
 
 static int dispatch_http_event(sd_event_source *event,
@@ -690,13 +705,22 @@ static int fd_fd(const char *spec) {
 }
 
 
-static int remoteserver_init(RemoteServer *s) {
+static int remoteserver_init(RemoteServer *s,
+                             const char* key,
+                             const char* cert,
+                             const char* trust) {
         int r, n, fd;
         const char *output_name = NULL;
         char **file;
 
         assert(s);
 
+
+        if ((arg_listen_raw || arg_listen_http) && trust) {
+                log_error("Option --trust makes all non-HTTPS connections untrusted.");
+                return -EINVAL;
+        }
+
         sd_event_default(&s->events);
 
         setup_signals(s);
@@ -722,9 +746,9 @@ static int remoteserver_init(RemoteServer *s) {
                         log_info("Received a listening socket (fd:%d)", fd);
 
                         if (fd == http_socket)
-                                r = setup_microhttpd_server(s, fd, false);
+                                r = setup_microhttpd_server(s, fd, NULL, NULL, NULL);
                         else if (fd == https_socket)
-                                r = setup_microhttpd_server(s, fd, true);
+                                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, true)) {
@@ -782,7 +806,7 @@ static int remoteserver_init(RemoteServer *s) {
         }
 
         if (arg_listen_http) {
-                r = setup_microhttpd_socket(s, arg_listen_http, false);
+                r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL);
                 if (r < 0)
                         return r;
 
@@ -790,7 +814,7 @@ static int remoteserver_init(RemoteServer *s) {
         }
 
         if (arg_listen_https) {
-                r = setup_microhttpd_socket(s, arg_listen_https, true);
+                r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust);
                 if (r < 0)
                         return r;
 
@@ -959,6 +983,24 @@ static int dispatch_raw_connection_event(sd_event_source *event,
  **********************************************************************
  **********************************************************************/
 
+static int parse_config(void) {
+        const ConfigTableItem items[] = {
+                { "Remote",  "ServerKeyFile",          config_parse_path,       0, &arg_key        },
+                { "Remote",  "ServerCertificateFile",  config_parse_path,       0, &arg_cert       },
+                { "Remote",  "TrustedCertificateFile", config_parse_path,       0, &arg_trust      },
+                {}};
+        int r;
+
+        r = config_parse(NULL, PKGSYSCONFDIR "/journal-remote.conf", NULL,
+                         "Remote\0",
+                         config_item_table_lookup, items,
+                         false, false, NULL);
+        if (r < 0)
+                log_error("Failed to parse configuration file: %s", strerror(-r));
+
+        return r;
+}
+
 static void help(void) {
         printf("%s [OPTIONS...] {FILE|-}...\n\n"
                "Write external journal events to a journal file.\n\n"
@@ -971,9 +1013,12 @@ static void help(void) {
                "  -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
                "  --[no-]compress      Use XZ-compression in the output journal (default: yes)\n"
                "  --[no-]seal          Use Event sealing in the output journal (default: no)\n"
-               "  --key=FILENAME       Specify key in PEM format\n"
-               "  --cert=FILENAME      Specify certificate in PEM format\n"
-               "  --trust=FILENAME     Specify CA certificate in PEM format\n"
+               "  --key=FILENAME       Specify key in PEM format (default:\n"
+               "                           \"" KEY_FILE "\")\n"
+               "  --cert=FILENAME      Specify certificate in PEM format (default:\n"
+               "                           \"" CERT_FILE "\")\n"
+               "  --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
+               "                           \"" TRUST_FILE "\")\n"
                "  --gnutls-log=CATEGORY...\n"
                "                       Specify a list of gnutls logging categories\n"
                "  -h --help            Show this help and exit\n"
@@ -1103,48 +1148,49 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_KEY:
-                        if (key_pem) {
+                        if (arg_key) {
                                 log_error("Key file specified twice");
                                 return -EINVAL;
                         }
-                        r = read_full_file(optarg, &key_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read key file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(key_pem);
+
+                        arg_key = strdup(optarg);
+                        if (!arg_key)
+                                return log_oom();
+
                         break;
 
                 case ARG_CERT:
-                        if (cert_pem) {
+                        if (arg_cert) {
                                 log_error("Certificate file specified twice");
                                 return -EINVAL;
                         }
-                        r = read_full_file(optarg, &cert_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read certificate file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(cert_pem);
+
+                        arg_cert = strdup(optarg);
+                        if (!arg_cert)
+                                return log_oom();
+
                         break;
 
                 case ARG_TRUST:
-#ifdef HAVE_GNUTLS
-                        if (trust_pem) {
-                                log_error("CA certificate file specified twice");
+                        if (arg_trust || arg_trust_all) {
+                                log_error("Confusing trusted CA configuration");
                                 return -EINVAL;
                         }
-                        r = read_full_file(optarg, &trust_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read CA certificate file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(trust_pem);
-                        break;
+
+                        if (streq(optarg, "all"))
+                                arg_trust_all = true;
+                        else {
+#ifdef HAVE_GNUTLS
+                                arg_trust = strdup(optarg);
+                                if (!arg_trust)
+                                        return log_oom();
 #else
-                        log_error("Option --trust is not available.");
-                        return -EINVAL;
+                                log_error("Option --trust is not available.");
+                                return -EINVAL;
 #endif
+                        }
+
+                        break;
 
                 case 'o':
                         if (arg_output) {
@@ -1198,17 +1244,43 @@ static int parse_argv(int argc, char *argv[]) {
                         return -EINVAL;
                 }
 
-        if (arg_listen_https && !(key_pem && cert_pem)) {
-                log_error("Options --key and --cert must be used when using HTTPS.");
-                return -EINVAL;
-        }
-
         if (optind < argc)
                 arg_files = argv + optind;
 
         return 1 /* work to do */;
 }
 
+static int load_certificates(char **key, char **cert, char **trust) {
+        int r;
+
+        r = read_full_file(arg_key ?: KEY_FILE, key, NULL);
+        if (r < 0) {
+                log_error("Failed to read key from file '%s': %s",
+                          arg_key ?: KEY_FILE, strerror(-r));
+                return r;
+        }
+
+        r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL);
+        if (r < 0) {
+                log_error("Failed to read certificate from file '%s': %s",
+                          arg_cert ?: CERT_FILE, strerror(-r));
+                return r;
+        }
+
+        if (arg_trust_all)
+                log_info("Certificate checking disabled.");
+        else {
+                r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL);
+                if (r < 0) {
+                        log_error("Failed to read CA certificate file '%s': %s",
+                                  arg_trust ?: TRUST_FILE, strerror(-r));
+                        return r;
+                }
+        }
+
+        return 0;
+}
+
 static int setup_gnutls_logger(char **categories) {
         if (!arg_listen_http && !arg_listen_https)
                 return 0;
@@ -1237,10 +1309,15 @@ static int setup_gnutls_logger(char **categories) {
 int main(int argc, char **argv) {
         RemoteServer s = {};
         int r, r2;
+        _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;
@@ -1249,7 +1326,10 @@ int main(int argc, char **argv) {
         if (r < 0)
                 return EXIT_FAILURE;
 
-        if (remoteserver_init(&s) < 0)
+        if (load_certificates(&key, &cert, &trust) < 0)
+                return EXIT_FAILURE;
+
+        if (remoteserver_init(&s, key, cert, trust) < 0)
                 return EXIT_FAILURE;
 
         sd_event_set_watchdog(s.events, true);
@@ -1279,5 +1359,9 @@ int main(int argc, char **argv) {
 
         sd_notify(false, "STATUS=Shutting down...");
 
+        free(arg_key);
+        free(arg_cert);
+        free(arg_trust);
+
         return r >= 0 && r2 >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 }
diff --git a/src/journal-remote/journal-remote.conf.in b/src/journal-remote/journal-remote.conf.in
new file mode 100644
index 0000000..a06c7e0
--- /dev/null
+++ b/src/journal-remote/journal-remote.conf.in
@@ -0,0 +1,4 @@
+[Remote]
+# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem
+# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem
+# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem
diff --git a/tmpfiles.d/systemd-remote.conf b/tmpfiles.d/systemd-remote.conf
new file mode 100644
index 0000000..1b8973a
--- /dev/null
+++ b/tmpfiles.d/systemd-remote.conf
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+# See tmpfiles.d(5) for details
+
+z /var/log/journal/remote 2755 root systemd-journal-remote - -
+z /run/log/journal/remote 2755 root systemd-journal-remote - -
diff --git a/units/.gitignore b/units/.gitignore
index 5d68927..d9b60ac 100644
--- a/units/.gitignore
+++ b/units/.gitignore
@@ -31,17 +31,19 @@
 /systemd-hostnamed.service
 /systemd-hybrid-sleep.service
 /systemd-initctl.service
+/systemd-journal-catalog-update.service
 /systemd-journal-flush.service
 /systemd-journal-gatewayd.service
-/systemd-journal-catalog-update.service
+/systemd-journal-remote.service
+/systemd-journal-upload.service
 /systemd-journald.service
 /systemd-kexec.service
 /systemd-localed.service
 /systemd-logind.service
 /systemd-machined.service
 /systemd-modules-load.service
-/systemd-networkd.service
 /systemd-networkd-wait-online.service
+/systemd-networkd.service
 /systemd-nspawn at .service
 /systemd-poweroff.service
 /systemd-quotacheck.service
@@ -67,9 +69,9 @@
 /systemd-udev-settle.service
 /systemd-udev-trigger.service
 /systemd-udevd.service
+/systemd-update-done.service
 /systemd-update-utmp-runlevel.service
 /systemd-update-utmp.service
-/systemd-update-done.service
 /systemd-user-sessions.service
 /systemd-vconsole-setup.service
 /user at .service
diff --git a/units/systemd-journal-remote.service.in b/units/systemd-journal-remote.service.in
new file mode 100644
index 0000000..4a898d6
--- /dev/null
+++ b/units/systemd-journal-remote.service.in
@@ -0,0 +1,24 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Journal Remote Sink Service
+Requires=systemd-journal-remote.socket
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-journal-remote \
+          --listen-https=-3 \
+          --output=/var/log/journal/remote/
+User=systemd-journal-remote
+Group=systemd-journal-remote
+PrivateTmp=yes
+PrivateDevices=yes
+PrivateNetwork=yes
+WatchdogSec=10min
+
+[Install]
+Also=systemd-journal-remote.socket
diff --git a/units/systemd-journal-remote.socket b/units/systemd-journal-remote.socket
new file mode 100644
index 0000000..076dcae
--- /dev/null
+++ b/units/systemd-journal-remote.socket
@@ -0,0 +1,15 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Journal Remote Sink Socket
+
+[Socket]
+ListenStream=19532
+
+[Install]
+WantedBy=sockets.target
diff --git a/units/systemd-journal-upload.service.in b/units/systemd-journal-upload.service.in
new file mode 100644
index 0000000..6388291
--- /dev/null
+++ b/units/systemd-journal-upload.service.in
@@ -0,0 +1,22 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Journal Remote Upload Service
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-journal-upload \
+          --save-state
+User=systemd-journal-upload
+Group=systemd-journal-uplaod
+PrivateTmp=yes
+PrivateDevices=yes
+WatchdogSec=20min
+
+[Install]
+WantedBy=multi-user.target
+After=network.target

commit a3152e7655231b94fa7b9582906fb86ab00b9c99
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Tue Apr 1 20:30:13 2014 -0400

    journal-upload: add watchdog support

diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 906759b..063d6df 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -1252,6 +1252,8 @@ int main(int argc, char **argv) {
         if (remoteserver_init(&s) < 0)
                 return EXIT_FAILURE;
 
+        sd_event_set_watchdog(s.events, true);
+
         log_debug("%s running as pid "PID_FMT,
                   program_invocation_short_name, getpid());
         sd_notify(false,
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index 7685537..264f915 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -340,6 +340,43 @@ static int open_file_for_upload(Uploader *u, const char *filename) {
         return r;
 }
 
+static int dispatch_sigterm(sd_event_source *event,
+                            const struct signalfd_siginfo *si,
+                            void *userdata) {
+        Uploader *u = userdata;
+
+        assert(u);
+
+        log_received_signal(LOG_INFO, si);
+
+        close_fd_input(u);
+        close_journal_input(u);
+
+        sd_event_exit(u->events, 0);
+        return 0;
+}
+
+static int setup_signals(Uploader *u) {
+        sigset_t mask;
+        int r;
+
+        assert(u);
+
+        assert_se(sigemptyset(&mask) == 0);
+        sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+        assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+        r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u);
+        if (r < 0)
+                return r;
+
+        r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
         int r;
 
@@ -358,6 +395,12 @@ static int setup_uploader(Uploader *u, const char *url, const char *state_file)
                 return r;
         }
 
+        r = setup_signals(u);
+        if (r < 0) {
+                log_error("Failed to set up signals: %s", strerror(-r));
+                return r;
+        }
+
         return load_cursor_state(u);
 }
 
@@ -376,6 +419,8 @@ static void destroy_uploader(Uploader *u) {
         close_fd_input(u);
         close_journal_input(u);
 
+        sd_event_source_unref(u->sigterm_event);
+        sd_event_source_unref(u->sigint_event);
         sd_event_unref(u->events);
 }
 
@@ -668,6 +713,8 @@ int main(int argc, char **argv) {
         if (r < 0)
                 goto cleanup;
 
+        sd_event_set_watchdog(u.events, true);
+
         log_debug("%s running as pid "PID_FMT,
                   program_invocation_short_name, getpid());
 
diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h
index 0adb915..9ccad10 100644
--- a/src/journal-remote/journal-upload.h
+++ b/src/journal-remote/journal-upload.h
@@ -21,6 +21,7 @@ typedef enum {
 
 typedef struct Uploader {
         sd_event *events;
+        sd_event_source *sigint_event, *sigterm_event;
 
         const char *url;
         CURL *easy;

commit 722b6795655149a68277b3cffeba711e1d440e5a
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Tue Apr 1 09:09:35 2014 -0400

    journal-upload: make state persistent

diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c
index a3be1bf..1cd52db 100644
--- a/src/journal-remote/journal-upload-journal.c
+++ b/src/journal-remote/journal-upload-journal.c
@@ -21,17 +21,17 @@ static ssize_t write_entry(char *buf, size_t size, Uploader *u) {
 
                 switch(u->entry_state) {
                 case ENTRY_CURSOR: {
-                        free(u->last_cursor);
-                        u->last_cursor = NULL;
+                        free(u->current_cursor);
+                        u->current_cursor = NULL;
 
-                        r = sd_journal_get_cursor(u->journal, &u->last_cursor);
+                        r = sd_journal_get_cursor(u->journal, &u->current_cursor);
                         if (r < 0) {
                                 log_error("Failed to get cursor: %s", strerror(-r));
                                 return r;
                         }
 
                         r = snprintf(buf + pos, size - pos,
-                                     "__CURSOR=%s\n", u->last_cursor);
+                                     "__CURSOR=%s\n", u->current_cursor);
                         if (pos + r > size)
                                 /* not enough space */
                                 return pos;
@@ -282,7 +282,7 @@ static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void
                         break;
 
                 log_debug("Entry %zu (%s) has been uploaded.",
-                          u->entries_sent, u->last_cursor);
+                          u->entries_sent, u->current_cursor);
         }
 
         return filled;
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index 0cab031..7685537 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -30,6 +30,7 @@
 #include "log.h"
 #include "util.h"
 #include "build.h"
+#include "fileio.h"
 #include "journal-upload.h"
 
 static const char* arg_url;
@@ -48,9 +49,12 @@ static int arg_journal_type = 0;
 static const char *arg_machine = NULL;
 static bool arg_merge = false;
 static int arg_follow = -1;
+static const char *arg_save_state = NULL;
 
 #define SERVER_ANSWER_KEEP 2048
 
+#define STATE_FILE "/var/lib/systemd/journal-upload/state"
+
 #define easy_setopt(curl, opt, value, level, cmd)                       \
         {                                                               \
                 code = curl_easy_setopt(curl, opt, value);              \
@@ -83,6 +87,59 @@ static size_t output_callback(char *buf,
         return size * nmemb;
 }
 
+static int update_cursor_state(Uploader *u) {
+        _cleanup_free_ char *temp_path = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        if (!u->state_file || !u->last_cursor)
+                return 0;
+
+        r = fopen_temporary(u->state_file, &f, &temp_path);
+        if (r < 0)
+                goto finish;
+
+        fprintf(f,
+                "# This is private data. Do not parse.\n"
+                "LAST_CURSOR=%s\n",
+                u->last_cursor);
+
+        fflush(f);
+
+        if (ferror(f) || rename(temp_path, u->state_file) < 0) {
+                r = -errno;
+                unlink(u->state_file);
+                unlink(temp_path);
+        }
+
+finish:
+        if (r < 0)
+                log_error("Failed to save state %s: %s", u->state_file, strerror(-r));
+
+        return r;
+}
+
+static int load_cursor_state(Uploader *u) {
+        int r;
+
+        if (!u->state_file)
+                return 0;
+
+        r = parse_env_file(u->state_file, NEWLINE,
+                           "LAST_CURSOR",  &u->last_cursor,
+                           NULL);
+
+        if (r < 0 && r != -ENOENT) {
+                log_error("Failed to read state file %s: %s",
+                          u->state_file, strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
+
+
+
 int start_upload(Uploader *u,
                  size_t (*input_callback)(void *ptr,
                                           size_t size,
@@ -283,7 +340,7 @@ static int open_file_for_upload(Uploader *u, const char *filename) {
         return r;
 }
 
-static int setup_uploader(Uploader *u, const char *url) {
+static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
         int r;
 
         assert(u);
@@ -293,6 +350,7 @@ static int setup_uploader(Uploader *u, const char *url) {
         u->input = -1;
 
         u->url = url;
+        u->state_file = state_file;
 
         r = sd_event_default(&u->events);
         if (r < 0) {
@@ -300,7 +358,7 @@ static int setup_uploader(Uploader *u, const char *url) {
                 return r;
         }
 
-        return 0;
+        return load_cursor_state(u);
 }
 
 static void destroy_uploader(Uploader *u) {
@@ -311,6 +369,7 @@ static void destroy_uploader(Uploader *u) {
         free(u->answer);
 
         free(u->last_cursor);
+        free(u->current_cursor);
 
         u->input_event = sd_event_source_unref(u->input_event);
 
@@ -353,7 +412,12 @@ static int perform_upload(Uploader *u) {
         } else
                 log_debug("Upload finished successfully with code %lu: %s",
                           status, strna(u->answer));
-        return 0;
+
+        free(u->last_cursor);
+        u->last_cursor = u->current_cursor;
+        u->current_cursor = NULL;
+
+        return update_cursor_state(u);
 }
 
 static void help(void) {
@@ -373,6 +437,8 @@ static void help(void) {
                "  --cursor=CURSOR          Start at the specified cursor\n"
                "  --after-cursor=CURSOR    Start after the specified cursor\n"
                "  --[no-]follow            Do [not] wait for input\n"
+               "  --save-state[=FILE]      Save uploaded cursors (default \n"
+               "                           " STATE_FILE ")\n"
                "  -h --help                Show this help and exit\n"
                "  --version                Print version string and exit\n"
                , program_invocation_short_name);
@@ -391,6 +457,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_AFTER_CURSOR,
                 ARG_FOLLOW,
                 ARG_NO_FOLLOW,
+                ARG_SAVE_STATE,
         };
 
         static const struct option options[] = {
@@ -410,6 +477,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR   },
                 { "follow",       no_argument,       NULL, ARG_FOLLOW         },
                 { "no-follow",    no_argument,       NULL, ARG_NO_FOLLOW      },
+                { "save-state",   optional_argument, NULL, ARG_SAVE_STATE     },
                 {}
         };
 
@@ -532,6 +600,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_follow = false;
                         break;
 
+                case ARG_SAVE_STATE:
+                        arg_save_state = optarg ?: STATE_FILE;
+                        break;
+
                 case '?':
                         log_error("Unknown option %s.", argv[optind-1]);
                         return -EINVAL;
@@ -592,7 +664,7 @@ int main(int argc, char **argv) {
         if (r <= 0)
                 goto finish;
 
-        r = setup_uploader(&u, arg_url);
+        r = setup_uploader(&u, arg_url, arg_save_state);
         if (r < 0)
                 goto cleanup;
 
@@ -606,7 +678,8 @@ int main(int argc, char **argv) {
                 if (r < 0)
                         goto finish;
                 r = open_journal_for_upload(&u, j,
-                                            arg_cursor, arg_after_cursor,
+                                            arg_cursor ?: u.last_cursor,
+                                            arg_cursor ? arg_after_cursor : true,
                                             !!arg_follow);
                 if (r < 0)
                         goto finish;
diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h
index f94d9ac..0adb915 100644
--- a/src/journal-remote/journal-upload.h
+++ b/src/journal-remote/journal-upload.h
@@ -43,8 +43,10 @@ typedef struct Uploader {
         size_t field_pos, field_length;
 
         /* general metrics */
+        const char *state_file;
+
         size_t entries_sent;
-        char *last_cursor;
+        char *last_cursor, *current_cursor;
 } Uploader;
 
 #define JOURNAL_UPLOAD_POLL_TIMEOUT (10 * USEC_PER_SEC)

commit eacbb4d33e2bb5c54311544851140efe3dd0f774
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sat Mar 29 00:37:25 2014 -0400

    journal-upload: use journal as the source

diff --git a/Makefile.am b/Makefile.am
index 371468f..7fefa58 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3482,7 +3482,8 @@ rootlibexec_PROGRAMS += \
 
 systemd_journal_upload_SOURCES = \
 	src/journal-remote/journal-upload.h \
-	src/journal-remote/journal-upload.c
+	src/journal-remote/journal-upload.c \
+	src/journal-remote/journal-upload-journal.c
 
 systemd_journal_upload_CFLAGS = \
 	$(AM_CFLAGS) \
@@ -3491,6 +3492,8 @@ systemd_journal_upload_CFLAGS = \
 systemd_journal_upload_LDADD = \
 	libsystemd-core.la \
 	libsystemd-internal.la \
+	libsystemd-journal-internal.la \
+	libsystemd-shared.la \
 	$(LIBCURL_LIBS)
 endif
 
diff --git a/src/journal-remote/journal-remote-write.c b/src/journal-remote/journal-remote-write.c
index 4d142bd..449636c 100644
--- a/src/journal-remote/journal-remote-write.c
+++ b/src/journal-remote/journal-remote-write.c
@@ -81,8 +81,10 @@ int writer_init(Writer *s) {
 }
 
 int writer_close(Writer *s) {
-        if (s->journal)
+        if (s->journal) {
                 journal_file_close(s->journal);
+                log_debug("Journal has been closed.");
+        }
         if (s->mmap)
                 mmap_cache_unref(s->mmap);
         return 0;
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 437e0b0..906759b 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -628,8 +628,6 @@ static int dispatch_http_event(sd_event_source *event,
 
         assert(d);
 
-        log_info("%s", __func__);
-
         r = MHD_run(d->daemon);
         if (r == MHD_NO) {
                 log_error("MHD_run failed!");
diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c
new file mode 100644
index 0000000..a3be1bf
--- /dev/null
+++ b/src/journal-remote/journal-upload-journal.c
@@ -0,0 +1,402 @@
+#include <stdbool.h>
+
+#include <curl/curl.h>
+
+#include "util.h"
+#include "log.h"
+#include "utf8.h"
+#include "journal-upload.h"
+
+/**
+ * Write up to size bytes to buf. Return negative on error, and number of
+ * bytes written otherwise. The last case is a kind of an error too.
+ */
+static ssize_t write_entry(char *buf, size_t size, Uploader *u) {
+        int r;
+        size_t pos = 0;
+
+        assert(size <= SSIZE_MAX);
+
+        while (true) {
+
+                switch(u->entry_state) {
+                case ENTRY_CURSOR: {
+                        free(u->last_cursor);
+                        u->last_cursor = NULL;
+
+                        r = sd_journal_get_cursor(u->journal, &u->last_cursor);
+                        if (r < 0) {
+                                log_error("Failed to get cursor: %s", strerror(-r));
+                                return r;
+                        }
+
+                        r = snprintf(buf + pos, size - pos,
+                                     "__CURSOR=%s\n", u->last_cursor);
+                        if (pos + r > size)
+                                /* not enough space */
+                                return pos;
+
+                        u->entry_state ++;
+
+                        if (pos + r == size) {
+                                /* exactly one character short, but we don't need it */
+                                buf[size - 1] = '\n';
+                                return size;
+                        }
+
+                        pos += r;
+                }       /* fall through */
+
+                case ENTRY_REALTIME: {
+                        usec_t realtime;
+
+                        r = sd_journal_get_realtime_usec(u->journal, &realtime);
+                        if (r < 0) {
+                                log_error("Failed to get realtime timestamp: %s", strerror(-r));
+                                return r;
+                        }
+
+                        r = snprintf(buf + pos, size - pos,
+                                     "__REALTIME_TIMESTAMP="USEC_FMT"\n", realtime);
+                        if (r + pos > size)
+                                /* not enough space */
+                                return pos;
+
+                        u->entry_state ++;
+
+                        if (r + pos == size) {
+                                /* exactly one character short, but we don't need it */
+                                buf[size - 1] = '\n';
+                                return size;
+                        }
+
+                        pos += r;
+                }       /* fall through */
+
+                case ENTRY_MONOTONIC: {
+                        usec_t monotonic;
+                        sd_id128_t boot_id;
+
+                        r = sd_journal_get_monotonic_usec(u->journal, &monotonic, &boot_id);
+                        if (r < 0) {
+                                log_error("Failed to get monotonic timestamp: %s", strerror(-r));
+                                return r;
+                        }
+
+                        r = snprintf(buf + pos, size - pos,
+                                     "__MONOTONIC_TIMESTAMP="USEC_FMT"\n", monotonic);
+                        if (r + pos > size)
+                                /* not enough space */
+                                return pos;
+
+                        u->entry_state ++;
+
+                        if (r + pos == size) {
+                                /* exactly one character short, but we don't need it */
+                                buf[size - 1] = '\n';
+                                return size;
+                        }
+
+                        pos += r;
+                }       /* fall through */
+
+                case ENTRY_BOOT_ID: {
+                        sd_id128_t boot_id;
+                        char sid[33];
+
+                        r = sd_journal_get_monotonic_usec(u->journal, NULL, &boot_id);
+                        if (r < 0) {
+                                log_error("Failed to get monotonic timestamp: %s", strerror(-r));
+                                return r;
+                        }
+
+                        r = snprintf(buf + pos, size - pos,
+                                     "_BOOT_ID=%s\n", sd_id128_to_string(boot_id, sid));
+                        if (r + pos> size)
+                                /* not enough space */
+                                return pos;
+
+                        u->entry_state ++;
+
+                        if (r + pos == size) {
+                                /* exactly one character short, but we don't need it */
+                                buf[size - 1] = '\n';
+                                return size;
+                        }
+
+                        pos += r;
+                }       /* fall through */
+
+                case ENTRY_NEW_FIELD: {
+                        u->field_pos = 0;
+
+                        r = sd_journal_enumerate_data(u->journal,
+                                                      &u->field_data,
+                                                      &u->field_length);
+                        if (r < 0) {
+                                log_error("Failed to move to next field in entry: %s",
+                                          strerror(-r));
+                                return r;
+                        } else if (r == 0) {
+                                u->entry_state = ENTRY_OUTRO;
+                                continue;
+                        }
+
+                        if (!utf8_is_printable_newline(u->field_data,
+                                                       u->field_length, false)) {
+                                u->entry_state = ENTRY_BINARY_FIELD_START;
+                                continue;
+                        }
+
+                        u->entry_state ++;
+                }       /* fall through */
+
+                case ENTRY_TEXT_FIELD:
+                case ENTRY_BINARY_FIELD: {
+                        bool done;
+                        size_t tocopy;
+
+                        done = size - pos > u->field_length - u->field_pos;
+                        if (done)
+                                tocopy = u->field_length - u->field_pos;
+                        else
+                                tocopy = size - pos;
+
+                        memcpy(buf + pos,
+                               (char*) u->field_data + u->field_pos,
+                               tocopy);
+
+                        if (done) {
+                                buf[pos + tocopy] = '\n';
+                                pos += tocopy + 1;
+                                u->entry_state = ENTRY_NEW_FIELD;
+                                continue;
+                        } else {
+                                u->field_pos += tocopy;
+                                return size;
+                        }
+                }
+
+                case ENTRY_BINARY_FIELD_START: {
+                        const char *c;
+                        size_t len;
+
+                        c = memchr(u->field_data, '=', u->field_length);
+                        if (!c || c == u->field_data) {
+                                log_error("Invalid field.");
+                                return -EINVAL;
+                        }
+
+                        len = c - (const char*)u->field_data;
+
+                        /* need space for label + '\n' */
+                        if (size - pos < len + 1)
+                                return pos;
+
+                        memcpy(buf + pos, u->field_data, len);
+                        buf[pos + len] = '\n';
+                        pos += len + 1;
+
+                        u->field_pos = len + 1;
+                        u->entry_state ++;
+                }       /* fall through */
+
+                case ENTRY_BINARY_FIELD_SIZE: {
+                        uint64_t le64;
+
+                        /* need space for uint64_t */
+                        if (size - pos < 8)
+                                return pos;
+
+                        le64 = htole64(u->field_length - u->field_pos);
+                        memcpy(buf + pos, &le64, 8);
+                        pos += 8;
+
+                        u->entry_state ++;
+                        continue;
+                }
+
+                case ENTRY_OUTRO:
+                        /* need space for '\n' */
+                        if (size - pos < 1)
+                                return pos;
+
+                        buf[pos++] = '\n';
+                        u->entry_state ++;
+                        u->entries_sent ++;
+
+                        return pos;
+
+                default:
+                        assert_not_reached("WTF?");
+                }
+        }
+        assert_not_reached("WTF?");
+}
+
+static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
+        Uploader *u = userp;
+        int r;
+        sd_journal *j;
+        size_t filled = 0;
+        ssize_t w;
+
+        assert(u);
+        assert(nmemb <= SSIZE_MAX / size);
+
+        j = u->journal;
+
+        while (j && filled < size * nmemb) {
+                if (u->entry_state == ENTRY_DONE) {
+                        r = sd_journal_next(j);
+                        if (r < 0) {
+                                log_error("Failed to move to next entry in journal: %s",
+                                          strerror(-r));
+                                return CURL_READFUNC_ABORT;
+                        } else if (r == 0) {
+                                if (u->input_event)
+                                        log_debug("No more entries, waiting for journal.");
+                                else {
+                                        log_info("No more entries, closing journal.");
+                                        close_journal_input(u);
+                                }
+
+                                u->uploading = false;
+
+                                break;
+                        }
+
+                        u->entry_state = ENTRY_CURSOR;
+                }
+
+                w = write_entry((char*)buf + filled, size * nmemb - filled, u);
+                if (w < 0)
+                        return CURL_READFUNC_ABORT;
+                filled += w;
+
+                if (filled == 0) {
+                        log_error("Buffer space is too small to write entry.");
+                        return CURL_READFUNC_ABORT;
+                } else if (u->entry_state != ENTRY_DONE)
+                        /* This means that all available space was used up */
+                        break;
+
+                log_debug("Entry %zu (%s) has been uploaded.",
+                          u->entries_sent, u->last_cursor);
+        }
+
+        return filled;
+}
+
+void close_journal_input(Uploader *u) {
+        assert(u);
+
+        if (u->journal) {
+                log_debug("Closing journal input.");
+
+                sd_journal_close(u->journal);
+                u->journal = NULL;
+        }
+        u->timeout = 0;
+}
+
+static int process_journal_input(Uploader *u, int skip) {
+        int r;
+
+        r = sd_journal_next_skip(u->journal, skip);
+        if (r < 0) {
+                log_error("Failed to skip to next entry: %s", strerror(-r));
+                return r;
+        } else if (r < skip)
+                return 0;
+
+        /* have data */
+        u->entry_state = ENTRY_CURSOR;
+        return start_upload(u, journal_input_callback, u);
+}
+
+int check_journal_input(Uploader *u) {
+        if (u->input_event) {
+                int r;
+
+                r = sd_journal_process(u->journal);
+                if (r < 0) {
+                        log_error("Failed to process journal: %s", strerror(-r));
+                        close_journal_input(u);
+                        return r;
+                }
+
+                if (r == SD_JOURNAL_NOP)
+                        return 0;
+        }
+
+        return process_journal_input(u, 1);
+}
+
+static int dispatch_journal_input(sd_event_source *event,
+                                  int fd,
+                                  uint32_t revents,
+                                  void *userp) {
+        Uploader *u = userp;
+
+        assert(u);
+
+        if (u->uploading) {
+                log_warning("dispatch_journal_input called when uploading, ignoring.");
+                return 0;
+        }
+
+        log_debug("Detected journal input, checking for new data.");
+        return check_journal_input(u);
+}
+
+int open_journal_for_upload(Uploader *u,
+                            sd_journal *j,
+                            const char *cursor,
+                            bool after_cursor,
+                            bool follow) {
+        int fd, r, events;
+
+        u->journal = j;
+
+        sd_journal_set_data_threshold(j, 0);
+
+        if (follow) {
+                fd = sd_journal_get_fd(j);
+                if (fd < 0) {
+                        log_error("sd_journal_get_fd failed: %s", strerror(-fd));
+                        return fd;
+                }
+
+                events = sd_journal_get_events(j);
+
+                r = sd_journal_reliable_fd(j);
+                assert(r >= 0);
+                if (r > 0)
+                        u->timeout = -1;
+                else
+                        u->timeout = JOURNAL_UPLOAD_POLL_TIMEOUT;
+
+                r = sd_event_add_io(u->events, &u->input_event,
+                                    fd, events, dispatch_journal_input, u);
+                if (r < 0) {
+                        log_error("Failed to register input event: %s", strerror(-r));
+                        return r;
+                }
+
+                log_debug("Listening for journal events on fd:%d, timeout %d",
+                          fd, u->timeout == (uint64_t) -1 ? -1 : (int) u->timeout);
+        } else
+                log_debug("Not listening for journal events.");
+
+        if (cursor) {
+                r = sd_journal_seek_cursor(j, cursor);
+                if (r < 0) {
+                        log_error("Failed to seek to cursor %s: %s",
+                                  cursor, strerror(-r));
+                        return r;
+                }
+        }
+
+        return process_journal_input(u, 1 + !!after_cursor);
+}
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index 538ba8b..0cab031 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -40,6 +40,17 @@ static const char *arg_key = NULL;
 static const char *arg_cert = NULL;
 static const char *arg_trust = NULL;
 
+static const char *arg_directory = NULL;
+static char **arg_file = NULL;
+static const char *arg_cursor = NULL;
+static bool arg_after_cursor = false;
+static int arg_journal_type = 0;
+static const char *arg_machine = NULL;
+static bool arg_merge = false;
+static int arg_follow = -1;
+
+#define SERVER_ANSWER_KEEP 2048
+
 #define easy_setopt(curl, opt, value, level, cmd)                       \
         {                                                               \
                 code = curl_easy_setopt(curl, opt, value);              \
@@ -51,6 +62,27 @@ static const char *arg_trust = NULL;
                 }                                                       \
         }
 
+static size_t output_callback(char *buf,
+                              size_t size,
+                              size_t nmemb,
+                              void *userp) {
+        Uploader *u = userp;
+
+        assert(u);
+
+        log_debug("The server answers (%zu bytes): %.*s",
+                  size*nmemb, (int)(size*nmemb), buf);
+
+        if (nmemb && !u->answer) {
+                u->answer = strndup(buf, size*nmemb);
+                if (!u->answer)
+                        log_warning("Failed to store server answer (%zu bytes): %s",
+                                    size*nmemb, strerror(ENOMEM));
+        }
+
+        return size * nmemb;
+}
+
 int start_upload(Uploader *u,
                  size_t (*input_callback)(void *ptr,
                                           size_t size,
@@ -97,6 +129,16 @@ int start_upload(Uploader *u,
                 easy_setopt(curl, CURLOPT_POST, 1L,
                             LOG_ERR, return -EXFULL);
 
+                easy_setopt(curl, CURLOPT_ERRORBUFFER, &u->error,
+                            LOG_ERR, return -EXFULL);
+
+                /* set where to write to */
+                easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
+                            LOG_ERR, return -EXFULL);
+
+                easy_setopt(curl, CURLOPT_WRITEDATA, data,
+                            LOG_ERR, return -EXFULL);
+
                 /* set where to read from */
                 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
                             LOG_ERR, return -EXFULL);
@@ -133,6 +175,12 @@ int start_upload(Uploader *u,
                                     LOG_WARNING, );
 
                 u->easy = curl;
+        } else {
+                /* truncate the potential old error message */
+                u->error[0] = '\0';
+
+                free(u->answer);
+                u->answer = 0;
         }
 
         /* upload to this place */
@@ -182,6 +230,7 @@ static void close_fd_input(Uploader *u) {
         if (u->input >= 0)
                 close_nointr(u->input);
         u->input = -1;
+        u->timeout = 0;
 }
 
 static int dispatch_fd_input(sd_event_source *event,
@@ -217,17 +266,20 @@ static int open_file_for_upload(Uploader *u, const char *filename) {
 
         u->input = fd;
 
-        r = sd_event_add_io(u->events, &u->input_event,
-                            fd, EPOLLIN, dispatch_fd_input, u);
-        if (r < 0) {
-                if (r != -EPERM) {
-                        log_error("Failed to register input event: %s", strerror(-r));
-                        return r;
-                }
+        if (arg_follow) {
+                r = sd_event_add_io(u->events, &u->input_event,
+                                    fd, EPOLLIN, dispatch_fd_input, u);
+                if (r < 0) {
+                        if (r != -EPERM || arg_follow > 0) {
+                                log_error("Failed to register input event: %s", strerror(-r));
+                                return r;
+                        }
 
-                /* Normal files should just be consumed without polling. */
-                r = start_upload(u, fd_input_callback, u);
+                        /* Normal files should just be consumed without polling. */
+                        r = start_upload(u, fd_input_callback, u);
+                }
         }
+
         return r;
 }
 
@@ -256,14 +308,54 @@ static void destroy_uploader(Uploader *u) {
 
         curl_easy_cleanup(u->easy);
         curl_slist_free_all(u->header);
+        free(u->answer);
+
+        free(u->last_cursor);
 
         u->input_event = sd_event_source_unref(u->input_event);
 
         close_fd_input(u);
+        close_journal_input(u);
 
         sd_event_unref(u->events);
 }
 
+static int perform_upload(Uploader *u) {
+        CURLcode code;
+        long status;
+
+        assert(u);
+
+        code = curl_easy_perform(u->easy);
+        if (code) {
+                log_error("Upload to %s failed: %.*s",
+                          u->url,
+                          u->error[0] ? (int) sizeof(u->error) : INT_MAX,
+                          u->error[0] ? u->error : curl_easy_strerror(code));
+                return -EIO;
+        }
+
+        code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
+        if (code) {
+                log_error("Failed to retrieve response code: %s",
+                          curl_easy_strerror(code));
+                return -EUCLEAN;
+        }
+
+        if (status >= 300) {
+                log_error("Upload to %s failed with code %lu: %s",
+                          u->url, status, strna(u->answer));
+                return -EIO;
+        } else if (status < 200) {
+                log_error("Upload to %s finished with unexpected code %lu: %s",
+                          u->url, status, strna(u->answer));
+                return -EIO;
+        } else
+                log_debug("Upload finished successfully with code %lu: %s",
+                          status, strna(u->answer));
+        return 0;
+}
+
 static void help(void) {
         printf("%s -u URL {FILE|-}...\n\n"
                "Upload journal events to a remote server.\n\n"
@@ -272,6 +364,15 @@ static void help(void) {
                "  --key=FILENAME           Specify key in PEM format\n"
                "  --cert=FILENAME          Specify certificate in PEM format\n"
                "  --trust=FILENAME         Specify CA certificate in PEM format\n"
+               "     --system              Use the system journal\n"
+               "     --user                Use the user journal for the current user\n"
+               "  -m --merge               Use  all available journals\n"
+               "  -M --machine=CONTAINER   Operate on local container\n"
+               "  -D --directory=PATH      Use journal files from directory\n"
+               "     --file=PATH           Use this journal file\n"
+               "  --cursor=CURSOR          Start at the specified cursor\n"
+               "  --after-cursor=CURSOR    Start after the specified cursor\n"
+               "  --[no-]follow            Do [not] wait for input\n"
                "  -h --help                Show this help and exit\n"
                "  --version                Print version string and exit\n"
                , program_invocation_short_name);
@@ -283,6 +384,13 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_KEY,
                 ARG_CERT,
                 ARG_TRUST,
+                ARG_USER,
+                ARG_SYSTEM,
+                ARG_FILE,
+                ARG_CURSOR,
+                ARG_AFTER_CURSOR,
+                ARG_FOLLOW,
+                ARG_NO_FOLLOW,
         };
 
         static const struct option options[] = {
@@ -292,17 +400,27 @@ static int parse_argv(int argc, char *argv[]) {
                 { "key",          required_argument, NULL, ARG_KEY            },
                 { "cert",         required_argument, NULL, ARG_CERT           },
                 { "trust",        required_argument, NULL, ARG_TRUST          },
+                { "system",       no_argument,       NULL, ARG_SYSTEM         },
+                { "user",         no_argument,       NULL, ARG_USER           },
+                { "merge",        no_argument,       NULL, 'm'                },
+                { "machine",      required_argument, NULL, 'M'                },
+                { "directory",    required_argument, NULL, 'D'                },
+                { "file",         required_argument, NULL, ARG_FILE           },
+                { "cursor",       required_argument, NULL, ARG_CURSOR         },
+                { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR   },
+                { "follow",       no_argument,       NULL, ARG_FOLLOW         },
+                { "no-follow",    no_argument,       NULL, ARG_NO_FOLLOW      },
                 {}
         };
 
-        int c;
+        int c, r;
 
         assert(argc >= 0);
         assert(argv);
 
         opterr = 0;
 
-        while ((c = getopt_long(argc, argv, "hu:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
                 switch(c) {
                 case 'h':
                         help();
@@ -349,6 +467,71 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_trust = optarg;
                         break;
 
+                case ARG_SYSTEM:
+                        arg_journal_type |= SD_JOURNAL_SYSTEM;
+                        break;
+
+                case ARG_USER:
+                        arg_journal_type |= SD_JOURNAL_CURRENT_USER;
+                        break;
+
+                case 'm':
+                        arg_merge = true;
+                        break;
+
+                case 'M':
+                        if (arg_machine) {
+                                log_error("cannot use more than one --machine/-M");
+                                return -EINVAL;
+                        }
+
+                        arg_machine = optarg;
+                        break;
+
+                case 'D':
+                        if (arg_directory) {
+                                log_error("cannot use more than one --directory/-D");
+                                return -EINVAL;
+                        }
+
+                        arg_directory = optarg;
+                        break;
+
+                case ARG_FILE:
+                        r = glob_extend(&arg_file, optarg);
+                        if (r < 0) {
+                                log_error("Failed to add paths: %s", strerror(-r));
+                                return r;
+                        };
+                        break;
+
+                case ARG_CURSOR:
+                        if (arg_cursor) {
+                                log_error("cannot use more than one --cursor/--after-cursor");
+                                return -EINVAL;
+                        }
+
+                        arg_cursor = optarg;
+                        break;
+
+                case ARG_AFTER_CURSOR:
+                        if (arg_cursor) {
+                                log_error("cannot use more than one --cursor/--after-cursor");
+                                return -EINVAL;
+                        }
+
+                        arg_cursor = optarg;
+                        arg_after_cursor = true;
+                        break;
+
+                case ARG_FOLLOW:
+                        arg_follow = true;
+                        break;
+
+                case ARG_NO_FOLLOW:
+                        arg_follow = false;
+                        break;
+
                 case '?':
                         log_error("Unknown option %s.", argv[optind-1]);
                         return -EINVAL;
@@ -371,18 +554,36 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
-        if (optind >= argc) {
-                log_error("Input argument missing.");
+        if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
+                log_error("Input arguments make no sense with journal input.");
                 return -EINVAL;
         }
 
         return 1;
 }
 
+static int open_journal(sd_journal **j) {
+        int r;
+
+        if (arg_directory)
+                r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
+        else if (arg_file)
+                r = sd_journal_open_files(j, (const char**) arg_file, 0);
+        else if (arg_machine)
+                r = sd_journal_open_container(j, arg_machine, 0);
+        else
+                r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
+        if (r < 0)
+                log_error("Failed to open %s: %s",
+                          arg_directory ? arg_directory : arg_file ? "files" : "journal",
+                          strerror(-r));
+        return r;
+}
 
 int main(int argc, char **argv) {
         Uploader u;
         int r;
+        bool use_journal;
 
         log_show_color(true);
         log_parse_environment();
@@ -397,22 +598,39 @@ int main(int argc, char **argv) {
 
         log_debug("%s running as pid "PID_FMT,
                   program_invocation_short_name, getpid());
+
+        use_journal = optind >= argc;
+        if (use_journal) {
+                sd_journal *j;
+                r = open_journal(&j);
+                if (r < 0)
+                        goto finish;
+                r = open_journal_for_upload(&u, j,
+                                            arg_cursor, arg_after_cursor,
+                                            !!arg_follow);
+                if (r < 0)
+                        goto finish;
+        }
+
         sd_notify(false,
                   "READY=1\n"
                   "STATUS=Processing input...");
 
         while (true) {
-                if (u.input < 0) {
+                if (use_journal) {
+                        if (!u.journal)
+                                break;
+
+                        r = check_journal_input(&u);
+                } else if (u.input < 0 && !use_journal) {
                         if (optind >= argc)
                                 break;
 
                         log_debug("Using %s as input.", argv[optind]);
-
                         r = open_file_for_upload(&u, argv[optind++]);
-                        if (r < 0)
-                                goto cleanup;
-
                 }
+                if (r < 0)
+                        goto cleanup;
 
                 r = sd_event_get_state(u.events);
                 if (r < 0)
@@ -421,21 +639,12 @@ int main(int argc, char **argv) {
                         break;
 
                 if (u.uploading) {
-                        CURLcode code;
-
-                        assert(u.easy);
-
-                        code = curl_easy_perform(u.easy);
-                        if (code) {
-                                log_error("Upload to %s failed: %s",
-                                          u.url, curl_easy_strerror(code));
-                                r = -EIO;
+                        r = perform_upload(&u);
+                        if (r < 0)
                                 break;
-                        } else
-                                log_debug("Upload finished successfully.");
                 }
 
-                r = sd_event_run(u.events, u.input >= 0 ? -1 : 0);
+                r = sd_event_run(u.events, u.timeout);
                 if (r < 0) {
                         log_error("Failed to run event loop: %s", strerror(-r));
                         break;
diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h
index 68d85be..f94d9ac 100644
--- a/src/journal-remote/journal-upload.h
+++ b/src/journal-remote/journal-upload.h
@@ -2,24 +2,64 @@
 
 #include <inttypes.h>
 
+#include "sd-journal.h"
 #include "sd-event.h"
 
+typedef enum {
+        ENTRY_CURSOR = 0,           /* Nothing actually written yet. */
+        ENTRY_REALTIME,
+        ENTRY_MONOTONIC,
+        ENTRY_BOOT_ID,
+        ENTRY_NEW_FIELD,            /* In between fields. */
+        ENTRY_TEXT_FIELD,           /* In the middle of a text field. */
+        ENTRY_BINARY_FIELD_START,   /* Writing the name of a binary field. */
+        ENTRY_BINARY_FIELD_SIZE,    /* Writing the size of a binary field. */
+        ENTRY_BINARY_FIELD,         /* In the middle of a binary field. */
+        ENTRY_OUTRO,                /* Writing '\n' */
+        ENTRY_DONE,                 /* Need to move to a new field. */
+} entry_state;
+
 typedef struct Uploader {
         sd_event *events;
 
         const char *url;
         CURL *easy;
         bool uploading;
+        char error[CURL_ERROR_SIZE];
         struct curl_slist *header;
+        char *answer;
+
+        sd_event_source *input_event;
+        uint64_t timeout;
 
+        /* fd stuff */
         int input;
 
-        sd_event_source *input_event;
+        /* journal stuff */
+        sd_journal* journal;
+
+        entry_state entry_state;
+        const void *field_data;
+        size_t field_pos, field_length;
+
+        /* general metrics */
+        size_t entries_sent;
+        char *last_cursor;
 } Uploader;
 
+#define JOURNAL_UPLOAD_POLL_TIMEOUT (10 * USEC_PER_SEC)
+
 int start_upload(Uploader *u,
                  size_t (*input_callback)(void *ptr,
                                           size_t size,
                                           size_t nmemb,
                                           void *userdata),
                  void *data);
+
+int open_journal_for_upload(Uploader *u,
+                            sd_journal *j,
+                            const char *cursor,
+                            bool after_cursor,
+                            bool follow);
+void close_journal_input(Uploader *u);
+int check_journal_input(Uploader *u);
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
index 86453e6..92e8286 100644
--- a/src/journal/journalctl.c
+++ b/src/journal/journalctl.c
@@ -167,8 +167,8 @@ static int help(void) {
         printf("%s [OPTIONS...] [MATCHES...]\n\n"
                "Query the journal.\n\n"
                "Flags:\n"
-               "     --system              Show only the system journal\n"
-               "     --user                Show only the user journal for the current user\n"
+               "     --system              Show the system journal\n"
+               "     --user                Show the user journal for the current user\n"
                "  -M --machine=CONTAINER   Operate on local container\n"
                "     --since=DATE          Start showing entries on or newer than the specified date\n"
                "     --until=DATE          Stop showing entries on or older than the specified date\n"
@@ -1752,7 +1752,7 @@ int main(int argc, char *argv[]) {
         }
 
         if (arg_cursor || arg_after_cursor) {
-                r = sd_journal_seek_cursor(j, arg_cursor ? arg_cursor : arg_after_cursor);
+                r = sd_journal_seek_cursor(j, arg_cursor ?: arg_after_cursor);
                 if (r < 0) {
                         log_error("Failed to seek to cursor: %s", strerror(-r));
                         return EXIT_FAILURE;

commit 7449bc1f34c206e3ff8e274cd74e2db950d492a1
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sat Mar 29 00:44:48 2014 -0400

    journal-upload: HTTPS support

diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 09144ea..437e0b0 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -1201,7 +1201,7 @@ static int parse_argv(int argc, char *argv[]) {
                 }
 
         if (arg_listen_https && !(key_pem && cert_pem)) {
-                log_error("Options --key and --cert must be used when https sources are specified");
+                log_error("Options --key and --cert must be used when using HTTPS.");
                 return -EINVAL;
         }
 
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
index e82f440..538ba8b 100644
--- a/src/journal-remote/journal-upload.c
+++ b/src/journal-remote/journal-upload.c
@@ -36,6 +36,10 @@ static const char* arg_url;
 
 static void close_fd_input(Uploader *u);
 
+static const char *arg_key = NULL;
+static const char *arg_cert = NULL;
+static const char *arg_trust = NULL;
+
 #define easy_setopt(curl, opt, value, level, cmd)                       \
         {                                                               \
                 code = curl_easy_setopt(curl, opt, value);              \
@@ -111,6 +115,23 @@ int start_upload(Uploader *u,
                             "systemd-journal-upload " PACKAGE_STRING,
                             LOG_WARNING, );
 
+                if (arg_key) {
+                        assert(arg_cert);
+
+                        easy_setopt(curl, CURLOPT_SSLKEY, arg_key,
+                                    LOG_ERR, return -EXFULL);
+                        easy_setopt(curl, CURLOPT_SSLCERT, arg_cert,
+                                    LOG_ERR, return -EXFULL);
+                }
+
+                if (arg_trust)
+                        easy_setopt(curl, CURLOPT_CAINFO, arg_trust,
+                                    LOG_ERR, return -EXFULL);
+
+                if (arg_key || arg_trust)
+                        easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
+                                    LOG_WARNING, );
+
                 u->easy = curl;
         }
 
@@ -248,6 +269,9 @@ static void help(void) {
                "Upload journal events to a remote server.\n\n"
                "Options:\n"
                "  --url=URL                Upload to this address\n"
+               "  --key=FILENAME           Specify key in PEM format\n"
+               "  --cert=FILENAME          Specify certificate in PEM format\n"
+               "  --trust=FILENAME         Specify CA certificate in PEM format\n"
                "  -h --help                Show this help and exit\n"
                "  --version                Print version string and exit\n"
                , program_invocation_short_name);
@@ -256,12 +280,18 @@ static void help(void) {
 static int parse_argv(int argc, char *argv[]) {
         enum {
                 ARG_VERSION = 0x100,
+                ARG_KEY,
+                ARG_CERT,
+                ARG_TRUST,
         };
 
         static const struct option options[] = {
                 { "help",         no_argument,       NULL, 'h'                },
                 { "version",      no_argument,       NULL, ARG_VERSION        },
                 { "url",          required_argument, NULL, 'u'                },
+                { "key",          required_argument, NULL, ARG_KEY            },
+                { "cert",         required_argument, NULL, ARG_CERT           },
+                { "trust",        required_argument, NULL, ARG_TRUST          },
                 {}
         };
 
@@ -292,6 +322,33 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_url = optarg;
                         break;
 
+                case ARG_KEY:
+                        if (arg_key) {
+                                log_error("cannot use more than one --key");
+                                return -EINVAL;
+                        }
+
+                        arg_key = optarg;
+                        break;
+
+                case ARG_CERT:
+                        if (arg_cert) {
+                                log_error("cannot use more than one --cert");
+                                return -EINVAL;
+                        }
+
+                        arg_cert = optarg;
+                        break;
+
+                case ARG_TRUST:
+                        if (arg_trust) {
+                                log_error("cannot use more than one --trust");
+                                return -EINVAL;
+                        }
+
+                        arg_trust = optarg;
+                        break;
+
                 case '?':
                         log_error("Unknown option %s.", argv[optind-1]);
                         return -EINVAL;
@@ -309,6 +366,11 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
+        if (!!arg_key != !!arg_cert) {
+                log_error("Options --key and --cert must be used together.");
+                return -EINVAL;
+        }
+
         if (optind >= argc) {
                 log_error("Input argument missing.");
                 return -EINVAL;

commit 3d090cc6f34e5970765dd1e7ee5e648a056d180d
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Mon Mar 17 22:54:28 2014 -0400

    journal-upload: a tool to push messages to systemd-journal-remote

diff --git a/.gitignore b/.gitignore
index 9df4d58..eab1f4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,14 +77,15 @@
 /systemd-hostnamed
 /systemd-inhibit
 /systemd-initctl
-/systemd-journald
 /systemd-journal-gatewayd
 /systemd-journal-remote
+/systemd-journal-upload
+/systemd-journald
 /systemd-kmsg-syslogd
 /systemd-localed
 /systemd-logind
-/systemd-machined
 /systemd-machine-id-setup
+/systemd-machined
 /systemd-modules-load
 /systemd-multi-seat-x
 /systemd-networkd
diff --git a/Makefile.am b/Makefile.am
index 4a698c8..371468f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3476,6 +3476,24 @@ systemd_journal_remote_LDADD += \
 endif
 endif
 
+if HAVE_LIBCURL
+rootlibexec_PROGRAMS += \
+	systemd-journal-upload
+
+systemd_journal_upload_SOURCES = \
+	src/journal-remote/journal-upload.h \
+	src/journal-remote/journal-upload.c
+
+systemd_journal_upload_CFLAGS = \
+	$(AM_CFLAGS) \
+	$(LIBCURL_CFLAGS)
+
+systemd_journal_upload_LDADD = \
+	libsystemd-core.la \
+	libsystemd-internal.la \
+	$(LIBCURL_LIBS)
+endif
+
 # using _CFLAGS = in the conditional below would suppress AM_CFLAGS
 journalctl_CFLAGS = \
 	$(AM_CFLAGS)
diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c
new file mode 100644
index 0000000..e82f440
--- /dev/null
+++ b/src/journal-remote/journal-upload.c
@@ -0,0 +1,389 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <curl/curl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <getopt.h>
+
+#include "sd-daemon.h"
+
+#include "log.h"
+#include "util.h"
+#include "build.h"
+#include "journal-upload.h"
+
+static const char* arg_url;
+
+static void close_fd_input(Uploader *u);
+
+#define easy_setopt(curl, opt, value, level, cmd)                       \
+        {                                                               \
+                code = curl_easy_setopt(curl, opt, value);              \
+                if (code) {                                             \
+                        log_full(level,                                 \
+                                 "curl_easy_setopt " #opt " failed: %s", \
+                                  curl_easy_strerror(code));            \
+                        cmd;                                            \
+                }                                                       \
+        }
+
+int start_upload(Uploader *u,
+                 size_t (*input_callback)(void *ptr,
+                                          size_t size,
+                                          size_t nmemb,
+                                          void *userdata),
+                 void *data) {
+        CURLcode code;
+
+        assert(u);
+        assert(input_callback);
+
+        if (!u->header) {
+                struct curl_slist *h;
+
+                h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
+                if (!h)
+                        return log_oom();
+
+                h = curl_slist_append(h, "Transfer-Encoding: chunked");
+                if (!h) {
+                        curl_slist_free_all(h);
+                        return log_oom();
+                }
+
+                h = curl_slist_append(h, "Accept: text/plain");
+                if (!h) {
+                        curl_slist_free_all(h);
+                        return log_oom();
+                }
+
+                u->header = h;
+        }
+
+        if (!u->easy) {
+                CURL *curl;
+
+                curl = curl_easy_init();
+                if (!curl) {
+                        log_error("Call to curl_easy_init failed.");
+                        return -ENOSR;
+                }
+
+                /* tell it to POST to the URL */
+                easy_setopt(curl, CURLOPT_POST, 1L,
+                            LOG_ERR, return -EXFULL);
+
+                /* set where to read from */
+                easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
+                            LOG_ERR, return -EXFULL);
+
+                easy_setopt(curl, CURLOPT_READDATA, data,
+                            LOG_ERR, return -EXFULL);
+
+                /* use our special own mime type and chunked transfer */
+                easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
+                            LOG_ERR, return -EXFULL);
+
+                /* enable verbose for easier tracing */
+                easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
+
+                easy_setopt(curl, CURLOPT_USERAGENT,
+                            "systemd-journal-upload " PACKAGE_STRING,
+                            LOG_WARNING, );
+
+                u->easy = curl;
+        }
+
+        /* upload to this place */
+        code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
+        if (code) {
+                log_error("curl_easy_setopt CURLOPT_URL failed: %s",
+                          curl_easy_strerror(code));
+                return -EXFULL;
+        }
+
+        u->uploading = true;
+
+        return 0;
+}
+
+static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
+        Uploader *u = userp;
+
+        ssize_t r;
+
+        assert(u);
+        assert(nmemb <= SSIZE_MAX / size);
+
+        if (u->input < 0)
+                return 0;
+
+        r = read(u->input, buf, size * nmemb);
+        log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
+
+        if (r > 0)
+                return r;
+
+        u->uploading = false;
+        if (r == 0) {
+                log_debug("Reached EOF");
+                close_fd_input(u);
+                return 0;
+        } else {
+                log_error("Aborting transfer after read error on input: %m.");
+                return CURL_READFUNC_ABORT;
+        }
+}
+
+static void close_fd_input(Uploader *u) {
+        assert(u);
+
+        if (u->input >= 0)
+                close_nointr(u->input);
+        u->input = -1;
+}
+
+static int dispatch_fd_input(sd_event_source *event,
+                             int fd,
+                             uint32_t revents,
+                             void *userp) {
+        Uploader *u = userp;
+
+        assert(u);
+        assert(revents & EPOLLIN);
+        assert(fd >= 0);
+
+        if (u->uploading) {
+                log_warning("dispatch_fd_input called when uploading, ignoring.");
+                return 0;
+        }
+
+        return start_upload(u, fd_input_callback, u);
+}
+
+static int open_file_for_upload(Uploader *u, const char *filename) {
+        int fd, r;
+
+        if (streq(filename, "-"))
+                fd = STDIN_FILENO;
+        else {
+                fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                if (fd < 0) {
+                        log_error("Failed to open %s: %m", filename);
+                        return -errno;
+                }
+        }
+
+        u->input = fd;
+
+        r = sd_event_add_io(u->events, &u->input_event,
+                            fd, EPOLLIN, dispatch_fd_input, u);
+        if (r < 0) {
+                if (r != -EPERM) {
+                        log_error("Failed to register input event: %s", strerror(-r));
+                        return r;
+                }
+
+                /* Normal files should just be consumed without polling. */
+                r = start_upload(u, fd_input_callback, u);
+        }
+        return r;
+}
+
+static int setup_uploader(Uploader *u, const char *url) {
+        int r;
+
+        assert(u);
+        assert(url);
+
+        memzero(u, sizeof(Uploader));
+        u->input = -1;
+
+        u->url = url;
+
+        r = sd_event_default(&u->events);
+        if (r < 0) {
+                log_error("sd_event_default failed: %s", strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
+
+static void destroy_uploader(Uploader *u) {
+        assert(u);
+
+        curl_easy_cleanup(u->easy);
+        curl_slist_free_all(u->header);
+
+        u->input_event = sd_event_source_unref(u->input_event);
+
+        close_fd_input(u);
+
+        sd_event_unref(u->events);
+}
+
+static void help(void) {
+        printf("%s -u URL {FILE|-}...\n\n"
+               "Upload journal events to a remote server.\n\n"
+               "Options:\n"
+               "  --url=URL                Upload to this address\n"
+               "  -h --help                Show this help and exit\n"
+               "  --version                Print version string and exit\n"
+               , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+        };
+
+        static const struct option options[] = {
+                { "help",         no_argument,       NULL, 'h'                },
+                { "version",      no_argument,       NULL, ARG_VERSION        },
+                { "url",          required_argument, NULL, 'u'                },
+                {}
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        opterr = 0;
+
+        while ((c = getopt_long(argc, argv, "hu:", options, NULL)) >= 0)
+                switch(c) {
+                case 'h':
+                        help();
+                        return 0 /* done */;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0 /* done */;
+
+                case 'u':
+                        if (arg_url) {
+                                log_error("cannot use more than one --url");
+                                return -EINVAL;
+                        }
+
+                        arg_url = optarg;
+                        break;
+
+                case '?':
+                        log_error("Unknown option %s.", argv[optind-1]);
+                        return -EINVAL;
+
+                case ':':
+                        log_error("Missing argument to %s.", argv[optind-1]);
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached("Unhandled option code.");
+                }
+
+        if (!arg_url) {
+                log_error("Required --url/-u option missing.");
+                return -EINVAL;
+        }
+
+        if (optind >= argc) {
+                log_error("Input argument missing.");
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+
+int main(int argc, char **argv) {
+        Uploader u;
+        int r;
+
+        log_show_color(true);
+        log_parse_environment();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        r = setup_uploader(&u, arg_url);
+        if (r < 0)
+                goto cleanup;
+
+        log_debug("%s running as pid "PID_FMT,
+                  program_invocation_short_name, getpid());
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing input...");
+
+        while (true) {
+                if (u.input < 0) {
+                        if (optind >= argc)
+                                break;
+
+                        log_debug("Using %s as input.", argv[optind]);
+
+                        r = open_file_for_upload(&u, argv[optind++]);
+                        if (r < 0)
+                                goto cleanup;
+
+                }
+
+                r = sd_event_get_state(u.events);
+                if (r < 0)
+                        break;
+                if (r == SD_EVENT_FINISHED)
+                        break;
+
+                if (u.uploading) {
+                        CURLcode code;
+
+                        assert(u.easy);
+
+                        code = curl_easy_perform(u.easy);
+                        if (code) {
+                                log_error("Upload to %s failed: %s",
+                                          u.url, curl_easy_strerror(code));
+                                r = -EIO;
+                                break;
+                        } else
+                                log_debug("Upload finished successfully.");
+                }
+
+                r = sd_event_run(u.events, u.input >= 0 ? -1 : 0);
+                if (r < 0) {
+                        log_error("Failed to run event loop: %s", strerror(-r));
+                        break;
+                }
+        }
+
+cleanup:
+        sd_notify(false, "STATUS=Shutting down...");
+        destroy_uploader(&u);
+
+finish:
+        return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h
new file mode 100644
index 0000000..68d85be
--- /dev/null
+++ b/src/journal-remote/journal-upload.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <inttypes.h>
+
+#include "sd-event.h"
+
+typedef struct Uploader {
+        sd_event *events;
+
+        const char *url;
+        CURL *easy;
+        bool uploading;
+        struct curl_slist *header;
+
+        int input;
+
+        sd_event_source *input_event;
+} Uploader;
+
+int start_upload(Uploader *u,
+                 size_t (*input_callback)(void *ptr,
+                                          size_t size,
+                                          size_t nmemb,
+                                          void *userdata),
+                 void *data);

commit 851d4e2a67efb2c8777df151b697391ff1a76af0
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Mar 30 22:35:37 2014 -0400

    journal-remote: reject fields above maximum size
    
    Also fix an infinite loop on E2BIG.
    
    Remember what range we already scanned for '\n', to avoid
    quadratic behaviour on long "text" fields.

diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c
index dbdf02a..fe21bd3 100644
--- a/src/journal-remote/journal-remote-parse.c
+++ b/src/journal-remote/journal-remote-parse.c
@@ -49,43 +49,43 @@ static int get_line(RemoteSource *source, char **line, size_t *size) {
         assert(source->filled <= source->size);
         assert(source->buf == NULL || source->size > 0);
 
-        if (source->buf)
-                c = memchr(source->buf, '\n', source->filled);
-
-        if (c != NULL)
-                goto docopy;
-
- resize:
-        if (source->fd < 0)
-                /* we have to wait for some data to come to us */
-                return -EWOULDBLOCK;
+        while (true) {
+                if (source->buf)
+                        c = memchr(source->buf + source->scanned, '\n',
+                                   source->filled - source->scanned);
+                if (c != NULL)
+                        break;
+
+                source->scanned = source->filled;
+                if (source->scanned >= DATA_SIZE_MAX) {
+                        log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX);
+                        return -E2BIG;
+                }
 
-        if (source->size - source->filled < LINE_CHUNK) {
-                // XXX: add check for maximum line length
+                if (source->fd < 0)
+                        /* we have to wait for some data to come to us */
+                        return -EWOULDBLOCK;
 
-                if (!GREEDY_REALLOC(source->buf, source->size,
-                                    source->filled + LINE_CHUNK))
-                        return log_oom();
-        }
-        assert(source->size - source->filled >= LINE_CHUNK);
+                if (source->size - source->filled < LINE_CHUNK &&
+                    !GREEDY_REALLOC(source->buf, source->size,
+                                    MAX(source->filled + LINE_CHUNK, DATA_SIZE_MAX)))
+                                return log_oom();
 
-        n = read(source->fd, source->buf + source->filled,
-                 source->size - source->filled);
-        if (n < 0) {
-                if (errno != EAGAIN && errno != EWOULDBLOCK)
-                        log_error("read(%d, ..., %zd): %m", source->fd,
-                                  source->size - source->filled);
-                return -errno;
-        } else if (n == 0)
-                return 0;
+                assert(source->size - source->filled >= LINE_CHUNK);
 
-        c = memchr(source->buf + source->filled, '\n', n);
-        source->filled += n;
+                n = read(source->fd, source->buf + source->filled,
+                         MAX(source->size, DATA_SIZE_MAX) - source->filled);
+                if (n < 0) {
+                        if (errno != EAGAIN && errno != EWOULDBLOCK)
+                                log_error("read(%d, ..., %zd): %m", source->fd,
+                                          source->size - source->filled);
+                        return -errno;
+                } else if (n == 0)
+                        return 0;
 
-        if (c == NULL)
-                goto resize;
+                source->filled += n;
+        }
 
- docopy:
         *line = source->buf;
         *size = c + 1 - source->buf;
 
@@ -102,6 +102,7 @@ static int get_line(RemoteSource *source, char **line, size_t *size) {
         source->buf = newbuf;
         source->size = newsize;
         source->filled = remain;
+        source->scanned = 0;
 
         return 1;
 }
@@ -111,8 +112,12 @@ int push_data(RemoteSource *source, const char *data, size_t size) {
         assert(source->state != STATE_EOF);
 
         if (!GREEDY_REALLOC(source->buf, source->size,
-                            source->filled + size))
-                return log_oom();
+                            source->filled + size)) {
+                log_error("Failed to store received data of size %zu "
+                          "(in addition to existing %zu bytes with %zu filled): %s",
+                          size, source->size, source->filled, strerror(ENOMEM));
+                return -ENOMEM;
+        }
 
         memcpy(source->buf + source->filled, data, size);
         source->filled += size;
@@ -131,6 +136,7 @@ static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
                source->state == STATE_DATA_FINISH);
         assert(size <= DATA_SIZE_MAX);
         assert(source->filled <= source->size);
+        assert(source->scanned <= source->filled);
         assert(source->buf != NULL || source->size == 0);
         assert(source->buf == NULL || source->size > 0);
         assert(data);
@@ -171,6 +177,7 @@ static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
         source->buf = newbuf;
         source->size = newsize;
         source->filled = remain;
+        source->scanned = 0;
 
         return 1;
 }
diff --git a/src/journal-remote/journal-remote-parse.h b/src/journal-remote/journal-remote-parse.h
index c1506d1..2b6c24e 100644
--- a/src/journal-remote/journal-remote-parse.h
+++ b/src/journal-remote/journal-remote-parse.h
@@ -38,6 +38,7 @@ typedef struct RemoteSource {
 
         char *buf;
         size_t size;
+        size_t scanned;
         size_t filled;
         size_t data_size;
 
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index de327cc..09144ea 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -412,25 +412,28 @@ static int process_http_upload(
                 log_info("Received %zu bytes", *upload_data_size);
 
                 r = push_data(source, upload_data, *upload_data_size);
-                if (r < 0) {
-                        log_error("Failed to store received data of size %zu: %s",
-                                  *upload_data_size, strerror(-r));
+                if (r < 0)
                         return mhd_respond_oom(connection);
-                }
+
                 *upload_data_size = 0;
         } else
                 finished = true;
 
         while (true) {
                 r = process_source(source, &server->writer, arg_compress, arg_seal);
-                if (r == -E2BIG)
-                        log_warning("Entry too big, skipped");
-                else if (r == -EAGAIN || r == -EWOULDBLOCK)
+                if (r == -EAGAIN || r == -EWOULDBLOCK)
                         break;
                 else if (r < 0) {
                         log_warning("Failed to process data for connection %p", connection);
-                        return mhd_respondf(connection, MHD_HTTP_UNPROCESSABLE_ENTITY,
-                                            "Processing failed: %s", strerror(-r));
+                        if (r == -E2BIG)
+                                return mhd_respondf(connection,
+                                                    MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
+                                                    "Entry is too large, maximum is %u bytes.\n",
+                                                    DATA_SIZE_MAX);
+                        else
+                                return mhd_respondf(connection,
+                                                    MHD_HTTP_UNPROCESSABLE_ENTITY,
+                                                    "Processing failed: %s.", strerror(-r));
                 }
         }
 
diff --git a/src/journal/journald-native.h b/src/journal/journald-native.h
index bf02fee..97808e7 100644
--- a/src/journal/journald-native.h
+++ b/src/journal/journald-native.h
@@ -25,8 +25,8 @@
 
 /* Make sure not to make this smaller than the maximum coredump
  * size. See COREDUMP_MAX in coredump.c */
-#define ENTRY_SIZE_MAX (1024*1024*768)
-#define DATA_SIZE_MAX (1024*1024*768)
+#define ENTRY_SIZE_MAX (1024*1024*768u)
+#define DATA_SIZE_MAX (1024*1024*768u)
 
 bool valid_user_field(const char *p, size_t l, bool allow_protected);
 

commit 5c879495eab608bf9b6e7bec1020d916a0503b6e
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Mar 23 12:36:05 2014 -0400

    journal-remote: small fixes

diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 9db4692..de327cc 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -958,7 +958,7 @@ static int dispatch_raw_connection_event(sd_event_source *event,
  **********************************************************************
  **********************************************************************/
 
-static int help(void) {
+static void help(void) {
         printf("%s [OPTIONS...] {FILE|-}...\n\n"
                "Write external journal events to a journal file.\n\n"
                "Options:\n"
@@ -980,8 +980,6 @@ static int help(void) {
                "\n"
                "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
                , program_invocation_short_name);
-
-        return 0;
 }
 
 static int parse_argv(int argc, char *argv[]) {
@@ -1239,7 +1237,6 @@ int main(int argc, char **argv) {
         RemoteServer s = {};
         int r, r2;
 
-        log_set_max_level(LOG_DEBUG);
         log_show_color(true);
         log_parse_environment();
 

commit 36ef43edcf1c9640b3fda017b381c4b1854641f4
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Mon Mar 17 22:52:53 2014 -0400

    build-sys: add check for libcurl

diff --git a/configure.ac b/configure.ac
index e16d50d..6e972e3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -788,6 +788,18 @@ fi
 AM_CONDITIONAL(HAVE_GNUTLS, [test "$have_gnutls" = "yes"])
 
 # ------------------------------------------------------------------------------
+have_libcurl=no
+AC_ARG_ENABLE(libcurl, AS_HELP_STRING([--disable-libcurl], [disable libcurl support]))
+if test "x$enable_libcurl" != "xno"; then
+        PKG_CHECK_MODULES(LIBCURL, [libcurl],
+                [AC_DEFINE(HAVE_LIBCURL, 1, [Define if libcurl is available]) have_libcurl=yes], have_libcurl=no)
+        if test "x$have_libcurl" = xno -a "x$enable_libcurl" = xyes; then
+                AC_MSG_ERROR([*** libcurl support requested but libraries not found])
+        fi
+fi
+AM_CONDITIONAL(HAVE_LIBCURL, [test "$have_libcurl" = "yes"])
+
+# ------------------------------------------------------------------------------
 have_binfmt=no
 AC_ARG_ENABLE(binfmt, AS_HELP_STRING([--disable-binfmt], [disable binfmt tool]))
 if test "x$enable_binfmt" != "xno"; then
@@ -1293,6 +1305,7 @@ AC_MSG_RESULT([
         MICROHTTPD:              ${have_microhttpd}
         CHKCONFIG:               ${have_chkconfig}
         GNUTLS:                  ${have_gnutls}
+        libcurl:                 ${have_libcurl}
         ELFUTILS:                ${have_elfutils}
         binfmt:                  ${have_binfmt}
         vconsole:                ${have_vconsole}

commit 1e4e7b71e1938daa9b2b9718a9f54c69017a9ef5
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Tue Mar 25 22:30:24 2014 -0400

    Move network-related journal programs to src/journal-remote/
    
    Directory src/journal has become one of the largest directories,
    and since systemd-journal-gatewayd, systemd-journal-remote, and
    forthcoming systemd-journal-upload are all closely related, create
    a separate directory for them.

diff --git a/Makefile.am b/Makefile.am
index ff64cbf..4a698c8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3449,19 +3449,19 @@ rootlibexec_PROGRAMS += \
 	systemd-journal-remote
 
 systemd_journal_remote_SOURCES = \
-	src/journal/journal-remote-parse.h \
-	src/journal/journal-remote-parse.c \
-	src/journal/journal-remote-write.h \
-	src/journal/journal-remote-write.c \
-	src/journal/journal-remote.c
+	src/journal-remote/journal-remote-parse.h \
+	src/journal-remote/journal-remote-parse.c \
+	src/journal-remote/journal-remote-write.h \
+	src/journal-remote/journal-remote-write.c \
+	src/journal-remote/journal-remote.c
 
 systemd_journal_remote_LDADD = \
 	libsystemd-internal.la \
 	libsystemd-journal-core.la
 
 systemd_journal_remote_SOURCES += \
-	src/journal/microhttpd-util.h \
-	src/journal/microhttpd-util.c
+	src/journal-remote/microhttpd-util.h \
+	src/journal-remote/microhttpd-util.c
 
 systemd_journal_remote_CFLAGS = \
 	$(AM_CFLAGS) \
@@ -3803,9 +3803,9 @@ rootlibexec_PROGRAMS += \
 	systemd-journal-gatewayd
 
 systemd_journal_gatewayd_SOURCES = \
-	src/journal/journal-gatewayd.c \
-	src/journal/microhttpd-util.h \
-	src/journal/microhttpd-util.c
+	src/journal-remote/journal-gatewayd.c \
+	src/journal-remote/microhttpd-util.h \
+	src/journal-remote/microhttpd-util.c
 
 systemd_journal_gatewayd_LDADD = \
 	libsystemd-logs.la \
@@ -3834,7 +3834,7 @@ nodist_systemunit_DATA += \
 	units/systemd-journal-gatewayd.service
 
 dist_gatewayddocumentroot_DATA = \
-	src/journal/browse.html
+	src/journal-remote/browse.html
 
 endif
 
diff --git a/src/journal-remote/Makefile b/src/journal-remote/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/journal-remote/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/journal-remote/browse.html b/src/journal-remote/browse.html
new file mode 100644
index 0000000..3594f70
--- /dev/null
+++ b/src/journal-remote/browse.html
@@ -0,0 +1,544 @@
+<!DOCTYPE html>
+<html>
+<head>
+        <title>Journal</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+        <style type="text/css">
+                div#divlogs, div#diventry {
+                        font-family: monospace;
+                        font-size: 7pt;
+                        background-color: #ffffff;
+                        padding: 1em;
+                        margin: 2em 0em;
+                        border-radius: 10px 10px 10px 10px;
+                        border: 1px solid threedshadow;
+                        white-space: nowrap;
+                        overflow-x: scroll;
+                }
+                div#diventry {
+                        display: none;
+                }
+                div#divlogs {
+                        display: block;
+                }
+                body {
+                        background-color: #ededed;
+                        color: #313739;
+                        font: message-box;
+                        margin: 3em;
+                }
+                td.timestamp {
+                        text-align: right;
+                        border-right: 1px dotted lightgrey;
+                        padding-right: 5px;
+                }
+                td.process {
+                        border-right: 1px dotted lightgrey;
+                        padding-left: 5px;
+                        padding-right: 5px;
+                }
+                td.message {
+                        padding-left: 5px;
+                }
+                td.message > a:link, td.message > a:visited {
+                        text-decoration: none;
+                        color: #313739;
+                }
+                td.message-error {
+                        padding-left: 5px;
+                        color: red;
+                        font-weight: bold;
+                }
+                td.message-error > a:link, td.message-error > a:visited {
+                        text-decoration: none;
+                        color: red;
+                }
+                td.message-highlight {
+                        padding-left: 5px;
+                        font-weight: bold;
+                }
+                td.message-highlight > a:link, td.message-highlight > a:visited {
+                        text-decoration: none;
+                        color: #313739;
+                }
+                td > a:hover, td > a:active {
+                        text-decoration: underline;
+                        color: #c13739;
+                }
+                table#tablelogs, table#tableentry {
+                        border-collapse: collapse;
+                }
+                td.field {
+                        text-align: right;
+                        border-right: 1px dotted lightgrey;
+                        padding-right: 5px;
+                }
+                td.data {
+                        padding-left: 5px;
+                }
+                div#keynav {
+                        text-align: center;
+                        font-size: 7pt;
+                        color: #818789;
+                        padding-top: 2em;
+                }
+                span.key {
+                        font-weight: bold;
+                        color: #313739;
+                }
+                div#buttonnav {
+                        text-align: center;
+                }
+                button {
+                        font-size: 18pt;
+                        font-weight: bold;
+                        width: 2em;
+                        height: 2em;
+                }
+                div#filternav {
+                        text-align: center;
+                }
+                select {
+                        width: 50em;
+                }
+        </style>
+</head>
+
+<body>
+        <!-- TODO:
+                - live display
+                - show red lines for reboots -->
+
+        <h1 id="title"></h1>
+
+        <div id="os"></div>
+        <div id="virtualization"></div>
+        <div id="cutoff"></div>
+        <div id="machine"></div>
+        <div id="usage"></div>
+        <div id="showing"></div>
+
+        <div id="filternav">
+                <select id="filter" onchange="onFilterChange(this);" onfocus="onFilterFocus(this);">
+                        <option>No filter</option>
+                </select>
+                    
+                <input id="boot" type="checkbox" onchange="onBootChange(this);">Only current boot</input>
+        </div>
+
+        <div id="divlogs"><table id="tablelogs"></table></div>
+        <a name="entry"></a>
+        <div id="diventry"><table id="tableentry"></table></div>
+
+        <div id="buttonnav">
+                <button id="head" onclick="entriesLoadHead();" title="First Page">⇤</button>
+                <button id="previous" type="button" onclick="entriesLoadPrevious();" title="Previous Page"/>←</button>
+                <button id="next" type="button" onclick="entriesLoadNext();" title="Next Page"/>→</button>
+                <button id="tail" type="button" onclick="entriesLoadTail();" title="Last Page"/>⇥</button>
+                    
+                <button id="more" type="button" onclick="entriesMore();" title="More Entries"/>+</button>
+                <button id="less" type="button" onclick="entriesLess();" title="Fewer Entries"/>-</button>
+        </div>
+
+        <div id="keynav">
+                <span class="key">g</span>: First Page     
+                <span class="key">←, k, BACKSPACE</span>: Previous Page     
+                <span class="key">→, j, SPACE</span>: Next Page     
+                <span class="key">G</span>: Last Page     
+                <span class="key">+</span>: More entries     
+                <span class="key">-</span>: Fewer entries
+        </div>
+
+        <script type="text/javascript">
+                var first_cursor = null;
+                var last_cursor = null;
+
+                function getNEntries() {
+                        var n;
+                        n = localStorage["n_entries"];
+                        if (n == null)
+                                return 50;
+                        n = parseInt(n);
+                        if (n < 10)
+                                return 10;
+                        if (n > 1000)
+                                return 1000;
+                        return n;
+                }
+
+                function showNEntries(n) {
+                        var showing = document.getElementById("showing");
+                        showing.innerHTML = "Showing <b>" + n.toString() + "</b> entries.";
+                }
+
+                function setNEntries(n) {
+                        if (n < 10)
+                                return 10;
+                        if (n > 1000)
+                                return 1000;
+                        localStorage["n_entries"] = n.toString();
+                        showNEntries(n);
+                }
+
+                function machineLoad() {
+                        var request = new XMLHttpRequest();
+                        request.open("GET", "/machine");
+                        request.onreadystatechange = machineOnResult;
+                        request.setRequestHeader("Accept", "application/json");
+                        request.send(null);
+                }
+
+                function formatBytes(u) {
+                        if (u >= 1024*1024*1024*1024)
+                                return (u/1024/1024/1024/1024).toFixed(1) + " TiB";
+                        else if (u >= 1024*1024*1024)
+                                return (u/1024/1024/1024).toFixed(1) + " GiB";
+                        else if (u >= 1024*1024)
+                                return (u/1024/1024).toFixed(1) + " MiB";
+                        else if (u >= 1024)
+                                return (u/1024).toFixed(1) + " KiB";
+                        else
+                                return u.toString() + " B";
+                }
+
+                function escapeHTML(s) {
+                        return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
+                }
+
+                function machineOnResult(event) {
+                        if ((event.currentTarget.readyState != 4) ||
+                                (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+                                return;
+
+                        var d = JSON.parse(event.currentTarget.responseText);
+
+                        var title = document.getElementById("title");
+                        title.innerHTML = 'Journal of ' + escapeHTML(d.hostname);
+                        document.title = 'Journal of ' + escapeHTML(d.hostname);
+
+                        var machine = document.getElementById("machine");
+                        machine.innerHTML = 'Machine ID is <b>' + d.machine_id + '</b>, current boot ID is <b>' + d.boot_id + '</b>.';
+
+                        var cutoff = document.getElementById("cutoff");
+                        var from = new Date(parseInt(d.cutoff_from_realtime) / 1000);
+                        var to = new Date(parseInt(d.cutoff_to_realtime) / 1000);
+                        cutoff.innerHTML = 'Journal begins at <b>' + from.toLocaleString() + '</b> and ends at <b>' + to.toLocaleString() + '</b>.';
+
+                        var usage = document.getElementById("usage");
+                        usage.innerHTML = 'Disk usage is <b>' + formatBytes(parseInt(d.usage)) + '</b>.';
+
+                        var os = document.getElementById("os");
+                        os.innerHTML = 'Operating system is <b>' + escapeHTML(d.os_pretty_name) + '</b>.';
+
+                        var virtualization = document.getElementById("virtualization");
+                        virtualization.innerHTML = d.virtualization == "bare" ? "Running on <b>bare metal</b>." : "Running on virtualization <b>" + escapeHTML(d.virtualization) + "</b>.";
+                }
+
+                function entriesLoad(range) {
+
+                        if (range == null)
+                                range = localStorage["cursor"] + ":0";
+                        if (range == null)
+                                range = "";
+
+                        var url = "/entries";
+
+                        if (localStorage["filter"] != "" && localStorage["filter"] != null) {
+                                url += "?_SYSTEMD_UNIT=" + escape(localStorage["filter"]);
+
+                                if (localStorage["boot"] == "1")
+                                        url += "&boot";
+                        } else {
+                                if (localStorage["boot"] == "1")
+                                        url += "?boot";
+                        }
+
+                        var request = new XMLHttpRequest();
+                        request.open("GET", url);
+                        request.onreadystatechange = entriesOnResult;
+                        request.setRequestHeader("Accept", "application/json");
+                        request.setRequestHeader("Range", "entries=" + range + ":" + getNEntries().toString());
+                        request.send(null);
+                }
+
+                function entriesLoadNext() {
+                        if (last_cursor == null)
+                                entriesLoad("");
+                        else
+                                entriesLoad(last_cursor + ":1");
+                }
+
+                function entriesLoadPrevious() {
+                        if (first_cursor == null)
+                                entriesLoad("");
+                        else
+                                entriesLoad(first_cursor + ":-" + getNEntries().toString());
+                }
+
+                function entriesLoadHead() {
+                        entriesLoad("");
+                }
+
+                function entriesLoadTail() {
+                        entriesLoad(":-" + getNEntries().toString());
+                }
+
+                function entriesOnResult(event) {
+
+                        if ((event.currentTarget.readyState != 4) ||
+                                (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+                                return;
+
+                        var logs = document.getElementById("tablelogs");
+
+                        var lc = null;
+                        var fc = null;
+
+                        var i, l = event.currentTarget.responseText.split('\n');
+
+                        if (l.length <= 1) {
+                                logs.innerHTML = '<tbody><tr><td colspan="3"><i>No further entries...</i></td></tr></tbody>';
+                                return;
+                        }
+
+                        var buf = '';
+
+                        for (i in l) {
+
+                                if (l[i] == '')
+                                        continue;
+
+                                var d = JSON.parse(l[i]);
+                                if (d.MESSAGE == undefined || d.__CURSOR == undefined)
+                                        continue;
+
+                                if (fc == null)
+                                        fc = d.__CURSOR;
+                                lc = d.__CURSOR;
+
+                                var priority;
+                                if (d.PRIORITY != undefined)
+                                        priority = parseInt(d.PRIORITY);
+                                else
+                                        priority = 6;
+
+                                if (priority <= 3)
+                                        clazz = "message-error";
+                                else if (priority <= 5)
+                                        clazz = "message-highlight";
+                                else
+                                        clazz = "message";
+
+                                buf += '<tr><td class="timestamp">';
+
+                                if (d.__REALTIME_TIMESTAMP != undefined) {
+                                        var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) / 1000);
+                                        buf += timestamp.toLocaleString();
+                                }
+
+                                buf += '</td><td class="process">';
+
+                                if (d.SYSLOG_IDENTIFIER != undefined)
+                                        buf += escapeHTML(d.SYSLOG_IDENTIFIER);
+                                else if (d._COMM != undefined)
+                                        buf += escapeHTML(d._COMM);
+
+                                if (d._PID != undefined)
+                                        buf += "[" + escapeHTML(d._PID) + "]";
+                                else if (d.SYSLOG_PID != undefined)
+                                        buf += "[" + escapeHTML(d.SYSLOG_PID) + "]";
+
+                                buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + d.__CURSOR + '\');">';
+
+                                if (d.MESSAGE == null)
+                                        buf += "[blob data]";
+                                else if (d.MESSAGE instanceof Array)
+                                        buf += "[" + formatBytes(d.MESSAGE.length) + " blob data]";
+                                else
+                                        buf += escapeHTML(d.MESSAGE);
+
+                                buf += '</a></td></tr>';
+                        }
+
+                        logs.innerHTML = '<tbody>' + buf + '</tbody>';
+
+                        if (fc != null) {
+                                first_cursor = fc;
+                                localStorage["cursor"] = fc;
+                        }
+                        if (lc != null)
+                                last_cursor = lc;
+                }
+
+                function entriesMore() {
+                        setNEntries(getNEntries() + 10);
+                        entriesLoad(first_cursor);
+                }
+
+                function entriesLess() {
+                        setNEntries(getNEntries() - 10);
+                        entriesLoad(first_cursor);
+                }
+
+                function onResultMessageClick(event) {
+                        if ((event.currentTarget.readyState != 4) ||
+                                (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+                                return;
+
+                        var d = JSON.parse(event.currentTarget.responseText);
+
+                        document.getElementById("diventry").style.display = "block";
+                        entry = document.getElementById("tableentry");
+
+                        var buf = "";
+                        for (var key in d){
+                                var data = d[key];
+
+                                if (data == null)
+                                        data = "[blob data]";
+                                else if (data instanceof Array)
+                                        data = "[" + formatBytes(data.length) + " blob data]";
+                                else
+                                        data = escapeHTML(data);
+
+                                buf += '<tr><td class="field">' + key + '</td><td class="data">' + data + '</td></tr>';
+                        }
+                        entry.innerHTML = '<tbody>' + buf + '</tbody>';
+                }
+
+                function onMessageClick(t) {
+                        var request = new XMLHttpRequest();
+                        request.open("GET", "/entries?discrete");
+                        request.onreadystatechange = onResultMessageClick;
+                        request.setRequestHeader("Accept", "application/json");
+                        request.setRequestHeader("Range", "entries=" + t + ":0:1");
+                        request.send(null);
+                }
+
+                function onKeyUp(event) {
+                        switch (event.keyCode) {
+                                case 8:
+                                case 37:
+                                case 75:
+                                        entriesLoadPrevious();
+                                        break;
+                                case 32:
+                                case 39:
+                                case 74:
+                                        entriesLoadNext();
+                                        break;
+
+                                case 71:
+                                        if (event.shiftKey)
+                                                entriesLoadTail();
+                                        else
+                                                entriesLoadHead();
+                                        break;
+                                case 171:
+                                        entriesMore();
+                                        break;
+                                case 173:
+                                        entriesLess();
+                                        break;
+                        }
+                }
+
+                function onMouseWheel(event) {
+                        if (event.detail < 0 || event.wheelDelta > 0)
+                                entriesLoadPrevious();
+                        else
+                                entriesLoadNext();
+                }
+
+                function onResultFilterFocus(event) {
+                        if ((event.currentTarget.readyState != 4) ||
+                                (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+                                return;
+
+                        f = document.getElementById("filter");
+
+                        var l = event.currentTarget.responseText.split('\n');
+                        var buf = '<option>No filter</option>';
+                        var j = -1;
+
+                        for (i in l) {
+
+                                if (l[i] == '')
+                                      continue;
+
+                                var d = JSON.parse(l[i]);
+                                if (d._SYSTEMD_UNIT == undefined)
+                                      continue;
+
+                                buf += '<option value="' + escape(d._SYSTEMD_UNIT) + '">' + escapeHTML(d._SYSTEMD_UNIT) + '</option>';
+
+                                if (d._SYSTEMD_UNIT == localStorage["filter"])
+                                        j = i;
+                        }
+
+                        if (j < 0) {
+                                if (localStorage["filter"] != null && localStorage["filter"] != "") {
+                                          buf += '<option value="' + escape(localStorage["filter"]) + '">' + escapeHTML(localStorage["filter"]) + '</option>';
+                                          j = i + 1;
+                                } else
+                                          j = 0;
+                        }
+
+                        f.innerHTML = buf;
+                        f.selectedIndex = j;
+                }
+
+                function onFilterFocus(w) {
+                        var request = new XMLHttpRequest();
+                        request.open("GET", "/fields/_SYSTEMD_UNIT");
+                        request.onreadystatechange = onResultFilterFocus;
+                        request.setRequestHeader("Accept", "application/json");
+                        request.send(null);
+                }
+
+                function onFilterChange(w) {
+                        if (w.selectedIndex <= 0)
+                                localStorage["filter"] = "";
+                        else
+                                localStorage["filter"] = unescape(w.options[w.selectedIndex].value);
+
+                        entriesLoadHead();
+                }
+
+                function onBootChange(w) {
+                        localStorage["boot"] = w.checked ? "1" : "0";
+                        entriesLoadHead();
+                }
+
+                function initFilter() {
+                        f = document.getElementById("filter");
+
+                        var buf = '<option>No filter</option>';
+
+                        var filter = localStorage["filter"];
+                        if (filter != null && filter != "") {
+                                buf += '<option value="' + escape(filter) + '">' + escapeHTML(filter) + '</option>';
+                                j = 1;
+                        } else
+                                j = 0;
+
+                        f.innerHTML = buf;
+                        f.selectedIndex = j;
+                }
+
+                function installHandlers() {
+                        document.onkeyup = onKeyUp;
+
+                        logs = document.getElementById("divlogs");
+                        logs.addEventListener("mousewheel", onMouseWheel, false);
+                        logs.addEventListener("DOMMouseScroll", onMouseWheel, false);
+                }
+
+                machineLoad();
+                entriesLoad(null);
+                showNEntries(getNEntries());
+                initFilter();
+                installHandlers();
+        </script>
+</body>
+</html>
diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c
new file mode 100644
index 0000000..db07700
--- /dev/null
+++ b/src/journal-remote/journal-gatewayd.c
@@ -0,0 +1,1054 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <getopt.h>
+
+#include <microhttpd.h>
+
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
+
+#include "log.h"
+#include "util.h"
+#include "sd-journal.h"
+#include "sd-daemon.h"
+#include "sd-bus.h"
+#include "bus-util.h"
+#include "logs-show.h"
+#include "microhttpd-util.h"
+#include "build.h"
+#include "fileio.h"
+
+static char *key_pem = NULL;
+static char *cert_pem = NULL;
+static char *trust_pem = NULL;
+
+typedef struct RequestMeta {
+        sd_journal *journal;
+
+        OutputMode mode;
+
+        char *cursor;
+        int64_t n_skip;
+        uint64_t n_entries;
+        bool n_entries_set;
+
+        FILE *tmp;
+        uint64_t delta, size;
+
+        int argument_parse_error;
+
+        bool follow;
+        bool discrete;
+
+        uint64_t n_fields;
+        bool n_fields_set;
+} RequestMeta;
+
+static const char* const mime_types[_OUTPUT_MODE_MAX] = {
+        [OUTPUT_SHORT] = "text/plain",
+        [OUTPUT_JSON] = "application/json",
+        [OUTPUT_JSON_SSE] = "text/event-stream",
+        [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
+};
+
+static RequestMeta *request_meta(void **connection_cls) {
+        RequestMeta *m;
+
+        assert(connection_cls);
+        if (*connection_cls)
+                return *connection_cls;
+
+        m = new0(RequestMeta, 1);
+        if (!m)
+                return NULL;
+
+        *connection_cls = m;
+        return m;
+}
+
+static void request_meta_free(
+                void *cls,
+                struct MHD_Connection *connection,
+                void **connection_cls,
+                enum MHD_RequestTerminationCode toe) {
+
+        RequestMeta *m = *connection_cls;
+
+        if (!m)
+                return;
+
+        if (m->journal)
+                sd_journal_close(m->journal);
+
+        if (m->tmp)
+                fclose(m->tmp);
+
+        free(m->cursor);
+        free(m);
+}
+
+static int open_journal(RequestMeta *m) {
+        assert(m);
+
+        if (m->journal)
+                return 0;
+
+        return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
+}
+
+static ssize_t request_reader_entries(
+                void *cls,
+                uint64_t pos,
+                char *buf,
+                size_t max) {
+
+        RequestMeta *m = cls;
+        int r;
+        size_t n, k;
+
+        assert(m);
+        assert(buf);
+        assert(max > 0);
+        assert(pos >= m->delta);
+
+        pos -= m->delta;
+
+        while (pos >= m->size) {
+                off_t sz;
+
+                /* End of this entry, so let's serialize the next
+                 * one */
+
+                if (m->n_entries_set &&
+                    m->n_entries <= 0)
+                        return MHD_CONTENT_READER_END_OF_STREAM;
+
+                if (m->n_skip < 0)
+                        r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
+                else if (m->n_skip > 0)
+                        r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
+                else
+                        r = sd_journal_next(m->journal);
+
+                if (r < 0) {
+                        log_error("Failed to advance journal pointer: %s", strerror(-r));
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                } else if (r == 0) {
+
+                        if (m->follow) {
+                                r = sd_journal_wait(m->journal, (uint64_t) -1);
+                                if (r < 0) {
+                                        log_error("Couldn't wait for journal event: %s", strerror(-r));
+                                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                                }
+
+                                continue;
+                        }
+
+                        return MHD_CONTENT_READER_END_OF_STREAM;
+                }
+
+                if (m->discrete) {
+                        assert(m->cursor);
+
+                        r = sd_journal_test_cursor(m->journal, m->cursor);
+                        if (r < 0) {
+                                log_error("Failed to test cursor: %s", strerror(-r));
+                                return MHD_CONTENT_READER_END_WITH_ERROR;
+                        }
+
+                        if (r == 0)
+                                return MHD_CONTENT_READER_END_OF_STREAM;
+                }
+
+                pos -= m->size;
+                m->delta += m->size;
+
+                if (m->n_entries_set)
+                        m->n_entries -= 1;
+
+                m->n_skip = 0;
+
+                if (m->tmp)
+                        rewind(m->tmp);
+                else {
+                        m->tmp = tmpfile();
+                        if (!m->tmp) {
+                                log_error("Failed to create temporary file: %m");
+                                return MHD_CONTENT_READER_END_WITH_ERROR;
+                        }
+                }
+
+                r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
+                if (r < 0) {
+                        log_error("Failed to serialize item: %s", strerror(-r));
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                }
+
+                sz = ftello(m->tmp);
+                if (sz == (off_t) -1) {
+                        log_error("Failed to retrieve file position: %m");
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                }
+
+                m->size = (uint64_t) sz;
+        }
+
+        if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
+                log_error("Failed to seek to position: %m");
+                return MHD_CONTENT_READER_END_WITH_ERROR;
+        }
+
+        n = m->size - pos;
+        if (n > max)
+                n = max;
+
+        errno = 0;
+        k = fread(buf, 1, n, m->tmp);
+        if (k != n) {
+                log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
+                return MHD_CONTENT_READER_END_WITH_ERROR;
+        }
+
+        return (ssize_t) k;
+}
+
+static int request_parse_accept(
+                RequestMeta *m,
+                struct MHD_Connection *connection) {
+
+        const char *header;
+
+        assert(m);
+        assert(connection);
+
+        header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
+        if (!header)
+                return 0;
+
+        if (streq(header, mime_types[OUTPUT_JSON]))
+                m->mode = OUTPUT_JSON;
+        else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
+                m->mode = OUTPUT_JSON_SSE;
+        else if (streq(header, mime_types[OUTPUT_EXPORT]))
+                m->mode = OUTPUT_EXPORT;
+        else
+                m->mode = OUTPUT_SHORT;
+
+        return 0;
+}
+
+static int request_parse_range(
+                RequestMeta *m,
+                struct MHD_Connection *connection) {
+
+        const char *range, *colon, *colon2;
+        int r;
+
+        assert(m);
+        assert(connection);
+
+        range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
+        if (!range)
+                return 0;
+
+        if (!startswith(range, "entries="))
+                return 0;
+
+        range += 8;
+        range += strspn(range, WHITESPACE);
+
+        colon = strchr(range, ':');
+        if (!colon)
+                m->cursor = strdup(range);
+        else {
+                const char *p;
+
+                colon2 = strchr(colon + 1, ':');
+                if (colon2) {
+                        _cleanup_free_ char *t;
+
+                        t = strndup(colon + 1, colon2 - colon - 1);
+                        if (!t)
+                                return -ENOMEM;
+
+                        r = safe_atoi64(t, &m->n_skip);
+                        if (r < 0)
+                                return r;
+                }
+
+                p = (colon2 ? colon2 : colon) + 1;
+                if (*p) {
+                        r = safe_atou64(p, &m->n_entries);
+                        if (r < 0)
+                                return r;
+
+                        if (m->n_entries <= 0)
+                                return -EINVAL;
+
+                        m->n_entries_set = true;
+                }
+
+                m->cursor = strndup(range, colon - range);
+        }
+
+        if (!m->cursor)
+                return -ENOMEM;
+
+        m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
+        if (isempty(m->cursor)) {
+                free(m->cursor);
+                m->cursor = NULL;
+        }
+
+        return 0;
+}
+
+static int request_parse_arguments_iterator(
+                void *cls,
+                enum MHD_ValueKind kind,
+                const char *key,
+                const char *value) {
+
+        RequestMeta *m = cls;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        assert(m);
+
+        if (isempty(key)) {
+                m->argument_parse_error = -EINVAL;
+                return MHD_NO;
+        }
+
+        if (streq(key, "follow")) {
+                if (isempty(value)) {
+                        m->follow = true;
+                        return MHD_YES;
+                }
+
+                r = parse_boolean(value);
+                if (r < 0) {
+                        m->argument_parse_error = r;
+                        return MHD_NO;
+                }
+
+                m->follow = r;
+                return MHD_YES;
+        }
+
+        if (streq(key, "discrete")) {
+                if (isempty(value)) {
+                        m->discrete = true;
+                        return MHD_YES;
+                }
+
+                r = parse_boolean(value);
+                if (r < 0) {
+                        m->argument_parse_error = r;
+                        return MHD_NO;
+                }
+
+                m->discrete = r;
+                return MHD_YES;
+        }
+
+        if (streq(key, "boot")) {
+                if (isempty(value))
+                        r = true;
+                else {
+                        r = parse_boolean(value);
+                        if (r < 0) {
+                                m->argument_parse_error = r;
+                                return MHD_NO;
+                        }
+                }
+
+                if (r) {
+                        char match[9 + 32 + 1] = "_BOOT_ID=";
+                        sd_id128_t bid;
+
+                        r = sd_id128_get_boot(&bid);
+                        if (r < 0) {
+                                log_error("Failed to get boot ID: %s", strerror(-r));
+                                return MHD_NO;
+                        }
+
+                        sd_id128_to_string(bid, match + 9);
+                        r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
+                        if (r < 0) {
+                                m->argument_parse_error = r;
+                                return MHD_NO;
+                        }
+                }
+
+                return MHD_YES;
+        }
+
+        p = strjoin(key, "=", strempty(value), NULL);
+        if (!p) {
+                m->argument_parse_error = log_oom();
+                return MHD_NO;
+        }
+
+        r = sd_journal_add_match(m->journal, p, 0);
+        if (r < 0) {
+                m->argument_parse_error = r;
+                return MHD_NO;
+        }
+
+        return MHD_YES;
+}
+
+static int request_parse_arguments(
+                RequestMeta *m,
+                struct MHD_Connection *connection) {
+
+        assert(m);
+        assert(connection);
+
+        m->argument_parse_error = 0;
+        MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
+
+        return m->argument_parse_error;
+}
+
+static int request_handler_entries(
+                struct MHD_Connection *connection,
+                void *connection_cls) {
+
+        struct MHD_Response *response;
+        RequestMeta *m = connection_cls;
+        int r;
+
+        assert(connection);
+        assert(m);
+
+        r = open_journal(m);
+        if (r < 0)
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+        if (request_parse_accept(m, connection) < 0)
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+
+        if (request_parse_range(m, connection) < 0)
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
+
+        if (request_parse_arguments(m, connection) < 0)
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
+
+        if (m->discrete) {
+                if (!m->cursor)
+                        return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
+
+                m->n_entries = 1;
+                m->n_entries_set = true;
+        }
+
+        if (m->cursor)
+                r = sd_journal_seek_cursor(m->journal, m->cursor);
+        else if (m->n_skip >= 0)
+                r = sd_journal_seek_head(m->journal);
+        else if (m->n_skip < 0)
+                r = sd_journal_seek_tail(m->journal);
+        if (r < 0)
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
+
+        response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
+        if (!response)
+                return respond_oom(connection);
+
+        MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
+
+        r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+        MHD_destroy_response(response);
+
+        return r;
+}
+
+static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
+        const char *eq;
+        size_t j;
+
+        eq = memchr(d, '=', l);
+        if (!eq)
+                return -EINVAL;
+
+        j = l - (eq - d + 1);
+
+        if (m == OUTPUT_JSON) {
+                fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
+                json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
+                fputs(" }\n", f);
+        } else {
+                fwrite(eq+1, 1, j, f);
+                fputc('\n', f);
+        }
+
+        return 0;
+}
+
+static ssize_t request_reader_fields(
+                void *cls,
+                uint64_t pos,
+                char *buf,
+                size_t max) {
+
+        RequestMeta *m = cls;
+        int r;
+        size_t n, k;
+
+        assert(m);
+        assert(buf);
+        assert(max > 0);
+        assert(pos >= m->delta);
+
+        pos -= m->delta;
+
+        while (pos >= m->size) {
+                off_t sz;
+                const void *d;
+                size_t l;
+
+                /* End of this field, so let's serialize the next
+                 * one */
+
+                if (m->n_fields_set &&
+                    m->n_fields <= 0)
+                        return MHD_CONTENT_READER_END_OF_STREAM;
+
+                r = sd_journal_enumerate_unique(m->journal, &d, &l);
+                if (r < 0) {
+                        log_error("Failed to advance field index: %s", strerror(-r));
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                } else if (r == 0)
+                        return MHD_CONTENT_READER_END_OF_STREAM;
+
+                pos -= m->size;
+                m->delta += m->size;
+
+                if (m->n_fields_set)
+                        m->n_fields -= 1;
+
+                if (m->tmp)
+                        rewind(m->tmp);
+                else {
+                        m->tmp = tmpfile();
+                        if (!m->tmp) {
+                                log_error("Failed to create temporary file: %m");
+                                return MHD_CONTENT_READER_END_WITH_ERROR;
+                        }
+                }
+
+                r = output_field(m->tmp, m->mode, d, l);
+                if (r < 0) {
+                        log_error("Failed to serialize item: %s", strerror(-r));
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                }
+
+                sz = ftello(m->tmp);
+                if (sz == (off_t) -1) {
+                        log_error("Failed to retrieve file position: %m");
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                }
+
+                m->size = (uint64_t) sz;
+        }
+
+        if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
+                log_error("Failed to seek to position: %m");
+                return MHD_CONTENT_READER_END_WITH_ERROR;
+        }
+
+        n = m->size - pos;
+        if (n > max)
+                n = max;
+
+        errno = 0;
+        k = fread(buf, 1, n, m->tmp);
+        if (k != n) {
+                log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
+                return MHD_CONTENT_READER_END_WITH_ERROR;
+        }
+
+        return (ssize_t) k;
+}
+
+static int request_handler_fields(
+                struct MHD_Connection *connection,
+                const char *field,
+                void *connection_cls) {
+
+        struct MHD_Response *response;
+        RequestMeta *m = connection_cls;
+        int r;
+
+        assert(connection);
+        assert(m);
+
+        r = open_journal(m);
+        if (r < 0)
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+        if (request_parse_accept(m, connection) < 0)
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+
+        r = sd_journal_query_unique(m->journal, field);
+        if (r < 0)
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
+
+        response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
+        if (!response)
+                return respond_oom(connection);
+
+        MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
+
+        r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+        MHD_destroy_response(response);
+
+        return r;
+}
+
+static int request_handler_redirect(
+                struct MHD_Connection *connection,
+                const char *target) {
+
+        char *page;
+        struct MHD_Response *response;
+        int ret;
+
+        assert(connection);
+        assert(target);
+
+        if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
+                return respond_oom(connection);
+
+        response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
+        if (!response) {
+                free(page);
+                return respond_oom(connection);
+        }
+
+        MHD_add_response_header(response, "Content-Type", "text/html");
+        MHD_add_response_header(response, "Location", target);
+
+        ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
+        MHD_destroy_response(response);
+
+        return ret;
+}
+
+static int request_handler_file(
+                struct MHD_Connection *connection,
+                const char *path,
+                const char *mime_type) {
+
+        struct MHD_Response *response;
+        int ret;
+        _cleanup_close_ int fd = -1;
+        struct stat st;
+
+        assert(connection);
+        assert(path);
+        assert(mime_type);
+
+        fd = open(path, O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
+
+        if (fstat(fd, &st) < 0)
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
+
+        response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
+        if (!response)
+                return respond_oom(connection);
+
+        fd = -1;
+
+        MHD_add_response_header(response, "Content-Type", mime_type);
+
+        ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+        MHD_destroy_response(response);
+
+        return ret;
+}
+
+static int get_virtualization(char **v) {
+        _cleanup_bus_unref_ sd_bus *bus = NULL;
+        char *b = NULL;
+        int r;
+
+        r = sd_bus_default_system(&bus);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_get_property_string(
+                        bus,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "Virtualization",
+                        NULL,
+                        &b);
+        if (r < 0)
+                return r;
+
+        if (isempty(b)) {
+                free(b);
+                *v = NULL;
+                return 0;
+        }
+
+        *v = b;
+        return 1;
+}
+
+static int request_handler_machine(
+                struct MHD_Connection *connection,
+                void *connection_cls) {
+
+        struct MHD_Response *response;
+        RequestMeta *m = connection_cls;
+        int r;
+        _cleanup_free_ char* hostname = NULL, *os_name = NULL;
+        uint64_t cutoff_from = 0, cutoff_to = 0, usage;
+        char *json;
+        sd_id128_t mid, bid;
+        _cleanup_free_ char *v = NULL;
+
+        assert(connection);
+        assert(m);
+
+        r = open_journal(m);
+        if (r < 0)
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+        r = sd_id128_get_machine(&mid);
+        if (r < 0)
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
+
+        r = sd_id128_get_boot(&bid);
+        if (r < 0)
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
+
+        hostname = gethostname_malloc();
+        if (!hostname)
+                return respond_oom(connection);
+
+        r = sd_journal_get_usage(m->journal, &usage);
+        if (r < 0)
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
+
+        r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
+        if (r < 0)
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
+
+        if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
+                parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
+
+        get_virtualization(&v);
+
+        r = asprintf(&json,
+                     "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
+                     "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
+                     "\"hostname\" : \"%s\","
+                     "\"os_pretty_name\" : \"%s\","
+                     "\"virtualization\" : \"%s\","
+                     "\"usage\" : \"%"PRIu64"\","
+                     "\"cutoff_from_realtime\" : \"%"PRIu64"\","
+                     "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
+                     SD_ID128_FORMAT_VAL(mid),
+                     SD_ID128_FORMAT_VAL(bid),
+                     hostname_cleanup(hostname, false),
+                     os_name ? os_name : "Linux",
+                     v ? v : "bare",
+                     usage,
+                     cutoff_from,
+                     cutoff_to);
+
+        if (r < 0)
+                return respond_oom(connection);
+
+        response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
+        if (!response) {
+                free(json);
+                return respond_oom(connection);
+        }
+
+        MHD_add_response_header(response, "Content-Type", "application/json");
+        r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+        MHD_destroy_response(response);
+
+        return r;
+}
+
+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) {
+        int r, code;
+
+        assert(connection);
+        assert(connection_cls);
+        assert(url);
+        assert(method);
+
+        if (!streq(method, "GET"))
+                return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+                                   "Unsupported method.\n");
+
+
+        if (!*connection_cls) {
+                if (!request_meta(connection_cls))
+                        return respond_oom(connection);
+                return MHD_YES;
+        }
+
+        if (trust_pem) {
+                r = check_permissions(connection, &code);
+                if (r < 0)
+                        return code;
+        }
+
+        if (streq(url, "/"))
+                return request_handler_redirect(connection, "/browse");
+
+        if (streq(url, "/entries"))
+                return request_handler_entries(connection, *connection_cls);
+
+        if (startswith(url, "/fields/"))
+                return request_handler_fields(connection, url + 8, *connection_cls);
+
+        if (streq(url, "/browse"))
+                return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
+
+        if (streq(url, "/machine"))
+                return request_handler_machine(connection, *connection_cls);
+
+        return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] ...\n\n"
+               "HTTP server for journal events.\n\n"
+               "  -h --help           Show this help\n"
+               "     --version        Show package version\n"
+               "     --cert=CERT.PEM  Server certificate in PEM format\n"
+               "     --key=KEY.PEM    Server key in PEM format\n"
+               "     --trust=CERT.PEM Certificat authority certificate in PEM format\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_KEY,
+                ARG_CERT,
+                ARG_TRUST,
+        };
+
+        int r, c;
+
+        static const struct option options[] = {
+                { "help",    no_argument,       NULL, 'h'         },
+                { "version", no_argument,       NULL, ARG_VERSION },
+                { "key",     required_argument, NULL, ARG_KEY     },
+                { "cert",    required_argument, NULL, ARG_CERT    },
+                { "trust",   required_argument, NULL, ARG_TRUST   },
+                {}
+        };
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+
+                switch(c) {
+
+                case 'h':
+                        return help();
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case ARG_KEY:
+                        if (key_pem) {
+                                log_error("Key file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &key_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read key file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(key_pem);
+                        break;
+
+                case ARG_CERT:
+                        if (cert_pem) {
+                                log_error("Certificate file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &cert_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read certificate file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(cert_pem);
+                        break;
+
+                case ARG_TRUST:
+#ifdef HAVE_GNUTLS
+                        if (trust_pem) {
+                                log_error("CA certificate file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &trust_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read CA certificate file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(trust_pem);
+                        break;
+#else
+                        log_error("Option --trust is not available.");
+#endif
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached("Unhandled option");
+                }
+
+        if (optind < argc) {
+                log_error("This program does not take arguments.");
+                return -EINVAL;
+        }
+
+        if (!!key_pem != !!cert_pem) {
+                log_error("Certificate and key files must be specified together");
+                return -EINVAL;
+        }
+
+        if (trust_pem && !key_pem) {
+                log_error("CA certificate can only be used with certificate file");
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        struct MHD_Daemon *d = NULL;
+        int r, n;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r < 0)
+                return EXIT_FAILURE;
+        if (r == 0)
+                return EXIT_SUCCESS;
+
+#ifdef HAVE_GNUTLS
+        gnutls_global_set_log_function(log_func_gnutls);
+        log_reset_gnutls_level();
+#endif
+
+        n = sd_listen_fds(1);
+        if (n < 0) {
+                log_error("Failed to determine passed sockets: %s", strerror(-n));
+                goto finish;
+        } else if (n > 1) {
+                log_error("Can't listen on more than one socket.");
+                goto finish;
+        } else {
+                struct MHD_OptionItem opts[] = {
+                        { MHD_OPTION_NOTIFY_COMPLETED,
+                          (intptr_t) request_meta_free, NULL },
+                        { MHD_OPTION_EXTERNAL_LOGGER,
+                          (intptr_t) microhttpd_logger, NULL },
+                        { MHD_OPTION_END, 0, NULL },
+                        { MHD_OPTION_END, 0, NULL },
+                        { MHD_OPTION_END, 0, NULL },
+                        { MHD_OPTION_END, 0, NULL },
+                        { MHD_OPTION_END, 0, NULL }};
+                int opts_pos = 2;
+                int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
+
+                if (n > 0)
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
+                if (key_pem) {
+                        assert(cert_pem);
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
+                        flags |= MHD_USE_SSL;
+                }
+                if (trust_pem) {
+                        assert(flags & MHD_USE_SSL);
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
+                }
+
+                d = MHD_start_daemon(flags, 19531,
+                                     NULL, NULL,
+                                     request_handler, NULL,
+                                     MHD_OPTION_ARRAY, opts,
+                                     MHD_OPTION_END);
+        }
+
+        if (!d) {
+                log_error("Failed to start daemon!");
+                goto finish;
+        }
+
+        pause();
+
+        r = EXIT_SUCCESS;
+
+finish:
+        if (d)
+                MHD_stop_daemon(d);
+
+        return r;
+}
diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c
new file mode 100644
index 0000000..dbdf02a
--- /dev/null
+++ b/src/journal-remote/journal-remote-parse.c
@@ -0,0 +1,439 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journal-remote-parse.h"
+#include "journald-native.h"
+
+#define LINE_CHUNK 1024u
+
+void source_free(RemoteSource *source) {
+        if (!source)
+                return;
+
+        if (source->fd >= 0) {
+                log_debug("Closing fd:%d (%s)", source->fd, source->name);
+                close(source->fd);
+        }
+        free(source->name);
+        free(source->buf);
+        iovw_free_contents(&source->iovw);
+        free(source);
+}
+
+static int get_line(RemoteSource *source, char **line, size_t *size) {
+        ssize_t n, remain;
+        char *c = NULL;
+        char *newbuf = NULL;
+        size_t newsize = 0;
+
+        assert(source);
+        assert(source->state == STATE_LINE);
+        assert(source->filled <= source->size);
+        assert(source->buf == NULL || source->size > 0);
+
+        if (source->buf)
+                c = memchr(source->buf, '\n', source->filled);
+
+        if (c != NULL)
+                goto docopy;
+
+ resize:
+        if (source->fd < 0)
+                /* we have to wait for some data to come to us */
+                return -EWOULDBLOCK;
+
+        if (source->size - source->filled < LINE_CHUNK) {
+                // XXX: add check for maximum line length
+
+                if (!GREEDY_REALLOC(source->buf, source->size,
+                                    source->filled + LINE_CHUNK))
+                        return log_oom();
+        }
+        assert(source->size - source->filled >= LINE_CHUNK);
+
+        n = read(source->fd, source->buf + source->filled,
+                 source->size - source->filled);
+        if (n < 0) {
+                if (errno != EAGAIN && errno != EWOULDBLOCK)
+                        log_error("read(%d, ..., %zd): %m", source->fd,
+                                  source->size - source->filled);
+                return -errno;
+        } else if (n == 0)
+                return 0;
+
+        c = memchr(source->buf + source->filled, '\n', n);
+        source->filled += n;
+
+        if (c == NULL)
+                goto resize;
+
+ docopy:
+        *line = source->buf;
+        *size = c + 1 - source->buf;
+
+        /* Check if something remains */
+        remain = source->buf + source->filled - c - 1;
+        assert(remain >= 0);
+        if (remain) {
+                newsize = MAX(remain, LINE_CHUNK);
+                newbuf = malloc(newsize);
+                if (!newbuf)
+                        return log_oom();
+                memcpy(newbuf, c + 1, remain);
+        }
+        source->buf = newbuf;
+        source->size = newsize;
+        source->filled = remain;
+
+        return 1;
+}
+
+int push_data(RemoteSource *source, const char *data, size_t size) {
+        assert(source);
+        assert(source->state != STATE_EOF);
+
+        if (!GREEDY_REALLOC(source->buf, source->size,
+                            source->filled + size))
+                return log_oom();
+
+        memcpy(source->buf + source->filled, data, size);
+        source->filled += size;
+
+        return 0;
+}
+
+static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
+        int n;
+        char *newbuf = NULL;
+        size_t newsize = 0, remain;
+
+        assert(source);
+        assert(source->state == STATE_DATA_START ||
+               source->state == STATE_DATA ||
+               source->state == STATE_DATA_FINISH);
+        assert(size <= DATA_SIZE_MAX);
+        assert(source->filled <= source->size);
+        assert(source->buf != NULL || source->size == 0);
+        assert(source->buf == NULL || source->size > 0);
+        assert(data);
+
+        while(source->filled < size) {
+                if (source->fd < 0)
+                        /* we have to wait for some data to come to us */
+                        return -EWOULDBLOCK;
+
+                if (!GREEDY_REALLOC(source->buf, source->size, size))
+                        return log_oom();
+
+                n = read(source->fd, source->buf + source->filled,
+                         source->size - source->filled);
+                if (n < 0) {
+                        if (errno != EAGAIN && errno != EWOULDBLOCK)
+                                log_error("read(%d, ..., %zd): %m", source->fd,
+                                          source->size - source->filled);
+                        return -errno;
+                } else if (n == 0)
+                        return 0;
+
+                source->filled += n;
+        }
+
+        *data = source->buf;
+
+        /* Check if something remains */
+        assert(size <= source->filled);
+        remain = source->filled - size;
+        if (remain) {
+                newsize = MAX(remain, LINE_CHUNK);
+                newbuf = malloc(newsize);
+                if (!newbuf)
+                        return log_oom();
+                memcpy(newbuf, source->buf + size, remain);
+        }
+        source->buf = newbuf;
+        source->size = newsize;
+        source->filled = remain;
+
+        return 1;
+}
+
+static int get_data_size(RemoteSource *source) {
+        int r;
+        _cleanup_free_ void *data = NULL;
+
+        assert(source);
+        assert(source->state == STATE_DATA_START);
+        assert(source->data_size == 0);
+
+        r = fill_fixed_size(source, &data, sizeof(uint64_t));
+        if (r <= 0)
+                return r;
+
+        source->data_size = le64toh( *(uint64_t *) data );
+        if (source->data_size > DATA_SIZE_MAX) {
+                log_error("Stream declares field with size %zu > %u == DATA_SIZE_MAX",
+                          source->data_size, DATA_SIZE_MAX);
+                return -EINVAL;
+        }
+        if (source->data_size == 0)
+                log_warning("Binary field with zero length");
+
+        return 1;
+}
+
+static int get_data_data(RemoteSource *source, void **data) {
+        int r;
+
+        assert(source);
+        assert(data);
+        assert(source->state == STATE_DATA);
+
+        r = fill_fixed_size(source, data, source->data_size);
+        if (r <= 0)
+                return r;
+
+        return 1;
+}
+
+static int get_data_newline(RemoteSource *source) {
+        int r;
+        _cleanup_free_ char *data = NULL;
+
+        assert(source);
+        assert(source->state == STATE_DATA_FINISH);
+
+        r = fill_fixed_size(source, (void**) &data, 1);
+        if (r <= 0)
+                return r;
+
+        assert(data);
+        if (*data != '\n') {
+                log_error("expected newline, got '%c'", *data);
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+static int process_dunder(RemoteSource *source, char *line, size_t n) {
+        const char *timestamp;
+        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)
+                /* ignore __CURSOR */
+                return 1;
+
+        timestamp = startswith(line, "__REALTIME_TIMESTAMP=");
+        if (timestamp) {
+                long long unsigned x;
+                line[n-1] = '\0';
+                r = safe_atollu(timestamp, &x);
+                if (r < 0)
+                        log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp);
+                else
+                        source->ts.realtime = x;
+                return r < 0 ? r : 1;
+        }
+
+        timestamp = startswith(line, "__MONOTONIC_TIMESTAMP=");
+        if (timestamp) {
+                long long unsigned x;
+                line[n-1] = '\0';
+                r = safe_atollu(timestamp, &x);
+                if (r < 0)
+                        log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp);
+                else
+                        source->ts.monotonic = x;
+                return r < 0 ? r : 1;
+        }
+
+        timestamp = startswith(line, "__");
+        if (timestamp) {
+                log_notice("Unknown dunder line %s", line);
+                return 1;
+        }
+
+        /* no dunder */
+        return 0;
+}
+
+int process_data(RemoteSource *source) {
+        int r;
+
+        switch(source->state) {
+        case STATE_LINE: {
+                char *line, *sep;
+                size_t n;
+
+                assert(source->data_size == 0);
+
+                r = get_line(source, &line, &n);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        source->state = STATE_EOF;
+                        return r;
+                }
+                assert(n > 0);
+                assert(line[n-1] == '\n');
+
+                if (n == 1) {
+                        log_debug("Received empty line, event is ready");
+                        free(line);
+                        return 1;
+                }
+
+                r = process_dunder(source, line, n);
+                if (r != 0) {
+                        free(line);
+                        return r < 0 ? r : 0;
+                }
+
+                /* MESSAGE=xxx\n
+                   or
+                   COREDUMP\n
+                   LLLLLLLL0011223344...\n
+                */
+                sep = memchr(line, '=', n);
+                if (sep)
+                        /* chomp newline */
+                        n--;
+                else
+                        /* replace \n with = */
+                        line[n-1] = '=';
+                log_debug("Received: %.*s", (int) n, line);
+
+                r = iovw_put(&source->iovw, line, n);
+                if (r < 0) {
+                        log_error("Failed to put line in iovect");
+                        free(line);
+                        return r;
+                }
+
+                if (!sep)
+                        source->state = STATE_DATA_START;
+                return 0; /* continue */
+        }
+
+        case STATE_DATA_START:
+                assert(source->data_size == 0);
+
+                r = get_data_size(source);
+                log_debug("get_data_size() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        source->state = STATE_EOF;
+                        return 0;
+                }
+
+                source->state = source->data_size > 0 ?
+                        STATE_DATA : STATE_DATA_FINISH;
+
+                return 0; /* continue */
+
+        case STATE_DATA: {
+                void *data;
+
+                assert(source->data_size > 0);
+
+                r = get_data_data(source, &data);
+                log_debug("get_data_data() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        source->state = STATE_EOF;
+                        return 0;
+                }
+
+                assert(data);
+
+                r = iovw_put(&source->iovw, data, source->data_size);
+                if (r < 0) {
+                        log_error("failed to put binary buffer in iovect");
+                        return r;
+                }
+
+                source->state = STATE_DATA_FINISH;
+
+                return 0; /* continue */
+        }
+
+        case STATE_DATA_FINISH:
+                r = get_data_newline(source);
+                log_debug("get_data_newline() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        source->state = STATE_EOF;
+                        return 0;
+                }
+
+                source->data_size = 0;
+                source->state = STATE_LINE;
+
+                return 0; /* continue */
+        default:
+                assert_not_reached("wtf?");
+        }
+}
+
+int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal) {
+        int r;
+
+        assert(source);
+        assert(writer);
+
+        r = process_data(source);
+        if (r <= 0)
+                return r;
+
+        /* We have a full event */
+        log_info("Received a full event from source@%p fd:%d (%s)",
+                 source, source->fd, source->name);
+
+        if (!source->iovw.count) {
+                log_warning("Entry with no payload, skipping");
+                goto freeing;
+        }
+
+        assert(source->iovw.iovec);
+        assert(source->iovw.count);
+
+        r = writer_write(writer, &source->iovw, &source->ts, compress, seal);
+        if (r < 0)
+                log_error("Failed to write entry of %zu bytes: %s",
+                          iovw_size(&source->iovw), strerror(-r));
+        else
+                r = 1;
+
+ freeing:
+        iovw_free_contents(&source->iovw);
+        return r;
+}
diff --git a/src/journal-remote/journal-remote-parse.h b/src/journal-remote/journal-remote-parse.h
new file mode 100644
index 0000000..c1506d1
--- /dev/null
+++ b/src/journal-remote/journal-remote-parse.h
@@ -0,0 +1,61 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include "sd-event.h"
+#include "journal-remote-write.h"
+
+typedef enum {
+        STATE_LINE = 0,    /* waiting to read, or reading line */
+        STATE_DATA_START,  /* reading binary data header */
+        STATE_DATA,        /* reading binary data */
+        STATE_DATA_FINISH, /* expecting newline */
+        STATE_EOF,         /* done */
+} source_state;
+
+typedef struct RemoteSource {
+        char* name;
+        int fd;
+
+        char *buf;
+        size_t size;
+        size_t filled;
+        size_t data_size;
+
+        struct iovec_wrapper iovw;
+
+        source_state state;
+        dual_timestamp ts;
+
+        sd_event_source *event;
+} RemoteSource;
+
+static inline int source_non_empty(RemoteSource *source) {
+        assert(source);
+
+        return source->filled > 0;
+}
+
+void source_free(RemoteSource *source);
+int process_data(RemoteSource *source);
+int push_data(RemoteSource *source, const char *data, size_t size);
+int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal);
diff --git a/src/journal-remote/journal-remote-write.c b/src/journal-remote/journal-remote-write.c
new file mode 100644
index 0000000..4d142bd
--- /dev/null
+++ b/src/journal-remote/journal-remote-write.c
@@ -0,0 +1,124 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journal-remote-write.h"
+
+int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
+        if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
+                return log_oom();
+
+        iovw->iovec[iovw->count++] = (struct iovec) {data, len};
+        return 0;
+}
+
+void iovw_free_contents(struct iovec_wrapper *iovw) {
+        for (size_t j = 0; j < iovw->count; j++)
+                free(iovw->iovec[j].iov_base);
+        free(iovw->iovec);
+        iovw->iovec = NULL;
+        iovw->size_bytes = iovw->count = 0;
+}
+
+size_t iovw_size(struct iovec_wrapper *iovw) {
+        size_t n = 0, i;
+
+        for(i = 0; i < iovw->count; i++)
+                n += iovw->iovec[i].iov_len;
+
+        return n;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int do_rotate(JournalFile **f, bool compress, bool seal) {
+        int r = journal_file_rotate(f, compress, seal);
+        if (r < 0) {
+                if (*f)
+                        log_error("Failed to rotate %s: %s", (*f)->path,
+                                  strerror(-r));
+                else
+                        log_error("Failed to create rotated journal: %s",
+                                  strerror(-r));
+        }
+
+        return r;
+}
+
+int writer_init(Writer *s) {
+        assert(s);
+
+        s->journal = NULL;
+
+        memset(&s->metrics, 0xFF, sizeof(s->metrics));
+
+        s->mmap = mmap_cache_new();
+        if (!s->mmap)
+                return log_oom();
+
+        s->seqnum = 0;
+
+        return 0;
+}
+
+int writer_close(Writer *s) {
+        if (s->journal)
+                journal_file_close(s->journal);
+        if (s->mmap)
+                mmap_cache_unref(s->mmap);
+        return 0;
+}
+
+int writer_write(Writer *s,
+                 struct iovec_wrapper *iovw,
+                 dual_timestamp *ts,
+                 bool compress,
+                 bool seal) {
+        int r;
+
+        assert(s);
+        assert(iovw);
+        assert(iovw->count > 0);
+
+        if (journal_file_rotate_suggested(s->journal, 0)) {
+                log_info("%s: Journal header limits reached or header out-of-date, rotating",
+                         s->journal->path);
+                r = do_rotate(&s->journal, compress, seal);
+                if (r < 0)
+                        return r;
+        }
+
+        r = journal_file_append_entry(s->journal, ts, iovw->iovec, iovw->count,
+                                      &s->seqnum, NULL, NULL);
+        if (r >= 0)
+                return 1;
+
+        log_info("%s: Write failed, rotating", s->journal->path);
+        r = do_rotate(&s->journal, compress, seal);
+        if (r < 0)
+                return r;
+
+        log_debug("Retrying write.");
+        r = journal_file_append_entry(s->journal, ts, iovw->iovec, iovw->count,
+                                      &s->seqnum, NULL, NULL);
+        return r < 0 ? r : 1;
+}
diff --git a/src/journal-remote/journal-remote-write.h b/src/journal-remote/journal-remote-write.h
new file mode 100644
index 0000000..8798216
--- /dev/null
+++ b/src/journal-remote/journal-remote-write.h
@@ -0,0 +1,51 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include <stdlib.h>
+
+#include "journal-file.h"
+
+struct iovec_wrapper {
+        struct iovec *iovec;
+        size_t size_bytes;
+        size_t count;
+};
+
+int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len);
+void iovw_free_contents(struct iovec_wrapper *iovw);
+size_t iovw_size(struct iovec_wrapper *iovw);
+
+typedef struct Writer {
+        JournalFile *journal;
+        JournalMetrics metrics;
+        MMapCache *mmap;
+        uint64_t seqnum;
+} Writer;
+
+int writer_init(Writer *s);
+int writer_close(Writer *s);
+int writer_write(Writer *s,
+                 struct iovec_wrapper *iovw,
+                 dual_timestamp *ts,
+                 bool compress,
+                 bool seal);
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
new file mode 100644
index 0000000..9db4692
--- /dev/null
+++ b/src/journal-remote/journal-remote.c
@@ -0,0 +1,1283 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "journal-file.h"
+#include "journald-native.h"
+#include "socket-util.h"
+#include "mkdir.h"
+#include "build.h"
+#include "macro.h"
+#include "strv.h"
+#include "fileio.h"
+#include "microhttpd-util.h"
+
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
+
+#include "journal-remote-parse.h"
+#include "journal-remote-write.h"
+
+#define REMOTE_JOURNAL_PATH "/var/log/journal/" SD_ID128_FORMAT_STR "/remote-%s.journal"
+
+static char* arg_output = NULL;
+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 char *key_pem = NULL;
+static char *cert_pem = NULL;
+static char *trust_pem = NULL;
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int spawn_child(const char* child, char** argv) {
+        int fd[2];
+        pid_t parent_pid, child_pid;
+        int r;
+
+        if (pipe(fd) < 0) {
+                log_error("Failed to create pager pipe: %m");
+                return -errno;
+        }
+
+        parent_pid = getpid();
+
+        child_pid = fork();
+        if (child_pid < 0) {
+                r = -errno;
+                log_error("Failed to fork: %m");
+                safe_close_pair(fd);
+                return r;
+        }
+
+        /* In the child */
+        if (child_pid == 0) {
+                r = dup2(fd[1], STDOUT_FILENO);
+                if (r < 0) {
+                        log_error("Failed to dup pipe to stdout: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                safe_close_pair(fd);
+
+                /* Make sure the child goes away when the parent dies */
+                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+                        _exit(EXIT_FAILURE);
+
+                /* Check whether our parent died before we were able
+                 * to set the death signal */
+                if (getppid() != parent_pid)
+                        _exit(EXIT_SUCCESS);
+
+                execvp(child, argv);
+                log_error("Failed to exec child %s: %m", child);
+                _exit(EXIT_FAILURE);
+        }
+
+        r = close(fd[1]);
+        if (r < 0)
+                log_warning("Failed to close write end of pipe: %m");
+
+        return fd[0];
+}
+
+static int spawn_curl(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("Failed to spawn curl: %m");
+        return r;
+}
+
+static int spawn_getter(char *getter, char *url) {
+        int r;
+        _cleanup_strv_free_ char **words = NULL;
+
+        assert(getter);
+        words = strv_split_quoted(getter);
+        if (!words)
+                return log_oom();
+
+        r = spawn_child(words[0], words);
+        if (r < 0)
+                log_error("Failed to spawn getter %s: %m", getter);
+
+        return r;
+}
+
+static int open_output(Writer *s, const char* url) {
+        _cleanup_free_ char *name, *output = NULL;
+        char *c;
+        int r;
+
+        assert(url);
+        name = strdup(url);
+        if (!name)
+                return log_oom();
+
+        for(c = name; *c; c++) {
+                if (*c == '/' || *c == ':' || *c == ' ')
+                        *c = '~';
+                else if (*c == '?') {
+                        *c = '\0';
+                        break;
+                }
+        }
+
+        if (!arg_output) {
+                sd_id128_t machine;
+                r = sd_id128_get_machine(&machine);
+                if (r < 0) {
+                        log_error("failed to determine machine ID128: %s", strerror(-r));
+                        return r;
+                }
+
+                r = asprintf(&output, REMOTE_JOURNAL_PATH,
+                             SD_ID128_FORMAT_VAL(machine), name);
+                if (r < 0)
+                        return log_oom();
+        } else {
+                r = is_dir(arg_output, true);
+                if (r > 0) {
+                        r = asprintf(&output,
+                                     "%s/remote-%s.journal", arg_output, name);
+                        if (r < 0)
+                                return log_oom();
+                } else {
+                        output = strdup(arg_output);
+                        if (!output)
+                                return log_oom();
+                }
+        }
+
+        r = journal_file_open_reliably(output,
+                                       O_RDWR|O_CREAT, 0640,
+                                       arg_compress, arg_seal,
+                                       &s->metrics,
+                                       s->mmap,
+                                       NULL, &s->journal);
+        if (r < 0)
+                log_error("Failed to open output journal %s: %s",
+                          arg_output, strerror(-r));
+        else
+                log_info("Opened output file %s", s->journal->path);
+        return r;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+typedef struct MHDDaemonWrapper {
+        uint64_t fd;
+        struct MHD_Daemon *daemon;
+
+        sd_event_source *event;
+} MHDDaemonWrapper;
+
+typedef struct RemoteServer {
+        RemoteSource **sources;
+        size_t sources_size;
+        size_t active;
+
+        sd_event *events;
+        sd_event_source *sigterm_event, *sigint_event, *listen_event;
+
+        Writer writer;
+
+        Hashmap *daemons;
+} RemoteServer;
+
+/* This should go away as soon as µhttpd allows state to be passed around. */
+static RemoteServer *server;
+
+static int dispatch_raw_source_event(sd_event_source *event,
+                                     int fd,
+                                     uint32_t revents,
+                                     void *userdata);
+static int dispatch_raw_connection_event(sd_event_source *event,
+                                         int fd,
+                                         uint32_t revents,
+                                         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, RemoteSource **source) {
+        assert(fd >= 0);
+        assert(source);
+
+        if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
+                return log_oom();
+
+        if (s->sources[fd] == NULL) {
+                s->sources[fd] = new0(RemoteSource, 1);
+                if (!s->sources[fd])
+                        return log_oom();
+                s->sources[fd]->fd = -1;
+                s->active++;
+        }
+
+        *source = s->sources[fd];
+        return 0;
+}
+
+static int remove_source(RemoteServer *s, int fd) {
+        RemoteSource *source;
+
+        assert(s);
+        assert(fd >= 0 && fd < (ssize_t) s->sources_size);
+
+        source = s->sources[fd];
+        if (source) {
+                source_free(source);
+                s->sources[fd] = NULL;
+                s->active--;
+        }
+
+        close(fd);
+
+        return 0;
+}
+
+static int add_source(RemoteServer *s, int fd, const char* name) {
+        RemoteSource *source = NULL;
+        _cleanup_free_ char *realname = NULL;
+        int r;
+
+        assert(s);
+        assert(fd >= 0);
+
+        if (name) {
+                realname = strdup(name);
+                if (!realname)
+                        return log_oom();
+        } else {
+                r = asprintf(&realname, "fd:%d", fd);
+                if (r < 0)
+                        return log_oom();
+        }
+
+        log_debug("Creating source for fd:%d (%s)", fd, realname);
+
+        r = get_source_for_fd(s, fd, &source);
+        if (r < 0) {
+                log_error("Failed to create source for fd:%d (%s)", fd, realname);
+                return r;
+        }
+        assert(source);
+        assert(source->fd < 0);
+        source->fd = fd;
+
+        r = sd_event_add_io(s->events, &source->event,
+                            fd, EPOLLIN, dispatch_raw_source_event, s);
+        if (r < 0) {
+                log_error("Failed to register event source for fd:%d: %s",
+                          fd, strerror(-r));
+                goto error;
+        }
+
+        return 1; /* work to do */
+
+ error:
+        remove_source(s, fd);
+        return r;
+}
+
+static int add_raw_socket(RemoteServer *s, int fd) {
+        int r;
+
+        r = sd_event_add_io(s->events, &s->listen_event, fd, EPOLLIN,
+                            dispatch_raw_connection_event, s);
+        if (r < 0) {
+                close(fd);
+                return r;
+        }
+
+        s->active ++;
+        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 RemoteSource *request_meta(void **connection_cls) {
+        RemoteSource *source;
+
+        assert(connection_cls);
+        if (*connection_cls)
+                return *connection_cls;
+
+        source = new0(RemoteSource, 1);
+        if (!source)
+                return NULL;
+        source->fd = -1;
+
+        log_debug("Added RemoteSource as connection metadata %p", source);
+
+        *connection_cls = source;
+        return source;
+}
+
+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;
+
+        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;
+        int r;
+
+        assert(source);
+
+        log_debug("request_handler_upload: connection %p, %zu bytes",
+                  connection, *upload_data_size);
+
+        if (*upload_data_size) {
+                log_info("Received %zu bytes", *upload_data_size);
+
+                r = push_data(source, upload_data, *upload_data_size);
+                if (r < 0) {
+                        log_error("Failed to store received data of size %zu: %s",
+                                  *upload_data_size, strerror(-r));
+                        return mhd_respond_oom(connection);
+                }
+                *upload_data_size = 0;
+        } else
+                finished = true;
+
+        while (true) {
+                r = process_source(source, &server->writer, arg_compress, arg_seal);
+                if (r == -E2BIG)
+                        log_warning("Entry too big, skipped");
+                else if (r == -EAGAIN || r == -EWOULDBLOCK)
+                        break;
+                else if (r < 0) {
+                        log_warning("Failed to process data for connection %p", connection);
+                        return mhd_respondf(connection, MHD_HTTP_UNPROCESSABLE_ENTITY,
+                                            "Processing failed: %s", strerror(-r));
+                }
+        }
+
+        if (!finished)
+                return MHD_YES;
+
+        /* The upload is finished */
+
+        if (source_non_empty(source)) {
+                log_warning("EOF reached with incomplete data");
+                return mhd_respond(connection, MHD_HTTP_EXPECTATION_FAILED,
+                                   "Trailing data not processed.");
+        }
+
+        return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n");
+};
+
+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;
+
+        assert(connection);
+        assert(connection_cls);
+        assert(url);
+        assert(method);
+
+        log_debug("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_METHOD_NOT_ACCEPTABLE,
+                                   "Unsupported method.\n");
+
+        if (!streq(url, "/upload"))
+                return mhd_respond(connection, MHD_HTTP_NOT_FOUND,
+                                   "Not found.\n");
+
+        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.\n");
+
+        if (trust_pem) {
+                r = check_permissions(connection, &code);
+                if (r < 0)
+                        return code;
+        }
+
+        if (!request_meta(connection_cls))
+                return respond_oom(connection);
+        return MHD_YES;
+}
+
+static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
+        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_END},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END}};
+        int opts_pos = 3;
+        int flags =
+                MHD_USE_DEBUG |
+                MHD_USE_PEDANTIC_CHECKS |
+                MHD_USE_EPOLL_LINUX_ONLY |
+                MHD_USE_DUAL_STACK;
+
+        const union MHD_DaemonInfo *info;
+        int r, epoll_fd;
+        MHDDaemonWrapper *d;
+
+        assert(fd >= 0);
+
+        r = fd_nonblock(fd, true);
+        if (r < 0) {
+                log_error("Failed to make fd:%d nonblocking: %s", fd, strerror(-r));
+                return r;
+        }
+
+        if (https) {
+                opts[opts_pos++] = (struct MHD_OptionItem)
+                        {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
+                opts[opts_pos++] = (struct MHD_OptionItem)
+                        {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
+
+                flags |= MHD_USE_SSL;
+
+                if (trust_pem)
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
+        }
+
+        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)",
+                  https ? "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 = -ENOTSUP;
+                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->event,
+                            epoll_fd, EPOLLIN, dispatch_http_event, d);
+        if (r < 0) {
+                log_error("Failed to add event callback: %s", strerror(-r));
+                goto error;
+        }
+
+        r = hashmap_ensure_allocated(&s->daemons, uint64_hash_func, uint64_compare_func);
+        if (r < 0) {
+                log_oom();
+                goto error;
+        }
+
+        r = hashmap_put(s->daemons, &d->fd, d);
+        if (r < 0) {
+                log_error("Failed to add daemon to hashmap: %s", strerror(-r));
+                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,
+                                   bool https) {
+        int fd;
+
+        fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        return setup_microhttpd_server(s, fd, https);
+}
+
+static int dispatch_http_event(sd_event_source *event,
+                               int fd,
+                               uint32_t revents,
+                               void *userdata) {
+        MHDDaemonWrapper *d = userdata;
+        int r;
+
+        assert(d);
+
+        log_info("%s", __func__);
+
+        r = MHD_run(d->daemon);
+        if (r == MHD_NO) {
+                log_error("MHD_run failed!");
+                // XXX: unregister daemon
+                return -EINVAL;
+        }
+
+        return 1; /* work to do */
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int dispatch_sigterm(sd_event_source *event,
+                            const struct signalfd_siginfo *si,
+                            void *userdata) {
+        RemoteServer *s = userdata;
+
+        assert(s);
+
+        log_received_signal(LOG_INFO, si);
+
+        sd_event_exit(s->events, 0);
+        return 0;
+}
+
+static int setup_signals(RemoteServer *s) {
+        sigset_t mask;
+        int r;
+
+        assert(s);
+
+        assert_se(sigemptyset(&mask) == 0);
+        sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+        assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+        r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, dispatch_sigterm, s);
+        if (r < 0)
+                return r;
+
+        r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, dispatch_sigterm, s);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int fd_fd(const char *spec) {
+        int fd, r;
+
+        r = safe_atoi(spec, &fd);
+        if (r < 0)
+                return r;
+
+        if (fd >= 0)
+                return -ENOENT;
+
+        return -fd;
+}
+
+
+static int remoteserver_init(RemoteServer *s) {
+        int r, n, fd;
+        const char *output_name = NULL;
+        char **file;
+
+        assert(s);
+
+        sd_event_default(&s->events);
+
+        setup_signals(s);
+
+        assert(server == NULL);
+        server = s;
+
+        n = sd_listen_fds(true);
+        if (n < 0) {
+                log_error("Failed to read listening file descriptors from environment: %s",
+                          strerror(-n));
+                return n;
+        } else
+                log_info("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, false)) {
+                        log_info("Received a listening socket (fd:%d)", fd);
+
+                        if (fd == http_socket)
+                                r = setup_microhttpd_server(s, fd, false);
+                        else if (fd == https_socket)
+                                r = setup_microhttpd_server(s, fd, true);
+                        else
+                                r = add_raw_socket(s, fd);
+                } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
+                        log_info("Received a connection socket (fd:%d)", fd);
+
+                        r = add_source(s, fd, NULL);
+                } else {
+                        log_error("Unknown socket passed on fd:%d", fd);
+
+                        return -EINVAL;
+                }
+
+                if(r < 0) {
+                        log_error("Failed to register socket (fd:%d): %s",
+                                  fd, strerror(-r));
+                        return r;
+                }
+
+                output_name = "socket";
+        }
+
+        if (arg_url) {
+                _cleanup_free_ char *url = NULL;
+                _cleanup_strv_free_ char **urlv = strv_new(arg_url, "/entries", NULL);
+                if (!urlv)
+                        return log_oom();
+                url = strv_join(urlv, "");
+                if (!url)
+                        return log_oom();
+
+                if (arg_getter) {
+                        log_info("Spawning getter %s...", url);
+                        fd = spawn_getter(arg_getter, url);
+                } else {
+                        log_info("Spawning curl %s...", url);
+                        fd = spawn_curl(url);
+                }
+                if (fd < 0)
+                        return fd;
+
+                r = add_source(s, fd, arg_url);
+                if (r < 0)
+                        return r;
+
+                output_name = arg_url;
+        }
+
+        if (arg_listen_raw) {
+                log_info("Listening on a socket...");
+                r = setup_raw_socket(s, arg_listen_raw);
+                if (r < 0)
+                        return r;
+
+                output_name = arg_listen_raw;
+        }
+
+        if (arg_listen_http) {
+                r = setup_microhttpd_socket(s, arg_listen_http, false);
+                if (r < 0)
+                        return r;
+
+                output_name = arg_listen_http;
+        }
+
+        if (arg_listen_https) {
+                r = setup_microhttpd_socket(s, arg_listen_https, true);
+                if (r < 0)
+                        return r;
+
+                output_name = arg_listen_https;
+        }
+
+        STRV_FOREACH(file, arg_files) {
+                if (streq(*file, "-")) {
+                        log_info("Reading standard input...");
+
+                        fd = STDIN_FILENO;
+                        output_name = "stdin";
+                } else {
+                        log_info("Reading file %s...", *file);
+
+                        fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+                        if (fd < 0) {
+                                log_error("Failed to open %s: %m", *file);
+                                return -errno;
+                        }
+                        output_name = *file;
+                }
+
+                r = add_source(s, fd, output_name);
+                if (r < 0)
+                        return r;
+        }
+
+        if (s->active == 0) {
+                log_error("Zarro sources specified");
+                return -EINVAL;
+        }
+
+        if (!!n + !!arg_url + !!arg_listen_raw + !!arg_files)
+                output_name = "multiple";
+
+        r = writer_init(&s->writer);
+        if (r < 0)
+                return r;
+
+        r = open_output(&s->writer, output_name);
+        return r;
+}
+
+static int server_destroy(RemoteServer *s) {
+        int r;
+        size_t i;
+        MHDDaemonWrapper *d;
+
+        r = writer_close(&s->writer);
+
+        while ((d = hashmap_steal_first(s->daemons))) {
+                MHD_stop_daemon(d->daemon);
+                sd_event_source_unref(d->event);
+                free(d);
+        }
+
+        hashmap_free(s->daemons);
+
+        assert(s->sources_size == 0 || s->sources);
+        for (i = 0; i < s->sources_size; i++)
+                remove_source(s, i);
+
+        free(s->sources);
+
+        sd_event_source_unref(s->sigterm_event);
+        sd_event_source_unref(s->sigint_event);
+        sd_event_source_unref(s->listen_event);
+        sd_event_unref(s->events);
+
+        /* fds that we're listening on remain open... */
+
+        return r;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int dispatch_raw_source_event(sd_event_source *event,
+                                     int fd,
+                                     uint32_t revents,
+                                     void *userdata) {
+
+        RemoteServer *s = userdata;
+        RemoteSource *source;
+        int r;
+
+        assert(fd >= 0 && fd < (ssize_t) s->sources_size);
+        source = s->sources[fd];
+        assert(source->fd == fd);
+
+        r = process_source(source, &s->writer, arg_compress, arg_seal);
+        if (source->state == STATE_EOF) {
+                log_info("EOF reached with source fd:%d (%s)",
+                         source->fd, source->name);
+                if (source_non_empty(source))
+                        log_warning("EOF reached with incomplete data");
+                remove_source(s, source->fd);
+                log_info("%zd active source remaining", s->active);
+        } else if (r == -E2BIG) {
+                log_error("Entry too big, skipped");
+                r = 1;
+        }
+
+        return r;
+}
+
+static int accept_connection(const char* type, int fd, SocketAddress *addr) {
+        int fd2, r;
+
+        log_debug("Accepting new %s connection on fd:%d", type, fd);
+        fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC);
+        if (fd2 < 0) {
+                log_error("accept() on fd:%d failed: %m", fd);
+                return -errno;
+        }
+
+        switch(socket_address_family(addr)) {
+        case AF_INET:
+        case AF_INET6: {
+                char* _cleanup_free_ a = NULL;
+
+                r = socket_address_print(addr, &a);
+                if (r < 0) {
+                        log_error("socket_address_print(): %s", strerror(-r));
+                        close(fd2);
+                        return r;
+                }
+
+                log_info("Accepted %s %s connection from %s",
+                         type,
+                         socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
+                         a);
+
+                return fd2;
+        };
+        default:
+                log_error("Rejected %s connection with unsupported family %d",
+                          type, socket_address_family(addr));
+                close(fd2);
+
+                return -EINVAL;
+        }
+}
+
+static int dispatch_raw_connection_event(sd_event_source *event,
+                                         int fd,
+                                         uint32_t revents,
+                                         void *userdata) {
+        RemoteServer *s = userdata;
+        int fd2;
+        SocketAddress addr = {
+                .size = sizeof(union sockaddr_union),
+                .type = SOCK_STREAM,
+        };
+
+        fd2 = accept_connection("raw", fd, &addr);
+        if (fd2 < 0)
+                return fd2;
+
+        return add_source(s, fd2, NULL);
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int help(void) {
+        printf("%s [OPTIONS...] {FILE|-}...\n\n"
+               "Write external journal events to a journal file.\n\n"
+               "Options:\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"
+               "  --[no-]compress      Use XZ-compression in the output journal (default: yes)\n"
+               "  --[no-]seal          Use Event sealing in the output journal (default: no)\n"
+               "  --key=FILENAME       Specify key in PEM format\n"
+               "  --cert=FILENAME      Specify certificate in PEM format\n"
+               "  --trust=FILENAME     Specify CA certificate in PEM format\n"
+               "  --gnutls-log=CATEGORY...\n"
+               "                       Specify a list of gnutls logging categories\n"
+               "  -h --help            Show this help and exit\n"
+               "  --version            Print version string and exit\n"
+               "\n"
+               "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
+               , program_invocation_short_name);
+
+        return 0;
+}
+
+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_COMPRESS,
+                ARG_NO_COMPRESS,
+                ARG_SEAL,
+                ARG_NO_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'              },
+                { "compress",     no_argument,       NULL, ARG_COMPRESS     },
+                { "no-compress",  no_argument,       NULL, ARG_NO_COMPRESS  },
+                { "seal",         no_argument,       NULL, ARG_SEAL         },
+                { "no-seal",      no_argument,       NULL, ARG_NO_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;
+
+        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:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0 /* done */;
+
+                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 = fd_fd(optarg);
+                        if (r >= 0)
+                                http_socket = r;
+                        else if (r == -ENOENT)
+                                arg_listen_http = optarg;
+                        else {
+                                log_error("Invalid port/fd specification %s: %s",
+                                          optarg, strerror(-r));
+                                return -EINVAL;
+                        }
+
+                        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 = fd_fd(optarg);
+                        if (r >= 0)
+                                https_socket = r;
+                        else if (r == -ENOENT)
+                                arg_listen_https = optarg;
+                        else {
+                                log_error("Invalid port/fd specification %s: %s",
+                                          optarg, strerror(-r));
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case ARG_KEY:
+                        if (key_pem) {
+                                log_error("Key file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &key_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read key file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(key_pem);
+                        break;
+
+                case ARG_CERT:
+                        if (cert_pem) {
+                                log_error("Certificate file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &cert_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read certificate file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(cert_pem);
+                        break;
+
+                case ARG_TRUST:
+#ifdef HAVE_GNUTLS
+                        if (trust_pem) {
+                                log_error("CA certificate file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &trust_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read CA certificate file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(trust_pem);
+                        break;
+#else
+                        log_error("Option --trust is not available.");
+                        return -EINVAL;
+#endif
+
+                case 'o':
+                        if (arg_output) {
+                                log_error("cannot use --output/-o more than once");
+                                return -EINVAL;
+                        }
+
+                        arg_output = optarg;
+                        break;
+
+                case ARG_COMPRESS:
+                        arg_compress = true;
+                        break;
+                case ARG_NO_COMPRESS:
+                        arg_compress = false;
+                        break;
+                case ARG_SEAL:
+                        arg_seal = true;
+                        break;
+                case ARG_NO_SEAL:
+                        arg_seal = false;
+                        break;
+
+                case ARG_GNUTLS_LOG: {
+#ifdef HAVE_GNUTLS
+                        char *word, *state;
+                        size_t size;
+
+                        FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) {
+                                char *cat;
+
+                                cat = strndup(word, size);
+                                if (!cat)
+                                        return log_oom();
+
+                                if (strv_consume(&arg_gnutls_log, cat) < 0)
+                                        return log_oom();
+                        }
+                        break;
+#else
+                        log_error("Option --gnutls-log is not available.");
+                        return -EINVAL;
+#endif
+                }
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+
+        if (arg_listen_https && !(key_pem && cert_pem)) {
+                log_error("Options --key and --cert must be used when https sources are specified");
+                return -EINVAL;
+        }
+
+        if (optind < argc)
+                arg_files = argv + optind;
+
+        return 1 /* work to do */;
+}
+
+static int setup_gnutls_logger(char **categories) {
+        if (!arg_listen_http && !arg_listen_https)
+                return 0;
+
+#ifdef HAVE_GNUTLS
+        {
+                char **cat;
+                int r;
+
+                gnutls_global_set_log_function(log_func_gnutls);
+
+                if (categories)
+                        STRV_FOREACH(cat, categories) {
+                                r = log_enable_gnutls_category(*cat);
+                                if (r < 0)
+                                        return r;
+                        }
+                else
+                        log_reset_gnutls_level();
+        }
+#endif
+
+        return 0;
+}
+
+int main(int argc, char **argv) {
+        RemoteServer s = {};
+        int r, r2;
+
+        log_set_max_level(LOG_DEBUG);
+        log_show_color(true);
+        log_parse_environment();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+        r = setup_gnutls_logger(arg_gnutls_log);
+        if (r < 0)
+                return EXIT_FAILURE;
+
+        if (remoteserver_init(&s) < 0)
+                return EXIT_FAILURE;
+
+        log_debug("%s running as pid "PID_FMT,
+                  program_invocation_short_name, getpid());
+        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("Failed to run event loop: %s", strerror(-r));
+                        break;
+                }
+        }
+
+        log_info("Finishing after writing %" PRIu64 " entries", s.writer.seqnum);
+        r2 = server_destroy(&s);
+
+        sd_notify(false, "STATUS=Shutting down...");
+
+        return r >= 0 && r2 >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/journal-remote/microhttpd-util.c b/src/journal-remote/microhttpd-util.c
new file mode 100644
index 0000000..d046686
--- /dev/null
+++ b/src/journal-remote/microhttpd-util.c
@@ -0,0 +1,298 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+  Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "microhttpd-util.h"
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+#include "strv.h"
+
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#endif
+
+void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
+        char *f;
+
+        f = strappenda("microhttpd: ", fmt);
+
+        DISABLE_WARNING_FORMAT_NONLITERAL;
+        log_metav(LOG_INFO, NULL, 0, NULL, f, ap);
+        REENABLE_WARNING;
+}
+
+
+static int mhd_respond_internal(struct MHD_Connection *connection,
+                                enum MHD_RequestTerminationCode code,
+                                char *buffer,
+                                size_t size,
+                                enum MHD_ResponseMemoryMode mode) {
+        struct MHD_Response *response;
+        int r;
+
+        assert(connection);
+
+        response = MHD_create_response_from_buffer(size, buffer, mode);
+        if (!response)
+                return MHD_NO;
+
+        log_debug("Queing response %u: %s", code, buffer);
+        MHD_add_response_header(response, "Content-Type", "text/plain");
+        r = MHD_queue_response(connection, code, response);
+        MHD_destroy_response(response);
+
+        return r;
+}
+
+int mhd_respond(struct MHD_Connection *connection,
+                enum MHD_RequestTerminationCode code,
+                const char *message) {
+
+        return mhd_respond_internal(connection, code,
+                                    (char*) message, strlen(message),
+                                    MHD_RESPMEM_PERSISTENT);
+}
+
+int mhd_respond_oom(struct MHD_Connection *connection) {
+        return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE,  "Out of memory.\n");
+}
+
+int mhd_respondf(struct MHD_Connection *connection,
+                 enum MHD_RequestTerminationCode code,
+                 const char *format, ...) {
+
+        char *m;
+        int r;
+        va_list ap;
+
+        assert(connection);
+        assert(format);
+
+        va_start(ap, format);
+        r = vasprintf(&m, format, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return respond_oom(connection);
+
+        return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
+}
+
+#ifdef HAVE_GNUTLS
+
+static struct {
+        const char *const names[4];
+        int level;
+        bool enabled;
+} gnutls_log_map[] = {
+        { {"0"},                  LOG_DEBUG },
+        { {"1", "audit"},         LOG_WARNING, true}, /* gnutls session audit */
+        { {"2", "assert"},        LOG_DEBUG },        /* gnutls assert log */
+        { {"3", "hsk", "ext"},    LOG_DEBUG },        /* gnutls handshake log */
+        { {"4", "rec"},           LOG_DEBUG },        /* gnutls record log */
+        { {"5", "dtls"},          LOG_DEBUG },        /* gnutls DTLS log */
+        { {"6", "buf"},           LOG_DEBUG },
+        { {"7", "write", "read"}, LOG_DEBUG },
+        { {"8"},                  LOG_DEBUG },
+        { {"9", "enc", "int"},    LOG_DEBUG },
+};
+
+void log_func_gnutls(int level, const char *message) {
+        assert_se(message);
+
+        if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
+                if (gnutls_log_map[level].enabled)
+                        log_meta(gnutls_log_map[level].level, NULL, 0, NULL,
+                                 "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
+        } else {
+                log_debug("Received GNUTLS message with unknown level %d.", level);
+                log_meta(LOG_DEBUG, NULL, 0, NULL, "gnutls: %s", message);
+        }
+}
+
+int log_enable_gnutls_category(const char *cat) {
+        unsigned i;
+
+        if (streq(cat, "all")) {
+                for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
+                        gnutls_log_map[i].enabled = true;
+                log_reset_gnutls_level();
+                return 0;
+        } else
+                for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
+                        if (strv_contains((char**)gnutls_log_map[i].names, cat)) {
+                                gnutls_log_map[i].enabled = true;
+                                log_reset_gnutls_level();
+                                return 0;
+                        }
+        log_error("No such log category: %s", cat);
+        return -EINVAL;
+}
+
+void log_reset_gnutls_level(void) {
+        int i;
+
+        for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
+                if (gnutls_log_map[i].enabled) {
+                        log_debug("Setting gnutls log level to %d", i);
+                        gnutls_global_set_log_level(i);
+                        break;
+                }
+}
+
+static int verify_cert_authorized(gnutls_session_t session) {
+        unsigned status;
+        gnutls_certificate_type_t type;
+        gnutls_datum_t out;
+        int r;
+
+        r = gnutls_certificate_verify_peers2(session, &status);
+        if (r < 0) {
+                log_error("gnutls_certificate_verify_peers2 failed: %s", strerror(-r));
+                return r;
+        }
+
+        type = gnutls_certificate_type_get(session);
+        r = gnutls_certificate_verification_status_print(status, type, &out, 0);
+        if (r < 0) {
+                log_error("gnutls_certificate_verification_status_print failed: %s", strerror(-r));
+                return r;
+        }
+
+        log_info("Certificate status: %s", out.data);
+
+        return status == 0 ? 0 : -EPERM;
+}
+
+static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
+        const gnutls_datum_t *pcert;
+        unsigned listsize;
+        gnutls_x509_crt_t cert;
+        int r;
+
+        assert(session);
+        assert(client_cert);
+
+        pcert = gnutls_certificate_get_peers(session, &listsize);
+        if (!pcert || !listsize) {
+                log_error("Failed to retrieve certificate chain");
+                return -EINVAL;
+        }
+
+        r = gnutls_x509_crt_init(&cert);
+        if (r < 0) {
+                log_error("Failed to initialize client certificate");
+                return r;
+        }
+
+        /* Note that by passing values between 0 and listsize here, you
+           can get access to the CA's certs */
+        r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
+        if (r < 0) {
+                log_error("Failed to import client certificate");
+                gnutls_x509_crt_deinit(cert);
+                return r;
+        }
+
+        *client_cert = cert;
+        return 0;
+}
+
+static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
+        size_t len = 0;
+        int r;
+
+        assert(buf);
+        assert(*buf == NULL);
+
+        r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
+        if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+                log_error("gnutls_x509_crt_get_dn failed");
+                return r;
+        }
+
+        *buf = malloc(len);
+        if (!*buf)
+                return log_oom();
+
+        gnutls_x509_crt_get_dn(client_cert, *buf, &len);
+        return 0;
+}
+
+int check_permissions(struct MHD_Connection *connection, int *code) {
+        const union MHD_ConnectionInfo *ci;
+        gnutls_session_t session;
+        gnutls_x509_crt_t client_cert;
+        _cleanup_free_ char *buf = NULL;
+        int r;
+
+        assert(connection);
+        assert(code);
+
+        *code = 0;
+
+        ci = MHD_get_connection_info(connection,
+                                     MHD_CONNECTION_INFO_GNUTLS_SESSION);
+        if (!ci) {
+                log_error("MHD_get_connection_info failed: session is unencrypted");
+                *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
+                                    "Encrypted connection is required");
+                return -EPERM;
+        }
+        session = ci->tls_session;
+        assert(session);
+
+        r = get_client_cert(session, &client_cert);
+        if (r < 0) {
+                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+                                    "Authorization through certificate is required");
+                return -EPERM;
+        }
+
+        r = get_auth_dn(client_cert, &buf);
+        if (r < 0) {
+                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+                                    "Failed to determine distinguished name from certificate");
+                return -EPERM;
+        }
+
+        log_info("Connection from %s", buf);
+
+        r = verify_cert_authorized(session);
+        if (r < 0) {
+                log_warning("Client is not authorized");
+                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+                                    "Client certificate not signed by recognized authority");
+        }
+        return r;
+}
+
+#else
+int check_permissions(struct MHD_Connection *connection, int *code) {
+        return -EPERM;
+}
+#endif
diff --git a/src/journal-remote/microhttpd-util.h b/src/journal-remote/microhttpd-util.h
new file mode 100644
index 0000000..4186da8
--- /dev/null
+++ b/src/journal-remote/microhttpd-util.h
@@ -0,0 +1,55 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include <stdarg.h>
+#include <microhttpd.h>
+
+#include "macro.h"
+
+void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0);
+
+/* respond_oom() must be usable with return, hence this form. */
+#define respond_oom(connection) log_oom(), mhd_respond_oom(connection)
+
+int mhd_respondf(struct MHD_Connection *connection,
+                 unsigned code,
+                 const char *format, ...) _printf_(3,4);
+
+int mhd_respond(struct MHD_Connection *connection,
+                unsigned code,
+                const char *message);
+
+int mhd_respond_oom(struct MHD_Connection *connection);
+
+int check_permissions(struct MHD_Connection *connection, int *code);
+
+#ifdef HAVE_GNUTLS
+void log_func_gnutls(int level, const char *message);
+int log_enable_gnutls_category(const char *cat);
+void log_reset_gnutls_level(void);
+
+/* This is additionally filtered by our internal log level, so it
+ * should be set fairly high to capture all potentially interesting
+ * events without overwhelming detail.
+ */
+#endif
diff --git a/src/journal/browse.html b/src/journal/browse.html
deleted file mode 100644
index 3594f70..0000000
--- a/src/journal/browse.html
+++ /dev/null
@@ -1,544 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-        <title>Journal</title>
-        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-        <style type="text/css">
-                div#divlogs, div#diventry {
-                        font-family: monospace;
-                        font-size: 7pt;
-                        background-color: #ffffff;
-                        padding: 1em;
-                        margin: 2em 0em;
-                        border-radius: 10px 10px 10px 10px;
-                        border: 1px solid threedshadow;
-                        white-space: nowrap;
-                        overflow-x: scroll;
-                }
-                div#diventry {
-                        display: none;
-                }
-                div#divlogs {
-                        display: block;
-                }
-                body {
-                        background-color: #ededed;
-                        color: #313739;
-                        font: message-box;
-                        margin: 3em;
-                }
-                td.timestamp {
-                        text-align: right;
-                        border-right: 1px dotted lightgrey;
-                        padding-right: 5px;
-                }
-                td.process {
-                        border-right: 1px dotted lightgrey;
-                        padding-left: 5px;
-                        padding-right: 5px;
-                }
-                td.message {
-                        padding-left: 5px;
-                }
-                td.message > a:link, td.message > a:visited {
-                        text-decoration: none;
-                        color: #313739;
-                }
-                td.message-error {
-                        padding-left: 5px;
-                        color: red;
-                        font-weight: bold;
-                }
-                td.message-error > a:link, td.message-error > a:visited {
-                        text-decoration: none;
-                        color: red;
-                }
-                td.message-highlight {
-                        padding-left: 5px;
-                        font-weight: bold;
-                }
-                td.message-highlight > a:link, td.message-highlight > a:visited {
-                        text-decoration: none;
-                        color: #313739;
-                }
-                td > a:hover, td > a:active {
-                        text-decoration: underline;
-                        color: #c13739;
-                }
-                table#tablelogs, table#tableentry {
-                        border-collapse: collapse;
-                }
-                td.field {
-                        text-align: right;
-                        border-right: 1px dotted lightgrey;
-                        padding-right: 5px;
-                }
-                td.data {
-                        padding-left: 5px;
-                }
-                div#keynav {
-                        text-align: center;
-                        font-size: 7pt;
-                        color: #818789;
-                        padding-top: 2em;
-                }
-                span.key {
-                        font-weight: bold;
-                        color: #313739;
-                }
-                div#buttonnav {
-                        text-align: center;
-                }
-                button {
-                        font-size: 18pt;
-                        font-weight: bold;
-                        width: 2em;
-                        height: 2em;
-                }
-                div#filternav {
-                        text-align: center;
-                }
-                select {
-                        width: 50em;
-                }
-        </style>
-</head>
-
-<body>
-        <!-- TODO:
-                - live display
-                - show red lines for reboots -->
-
-        <h1 id="title"></h1>
-
-        <div id="os"></div>
-        <div id="virtualization"></div>
-        <div id="cutoff"></div>
-        <div id="machine"></div>
-        <div id="usage"></div>
-        <div id="showing"></div>
-
-        <div id="filternav">
-                <select id="filter" onchange="onFilterChange(this);" onfocus="onFilterFocus(this);">
-                        <option>No filter</option>
-                </select>
-                    
-                <input id="boot" type="checkbox" onchange="onBootChange(this);">Only current boot</input>
-        </div>
-
-        <div id="divlogs"><table id="tablelogs"></table></div>
-        <a name="entry"></a>
-        <div id="diventry"><table id="tableentry"></table></div>
-
-        <div id="buttonnav">
-                <button id="head" onclick="entriesLoadHead();" title="First Page">⇤</button>
-                <button id="previous" type="button" onclick="entriesLoadPrevious();" title="Previous Page"/>←</button>
-                <button id="next" type="button" onclick="entriesLoadNext();" title="Next Page"/>→</button>
-                <button id="tail" type="button" onclick="entriesLoadTail();" title="Last Page"/>⇥</button>
-                    
-                <button id="more" type="button" onclick="entriesMore();" title="More Entries"/>+</button>
-                <button id="less" type="button" onclick="entriesLess();" title="Fewer Entries"/>-</button>
-        </div>
-
-        <div id="keynav">
-                <span class="key">g</span>: First Page     
-                <span class="key">←, k, BACKSPACE</span>: Previous Page     
-                <span class="key">→, j, SPACE</span>: Next Page     
-                <span class="key">G</span>: Last Page     
-                <span class="key">+</span>: More entries     
-                <span class="key">-</span>: Fewer entries
-        </div>
-
-        <script type="text/javascript">
-                var first_cursor = null;
-                var last_cursor = null;
-
-                function getNEntries() {
-                        var n;
-                        n = localStorage["n_entries"];
-                        if (n == null)
-                                return 50;
-                        n = parseInt(n);
-                        if (n < 10)
-                                return 10;
-                        if (n > 1000)
-                                return 1000;
-                        return n;
-                }
-
-                function showNEntries(n) {
-                        var showing = document.getElementById("showing");
-                        showing.innerHTML = "Showing <b>" + n.toString() + "</b> entries.";
-                }
-
-                function setNEntries(n) {
-                        if (n < 10)
-                                return 10;
-                        if (n > 1000)
-                                return 1000;
-                        localStorage["n_entries"] = n.toString();
-                        showNEntries(n);
-                }
-
-                function machineLoad() {
-                        var request = new XMLHttpRequest();
-                        request.open("GET", "/machine");
-                        request.onreadystatechange = machineOnResult;
-                        request.setRequestHeader("Accept", "application/json");
-                        request.send(null);
-                }
-
-                function formatBytes(u) {
-                        if (u >= 1024*1024*1024*1024)
-                                return (u/1024/1024/1024/1024).toFixed(1) + " TiB";
-                        else if (u >= 1024*1024*1024)
-                                return (u/1024/1024/1024).toFixed(1) + " GiB";
-                        else if (u >= 1024*1024)
-                                return (u/1024/1024).toFixed(1) + " MiB";
-                        else if (u >= 1024)
-                                return (u/1024).toFixed(1) + " KiB";
-                        else
-                                return u.toString() + " B";
-                }
-
-                function escapeHTML(s) {
-                        return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
-                }
-
-                function machineOnResult(event) {
-                        if ((event.currentTarget.readyState != 4) ||
-                                (event.currentTarget.status != 200 && event.currentTarget.status != 0))
-                                return;
-
-                        var d = JSON.parse(event.currentTarget.responseText);
-
-                        var title = document.getElementById("title");
-                        title.innerHTML = 'Journal of ' + escapeHTML(d.hostname);
-                        document.title = 'Journal of ' + escapeHTML(d.hostname);
-
-                        var machine = document.getElementById("machine");
-                        machine.innerHTML = 'Machine ID is <b>' + d.machine_id + '</b>, current boot ID is <b>' + d.boot_id + '</b>.';
-
-                        var cutoff = document.getElementById("cutoff");
-                        var from = new Date(parseInt(d.cutoff_from_realtime) / 1000);
-                        var to = new Date(parseInt(d.cutoff_to_realtime) / 1000);
-                        cutoff.innerHTML = 'Journal begins at <b>' + from.toLocaleString() + '</b> and ends at <b>' + to.toLocaleString() + '</b>.';
-
-                        var usage = document.getElementById("usage");
-                        usage.innerHTML = 'Disk usage is <b>' + formatBytes(parseInt(d.usage)) + '</b>.';
-
-                        var os = document.getElementById("os");
-                        os.innerHTML = 'Operating system is <b>' + escapeHTML(d.os_pretty_name) + '</b>.';
-
-                        var virtualization = document.getElementById("virtualization");
-                        virtualization.innerHTML = d.virtualization == "bare" ? "Running on <b>bare metal</b>." : "Running on virtualization <b>" + escapeHTML(d.virtualization) + "</b>.";
-                }
-
-                function entriesLoad(range) {
-
-                        if (range == null)
-                                range = localStorage["cursor"] + ":0";
-                        if (range == null)
-                                range = "";
-
-                        var url = "/entries";
-
-                        if (localStorage["filter"] != "" && localStorage["filter"] != null) {
-                                url += "?_SYSTEMD_UNIT=" + escape(localStorage["filter"]);
-
-                                if (localStorage["boot"] == "1")
-                                        url += "&boot";
-                        } else {
-                                if (localStorage["boot"] == "1")
-                                        url += "?boot";
-                        }
-
-                        var request = new XMLHttpRequest();
-                        request.open("GET", url);
-                        request.onreadystatechange = entriesOnResult;
-                        request.setRequestHeader("Accept", "application/json");
-                        request.setRequestHeader("Range", "entries=" + range + ":" + getNEntries().toString());
-                        request.send(null);
-                }
-
-                function entriesLoadNext() {
-                        if (last_cursor == null)
-                                entriesLoad("");
-                        else
-                                entriesLoad(last_cursor + ":1");
-                }
-
-                function entriesLoadPrevious() {
-                        if (first_cursor == null)
-                                entriesLoad("");
-                        else
-                                entriesLoad(first_cursor + ":-" + getNEntries().toString());
-                }
-
-                function entriesLoadHead() {
-                        entriesLoad("");
-                }
-
-                function entriesLoadTail() {
-                        entriesLoad(":-" + getNEntries().toString());
-                }
-
-                function entriesOnResult(event) {
-
-                        if ((event.currentTarget.readyState != 4) ||
-                                (event.currentTarget.status != 200 && event.currentTarget.status != 0))
-                                return;
-
-                        var logs = document.getElementById("tablelogs");
-
-                        var lc = null;
-                        var fc = null;
-
-                        var i, l = event.currentTarget.responseText.split('\n');
-
-                        if (l.length <= 1) {
-                                logs.innerHTML = '<tbody><tr><td colspan="3"><i>No further entries...</i></td></tr></tbody>';
-                                return;
-                        }
-
-                        var buf = '';
-
-                        for (i in l) {
-
-                                if (l[i] == '')
-                                        continue;
-
-                                var d = JSON.parse(l[i]);
-                                if (d.MESSAGE == undefined || d.__CURSOR == undefined)
-                                        continue;
-
-                                if (fc == null)
-                                        fc = d.__CURSOR;
-                                lc = d.__CURSOR;
-
-                                var priority;
-                                if (d.PRIORITY != undefined)
-                                        priority = parseInt(d.PRIORITY);
-                                else
-                                        priority = 6;
-
-                                if (priority <= 3)
-                                        clazz = "message-error";
-                                else if (priority <= 5)
-                                        clazz = "message-highlight";
-                                else
-                                        clazz = "message";
-
-                                buf += '<tr><td class="timestamp">';
-
-                                if (d.__REALTIME_TIMESTAMP != undefined) {
-                                        var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) / 1000);
-                                        buf += timestamp.toLocaleString();
-                                }
-
-                                buf += '</td><td class="process">';
-
-                                if (d.SYSLOG_IDENTIFIER != undefined)
-                                        buf += escapeHTML(d.SYSLOG_IDENTIFIER);
-                                else if (d._COMM != undefined)
-                                        buf += escapeHTML(d._COMM);
-
-                                if (d._PID != undefined)
-                                        buf += "[" + escapeHTML(d._PID) + "]";
-                                else if (d.SYSLOG_PID != undefined)
-                                        buf += "[" + escapeHTML(d.SYSLOG_PID) + "]";
-
-                                buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + d.__CURSOR + '\');">';
-
-                                if (d.MESSAGE == null)
-                                        buf += "[blob data]";
-                                else if (d.MESSAGE instanceof Array)
-                                        buf += "[" + formatBytes(d.MESSAGE.length) + " blob data]";
-                                else
-                                        buf += escapeHTML(d.MESSAGE);
-
-                                buf += '</a></td></tr>';
-                        }
-
-                        logs.innerHTML = '<tbody>' + buf + '</tbody>';
-
-                        if (fc != null) {
-                                first_cursor = fc;
-                                localStorage["cursor"] = fc;
-                        }
-                        if (lc != null)
-                                last_cursor = lc;
-                }
-
-                function entriesMore() {
-                        setNEntries(getNEntries() + 10);
-                        entriesLoad(first_cursor);
-                }
-
-                function entriesLess() {
-                        setNEntries(getNEntries() - 10);
-                        entriesLoad(first_cursor);
-                }
-
-                function onResultMessageClick(event) {
-                        if ((event.currentTarget.readyState != 4) ||
-                                (event.currentTarget.status != 200 && event.currentTarget.status != 0))
-                                return;
-
-                        var d = JSON.parse(event.currentTarget.responseText);
-
-                        document.getElementById("diventry").style.display = "block";
-                        entry = document.getElementById("tableentry");
-
-                        var buf = "";
-                        for (var key in d){
-                                var data = d[key];
-
-                                if (data == null)
-                                        data = "[blob data]";
-                                else if (data instanceof Array)
-                                        data = "[" + formatBytes(data.length) + " blob data]";
-                                else
-                                        data = escapeHTML(data);
-
-                                buf += '<tr><td class="field">' + key + '</td><td class="data">' + data + '</td></tr>';
-                        }
-                        entry.innerHTML = '<tbody>' + buf + '</tbody>';
-                }
-
-                function onMessageClick(t) {
-                        var request = new XMLHttpRequest();
-                        request.open("GET", "/entries?discrete");
-                        request.onreadystatechange = onResultMessageClick;
-                        request.setRequestHeader("Accept", "application/json");
-                        request.setRequestHeader("Range", "entries=" + t + ":0:1");
-                        request.send(null);
-                }
-
-                function onKeyUp(event) {
-                        switch (event.keyCode) {
-                                case 8:
-                                case 37:
-                                case 75:
-                                        entriesLoadPrevious();
-                                        break;
-                                case 32:
-                                case 39:
-                                case 74:
-                                        entriesLoadNext();
-                                        break;
-
-                                case 71:
-                                        if (event.shiftKey)
-                                                entriesLoadTail();
-                                        else
-                                                entriesLoadHead();
-                                        break;
-                                case 171:
-                                        entriesMore();
-                                        break;
-                                case 173:
-                                        entriesLess();
-                                        break;
-                        }
-                }
-
-                function onMouseWheel(event) {
-                        if (event.detail < 0 || event.wheelDelta > 0)
-                                entriesLoadPrevious();
-                        else
-                                entriesLoadNext();
-                }
-
-                function onResultFilterFocus(event) {
-                        if ((event.currentTarget.readyState != 4) ||
-                                (event.currentTarget.status != 200 && event.currentTarget.status != 0))
-                                return;
-
-                        f = document.getElementById("filter");
-
-                        var l = event.currentTarget.responseText.split('\n');
-                        var buf = '<option>No filter</option>';
-                        var j = -1;
-
-                        for (i in l) {
-
-                                if (l[i] == '')
-                                      continue;
-
-                                var d = JSON.parse(l[i]);
-                                if (d._SYSTEMD_UNIT == undefined)
-                                      continue;
-
-                                buf += '<option value="' + escape(d._SYSTEMD_UNIT) + '">' + escapeHTML(d._SYSTEMD_UNIT) + '</option>';
-
-                                if (d._SYSTEMD_UNIT == localStorage["filter"])
-                                        j = i;
-                        }
-
-                        if (j < 0) {
-                                if (localStorage["filter"] != null && localStorage["filter"] != "") {
-                                          buf += '<option value="' + escape(localStorage["filter"]) + '">' + escapeHTML(localStorage["filter"]) + '</option>';
-                                          j = i + 1;
-                                } else
-                                          j = 0;
-                        }
-
-                        f.innerHTML = buf;
-                        f.selectedIndex = j;
-                }
-
-                function onFilterFocus(w) {
-                        var request = new XMLHttpRequest();
-                        request.open("GET", "/fields/_SYSTEMD_UNIT");
-                        request.onreadystatechange = onResultFilterFocus;
-                        request.setRequestHeader("Accept", "application/json");
-                        request.send(null);
-                }
-
-                function onFilterChange(w) {
-                        if (w.selectedIndex <= 0)
-                                localStorage["filter"] = "";
-                        else
-                                localStorage["filter"] = unescape(w.options[w.selectedIndex].value);
-
-                        entriesLoadHead();
-                }
-
-                function onBootChange(w) {
-                        localStorage["boot"] = w.checked ? "1" : "0";
-                        entriesLoadHead();
-                }
-
-                function initFilter() {
-                        f = document.getElementById("filter");
-
-                        var buf = '<option>No filter</option>';
-
-                        var filter = localStorage["filter"];
-                        if (filter != null && filter != "") {
-                                buf += '<option value="' + escape(filter) + '">' + escapeHTML(filter) + '</option>';
-                                j = 1;
-                        } else
-                                j = 0;
-
-                        f.innerHTML = buf;
-                        f.selectedIndex = j;
-                }
-
-                function installHandlers() {
-                        document.onkeyup = onKeyUp;
-
-                        logs = document.getElementById("divlogs");
-                        logs.addEventListener("mousewheel", onMouseWheel, false);
-                        logs.addEventListener("DOMMouseScroll", onMouseWheel, false);
-                }
-
-                machineLoad();
-                entriesLoad(null);
-                showNEntries(getNEntries());
-                initFilter();
-                installHandlers();
-        </script>
-</body>
-</html>
diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
deleted file mode 100644
index db07700..0000000
--- a/src/journal/journal-gatewayd.c
+++ /dev/null
@@ -1,1054 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2012 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <getopt.h>
-
-#include <microhttpd.h>
-
-#ifdef HAVE_GNUTLS
-#include <gnutls/gnutls.h>
-#endif
-
-#include "log.h"
-#include "util.h"
-#include "sd-journal.h"
-#include "sd-daemon.h"
-#include "sd-bus.h"
-#include "bus-util.h"
-#include "logs-show.h"
-#include "microhttpd-util.h"
-#include "build.h"
-#include "fileio.h"
-
-static char *key_pem = NULL;
-static char *cert_pem = NULL;
-static char *trust_pem = NULL;
-
-typedef struct RequestMeta {
-        sd_journal *journal;
-
-        OutputMode mode;
-
-        char *cursor;
-        int64_t n_skip;
-        uint64_t n_entries;
-        bool n_entries_set;
-
-        FILE *tmp;
-        uint64_t delta, size;
-
-        int argument_parse_error;
-
-        bool follow;
-        bool discrete;
-
-        uint64_t n_fields;
-        bool n_fields_set;
-} RequestMeta;
-
-static const char* const mime_types[_OUTPUT_MODE_MAX] = {
-        [OUTPUT_SHORT] = "text/plain",
-        [OUTPUT_JSON] = "application/json",
-        [OUTPUT_JSON_SSE] = "text/event-stream",
-        [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
-};
-
-static RequestMeta *request_meta(void **connection_cls) {
-        RequestMeta *m;
-
-        assert(connection_cls);
-        if (*connection_cls)
-                return *connection_cls;
-
-        m = new0(RequestMeta, 1);
-        if (!m)
-                return NULL;
-
-        *connection_cls = m;
-        return m;
-}
-
-static void request_meta_free(
-                void *cls,
-                struct MHD_Connection *connection,
-                void **connection_cls,
-                enum MHD_RequestTerminationCode toe) {
-
-        RequestMeta *m = *connection_cls;
-
-        if (!m)
-                return;
-
-        if (m->journal)
-                sd_journal_close(m->journal);
-
-        if (m->tmp)
-                fclose(m->tmp);
-
-        free(m->cursor);
-        free(m);
-}
-
-static int open_journal(RequestMeta *m) {
-        assert(m);
-
-        if (m->journal)
-                return 0;
-
-        return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
-}
-
-static ssize_t request_reader_entries(
-                void *cls,
-                uint64_t pos,
-                char *buf,
-                size_t max) {
-
-        RequestMeta *m = cls;
-        int r;
-        size_t n, k;
-
-        assert(m);
-        assert(buf);
-        assert(max > 0);
-        assert(pos >= m->delta);
-
-        pos -= m->delta;
-
-        while (pos >= m->size) {
-                off_t sz;
-
-                /* End of this entry, so let's serialize the next
-                 * one */
-
-                if (m->n_entries_set &&
-                    m->n_entries <= 0)
-                        return MHD_CONTENT_READER_END_OF_STREAM;
-
-                if (m->n_skip < 0)
-                        r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
-                else if (m->n_skip > 0)
-                        r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
-                else
-                        r = sd_journal_next(m->journal);
-
-                if (r < 0) {
-                        log_error("Failed to advance journal pointer: %s", strerror(-r));
-                        return MHD_CONTENT_READER_END_WITH_ERROR;
-                } else if (r == 0) {
-
-                        if (m->follow) {
-                                r = sd_journal_wait(m->journal, (uint64_t) -1);
-                                if (r < 0) {
-                                        log_error("Couldn't wait for journal event: %s", strerror(-r));
-                                        return MHD_CONTENT_READER_END_WITH_ERROR;
-                                }
-
-                                continue;
-                        }
-
-                        return MHD_CONTENT_READER_END_OF_STREAM;
-                }
-
-                if (m->discrete) {
-                        assert(m->cursor);
-
-                        r = sd_journal_test_cursor(m->journal, m->cursor);
-                        if (r < 0) {
-                                log_error("Failed to test cursor: %s", strerror(-r));
-                                return MHD_CONTENT_READER_END_WITH_ERROR;
-                        }
-
-                        if (r == 0)
-                                return MHD_CONTENT_READER_END_OF_STREAM;
-                }
-
-                pos -= m->size;
-                m->delta += m->size;
-
-                if (m->n_entries_set)
-                        m->n_entries -= 1;
-
-                m->n_skip = 0;
-
-                if (m->tmp)
-                        rewind(m->tmp);
-                else {
-                        m->tmp = tmpfile();
-                        if (!m->tmp) {
-                                log_error("Failed to create temporary file: %m");
-                                return MHD_CONTENT_READER_END_WITH_ERROR;
-                        }
-                }
-
-                r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
-                if (r < 0) {
-                        log_error("Failed to serialize item: %s", strerror(-r));
-                        return MHD_CONTENT_READER_END_WITH_ERROR;
-                }
-
-                sz = ftello(m->tmp);
-                if (sz == (off_t) -1) {
-                        log_error("Failed to retrieve file position: %m");
-                        return MHD_CONTENT_READER_END_WITH_ERROR;
-                }
-
-                m->size = (uint64_t) sz;
-        }
-
-        if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
-                log_error("Failed to seek to position: %m");
-                return MHD_CONTENT_READER_END_WITH_ERROR;
-        }
-
-        n = m->size - pos;
-        if (n > max)
-                n = max;
-
-        errno = 0;
-        k = fread(buf, 1, n, m->tmp);
-        if (k != n) {
-                log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
-                return MHD_CONTENT_READER_END_WITH_ERROR;
-        }
-
-        return (ssize_t) k;
-}
-
-static int request_parse_accept(
-                RequestMeta *m,
-                struct MHD_Connection *connection) {
-
-        const char *header;
-
-        assert(m);
-        assert(connection);
-
-        header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
-        if (!header)
-                return 0;
-
-        if (streq(header, mime_types[OUTPUT_JSON]))
-                m->mode = OUTPUT_JSON;
-        else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
-                m->mode = OUTPUT_JSON_SSE;
-        else if (streq(header, mime_types[OUTPUT_EXPORT]))
-                m->mode = OUTPUT_EXPORT;
-        else
-                m->mode = OUTPUT_SHORT;
-
-        return 0;
-}
-
-static int request_parse_range(
-                RequestMeta *m,
-                struct MHD_Connection *connection) {
-
-        const char *range, *colon, *colon2;
-        int r;
-
-        assert(m);
-        assert(connection);
-
-        range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
-        if (!range)
-                return 0;
-
-        if (!startswith(range, "entries="))
-                return 0;
-
-        range += 8;
-        range += strspn(range, WHITESPACE);
-
-        colon = strchr(range, ':');
-        if (!colon)
-                m->cursor = strdup(range);
-        else {
-                const char *p;
-
-                colon2 = strchr(colon + 1, ':');
-                if (colon2) {
-                        _cleanup_free_ char *t;
-
-                        t = strndup(colon + 1, colon2 - colon - 1);
-                        if (!t)
-                                return -ENOMEM;
-
-                        r = safe_atoi64(t, &m->n_skip);
-                        if (r < 0)
-                                return r;
-                }
-
-                p = (colon2 ? colon2 : colon) + 1;
-                if (*p) {
-                        r = safe_atou64(p, &m->n_entries);
-                        if (r < 0)
-                                return r;
-
-                        if (m->n_entries <= 0)
-                                return -EINVAL;
-
-                        m->n_entries_set = true;
-                }
-
-                m->cursor = strndup(range, colon - range);
-        }
-
-        if (!m->cursor)
-                return -ENOMEM;
-
-        m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
-        if (isempty(m->cursor)) {
-                free(m->cursor);
-                m->cursor = NULL;
-        }
-
-        return 0;
-}
-
-static int request_parse_arguments_iterator(
-                void *cls,
-                enum MHD_ValueKind kind,
-                const char *key,
-                const char *value) {
-
-        RequestMeta *m = cls;
-        _cleanup_free_ char *p = NULL;
-        int r;
-
-        assert(m);
-
-        if (isempty(key)) {
-                m->argument_parse_error = -EINVAL;
-                return MHD_NO;
-        }
-
-        if (streq(key, "follow")) {
-                if (isempty(value)) {
-                        m->follow = true;
-                        return MHD_YES;
-                }
-
-                r = parse_boolean(value);
-                if (r < 0) {
-                        m->argument_parse_error = r;
-                        return MHD_NO;
-                }
-
-                m->follow = r;
-                return MHD_YES;
-        }
-
-        if (streq(key, "discrete")) {
-                if (isempty(value)) {
-                        m->discrete = true;
-                        return MHD_YES;
-                }
-
-                r = parse_boolean(value);
-                if (r < 0) {
-                        m->argument_parse_error = r;
-                        return MHD_NO;
-                }
-
-                m->discrete = r;
-                return MHD_YES;
-        }
-
-        if (streq(key, "boot")) {
-                if (isempty(value))
-                        r = true;
-                else {
-                        r = parse_boolean(value);
-                        if (r < 0) {
-                                m->argument_parse_error = r;
-                                return MHD_NO;
-                        }
-                }
-
-                if (r) {
-                        char match[9 + 32 + 1] = "_BOOT_ID=";
-                        sd_id128_t bid;
-
-                        r = sd_id128_get_boot(&bid);
-                        if (r < 0) {
-                                log_error("Failed to get boot ID: %s", strerror(-r));
-                                return MHD_NO;
-                        }
-
-                        sd_id128_to_string(bid, match + 9);
-                        r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
-                        if (r < 0) {
-                                m->argument_parse_error = r;
-                                return MHD_NO;
-                        }
-                }
-
-                return MHD_YES;
-        }
-
-        p = strjoin(key, "=", strempty(value), NULL);
-        if (!p) {
-                m->argument_parse_error = log_oom();
-                return MHD_NO;
-        }
-
-        r = sd_journal_add_match(m->journal, p, 0);
-        if (r < 0) {
-                m->argument_parse_error = r;
-                return MHD_NO;
-        }
-
-        return MHD_YES;
-}
-
-static int request_parse_arguments(
-                RequestMeta *m,
-                struct MHD_Connection *connection) {
-
-        assert(m);
-        assert(connection);
-
-        m->argument_parse_error = 0;
-        MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
-
-        return m->argument_parse_error;
-}
-
-static int request_handler_entries(
-                struct MHD_Connection *connection,
-                void *connection_cls) {
-
-        struct MHD_Response *response;
-        RequestMeta *m = connection_cls;
-        int r;
-
-        assert(connection);
-        assert(m);
-
-        r = open_journal(m);
-        if (r < 0)
-                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
-
-        if (request_parse_accept(m, connection) < 0)
-                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
-
-        if (request_parse_range(m, connection) < 0)
-                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
-
-        if (request_parse_arguments(m, connection) < 0)
-                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
-
-        if (m->discrete) {
-                if (!m->cursor)
-                        return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
-
-                m->n_entries = 1;
-                m->n_entries_set = true;
-        }
-
-        if (m->cursor)
-                r = sd_journal_seek_cursor(m->journal, m->cursor);
-        else if (m->n_skip >= 0)
-                r = sd_journal_seek_head(m->journal);
-        else if (m->n_skip < 0)
-                r = sd_journal_seek_tail(m->journal);
-        if (r < 0)
-                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
-
-        response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
-        if (!response)
-                return respond_oom(connection);
-
-        MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
-
-        r = MHD_queue_response(connection, MHD_HTTP_OK, response);
-        MHD_destroy_response(response);
-
-        return r;
-}
-
-static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
-        const char *eq;
-        size_t j;
-
-        eq = memchr(d, '=', l);
-        if (!eq)
-                return -EINVAL;
-
-        j = l - (eq - d + 1);
-
-        if (m == OUTPUT_JSON) {
-                fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
-                json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
-                fputs(" }\n", f);
-        } else {
-                fwrite(eq+1, 1, j, f);
-                fputc('\n', f);
-        }
-
-        return 0;
-}
-
-static ssize_t request_reader_fields(
-                void *cls,
-                uint64_t pos,
-                char *buf,
-                size_t max) {
-
-        RequestMeta *m = cls;
-        int r;
-        size_t n, k;
-
-        assert(m);
-        assert(buf);
-        assert(max > 0);
-        assert(pos >= m->delta);
-
-        pos -= m->delta;
-
-        while (pos >= m->size) {
-                off_t sz;
-                const void *d;
-                size_t l;
-
-                /* End of this field, so let's serialize the next
-                 * one */
-
-                if (m->n_fields_set &&
-                    m->n_fields <= 0)
-                        return MHD_CONTENT_READER_END_OF_STREAM;
-
-                r = sd_journal_enumerate_unique(m->journal, &d, &l);
-                if (r < 0) {
-                        log_error("Failed to advance field index: %s", strerror(-r));
-                        return MHD_CONTENT_READER_END_WITH_ERROR;
-                } else if (r == 0)
-                        return MHD_CONTENT_READER_END_OF_STREAM;
-
-                pos -= m->size;
-                m->delta += m->size;
-
-                if (m->n_fields_set)
-                        m->n_fields -= 1;
-
-                if (m->tmp)
-                        rewind(m->tmp);
-                else {
-                        m->tmp = tmpfile();
-                        if (!m->tmp) {
-                                log_error("Failed to create temporary file: %m");
-                                return MHD_CONTENT_READER_END_WITH_ERROR;
-                        }
-                }
-
-                r = output_field(m->tmp, m->mode, d, l);
-                if (r < 0) {
-                        log_error("Failed to serialize item: %s", strerror(-r));
-                        return MHD_CONTENT_READER_END_WITH_ERROR;
-                }
-
-                sz = ftello(m->tmp);
-                if (sz == (off_t) -1) {
-                        log_error("Failed to retrieve file position: %m");
-                        return MHD_CONTENT_READER_END_WITH_ERROR;
-                }
-
-                m->size = (uint64_t) sz;
-        }
-
-        if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
-                log_error("Failed to seek to position: %m");
-                return MHD_CONTENT_READER_END_WITH_ERROR;
-        }
-
-        n = m->size - pos;
-        if (n > max)
-                n = max;
-
-        errno = 0;
-        k = fread(buf, 1, n, m->tmp);
-        if (k != n) {
-                log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
-                return MHD_CONTENT_READER_END_WITH_ERROR;
-        }
-
-        return (ssize_t) k;
-}
-
-static int request_handler_fields(
-                struct MHD_Connection *connection,
-                const char *field,
-                void *connection_cls) {
-
-        struct MHD_Response *response;
-        RequestMeta *m = connection_cls;
-        int r;
-
-        assert(connection);
-        assert(m);
-
-        r = open_journal(m);
-        if (r < 0)
-                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
-
-        if (request_parse_accept(m, connection) < 0)
-                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
-
-        r = sd_journal_query_unique(m->journal, field);
-        if (r < 0)
-                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
-
-        response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
-        if (!response)
-                return respond_oom(connection);
-
-        MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
-
-        r = MHD_queue_response(connection, MHD_HTTP_OK, response);
-        MHD_destroy_response(response);
-
-        return r;
-}
-
-static int request_handler_redirect(
-                struct MHD_Connection *connection,
-                const char *target) {
-
-        char *page;
-        struct MHD_Response *response;
-        int ret;
-
-        assert(connection);
-        assert(target);
-
-        if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
-                return respond_oom(connection);
-
-        response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
-        if (!response) {
-                free(page);
-                return respond_oom(connection);
-        }
-
-        MHD_add_response_header(response, "Content-Type", "text/html");
-        MHD_add_response_header(response, "Location", target);
-
-        ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
-        MHD_destroy_response(response);
-
-        return ret;
-}
-
-static int request_handler_file(
-                struct MHD_Connection *connection,
-                const char *path,
-                const char *mime_type) {
-
-        struct MHD_Response *response;
-        int ret;
-        _cleanup_close_ int fd = -1;
-        struct stat st;
-
-        assert(connection);
-        assert(path);
-        assert(mime_type);
-
-        fd = open(path, O_RDONLY|O_CLOEXEC);
-        if (fd < 0)
-                return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
-
-        if (fstat(fd, &st) < 0)
-                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
-
-        response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
-        if (!response)
-                return respond_oom(connection);
-
-        fd = -1;
-
-        MHD_add_response_header(response, "Content-Type", mime_type);
-
-        ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
-        MHD_destroy_response(response);
-
-        return ret;
-}
-
-static int get_virtualization(char **v) {
-        _cleanup_bus_unref_ sd_bus *bus = NULL;
-        char *b = NULL;
-        int r;
-
-        r = sd_bus_default_system(&bus);
-        if (r < 0)
-                return r;
-
-        r = sd_bus_get_property_string(
-                        bus,
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        "Virtualization",
-                        NULL,
-                        &b);
-        if (r < 0)
-                return r;
-
-        if (isempty(b)) {
-                free(b);
-                *v = NULL;
-                return 0;
-        }
-
-        *v = b;
-        return 1;
-}
-
-static int request_handler_machine(
-                struct MHD_Connection *connection,
-                void *connection_cls) {
-
-        struct MHD_Response *response;
-        RequestMeta *m = connection_cls;
-        int r;
-        _cleanup_free_ char* hostname = NULL, *os_name = NULL;
-        uint64_t cutoff_from = 0, cutoff_to = 0, usage;
-        char *json;
-        sd_id128_t mid, bid;
-        _cleanup_free_ char *v = NULL;
-
-        assert(connection);
-        assert(m);
-
-        r = open_journal(m);
-        if (r < 0)
-                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
-
-        r = sd_id128_get_machine(&mid);
-        if (r < 0)
-                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
-
-        r = sd_id128_get_boot(&bid);
-        if (r < 0)
-                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
-
-        hostname = gethostname_malloc();
-        if (!hostname)
-                return respond_oom(connection);
-
-        r = sd_journal_get_usage(m->journal, &usage);
-        if (r < 0)
-                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
-
-        r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
-        if (r < 0)
-                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
-
-        if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
-                parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
-
-        get_virtualization(&v);
-
-        r = asprintf(&json,
-                     "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
-                     "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
-                     "\"hostname\" : \"%s\","
-                     "\"os_pretty_name\" : \"%s\","
-                     "\"virtualization\" : \"%s\","
-                     "\"usage\" : \"%"PRIu64"\","
-                     "\"cutoff_from_realtime\" : \"%"PRIu64"\","
-                     "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
-                     SD_ID128_FORMAT_VAL(mid),
-                     SD_ID128_FORMAT_VAL(bid),
-                     hostname_cleanup(hostname, false),
-                     os_name ? os_name : "Linux",
-                     v ? v : "bare",
-                     usage,
-                     cutoff_from,
-                     cutoff_to);
-
-        if (r < 0)
-                return respond_oom(connection);
-
-        response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
-        if (!response) {
-                free(json);
-                return respond_oom(connection);
-        }
-
-        MHD_add_response_header(response, "Content-Type", "application/json");
-        r = MHD_queue_response(connection, MHD_HTTP_OK, response);
-        MHD_destroy_response(response);
-
-        return r;
-}
-
-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) {
-        int r, code;
-
-        assert(connection);
-        assert(connection_cls);
-        assert(url);
-        assert(method);
-
-        if (!streq(method, "GET"))
-                return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
-                                   "Unsupported method.\n");
-
-
-        if (!*connection_cls) {
-                if (!request_meta(connection_cls))
-                        return respond_oom(connection);
-                return MHD_YES;
-        }
-
-        if (trust_pem) {
-                r = check_permissions(connection, &code);
-                if (r < 0)
-                        return code;
-        }
-
-        if (streq(url, "/"))
-                return request_handler_redirect(connection, "/browse");
-
-        if (streq(url, "/entries"))
-                return request_handler_entries(connection, *connection_cls);
-
-        if (startswith(url, "/fields/"))
-                return request_handler_fields(connection, url + 8, *connection_cls);
-
-        if (streq(url, "/browse"))
-                return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
-
-        if (streq(url, "/machine"))
-                return request_handler_machine(connection, *connection_cls);
-
-        return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
-}
-
-static int help(void) {
-
-        printf("%s [OPTIONS...] ...\n\n"
-               "HTTP server for journal events.\n\n"
-               "  -h --help           Show this help\n"
-               "     --version        Show package version\n"
-               "     --cert=CERT.PEM  Server certificate in PEM format\n"
-               "     --key=KEY.PEM    Server key in PEM format\n"
-               "     --trust=CERT.PEM Certificat authority certificate in PEM format\n",
-               program_invocation_short_name);
-
-        return 0;
-}
-
-static int parse_argv(int argc, char *argv[]) {
-        enum {
-                ARG_VERSION = 0x100,
-                ARG_KEY,
-                ARG_CERT,
-                ARG_TRUST,
-        };
-
-        int r, c;
-
-        static const struct option options[] = {
-                { "help",    no_argument,       NULL, 'h'         },
-                { "version", no_argument,       NULL, ARG_VERSION },
-                { "key",     required_argument, NULL, ARG_KEY     },
-                { "cert",    required_argument, NULL, ARG_CERT    },
-                { "trust",   required_argument, NULL, ARG_TRUST   },
-                {}
-        };
-
-        assert(argc >= 0);
-        assert(argv);
-
-        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
-
-                switch(c) {
-
-                case 'h':
-                        return help();
-
-                case ARG_VERSION:
-                        puts(PACKAGE_STRING);
-                        puts(SYSTEMD_FEATURES);
-                        return 0;
-
-                case ARG_KEY:
-                        if (key_pem) {
-                                log_error("Key file specified twice");
-                                return -EINVAL;
-                        }
-                        r = read_full_file(optarg, &key_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read key file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(key_pem);
-                        break;
-
-                case ARG_CERT:
-                        if (cert_pem) {
-                                log_error("Certificate file specified twice");
-                                return -EINVAL;
-                        }
-                        r = read_full_file(optarg, &cert_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read certificate file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(cert_pem);
-                        break;
-
-                case ARG_TRUST:
-#ifdef HAVE_GNUTLS
-                        if (trust_pem) {
-                                log_error("CA certificate file specified twice");
-                                return -EINVAL;
-                        }
-                        r = read_full_file(optarg, &trust_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read CA certificate file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(trust_pem);
-                        break;
-#else
-                        log_error("Option --trust is not available.");
-#endif
-
-                case '?':
-                        return -EINVAL;
-
-                default:
-                        assert_not_reached("Unhandled option");
-                }
-
-        if (optind < argc) {
-                log_error("This program does not take arguments.");
-                return -EINVAL;
-        }
-
-        if (!!key_pem != !!cert_pem) {
-                log_error("Certificate and key files must be specified together");
-                return -EINVAL;
-        }
-
-        if (trust_pem && !key_pem) {
-                log_error("CA certificate can only be used with certificate file");
-                return -EINVAL;
-        }
-
-        return 1;
-}
-
-int main(int argc, char *argv[]) {
-        struct MHD_Daemon *d = NULL;
-        int r, n;
-
-        log_set_target(LOG_TARGET_AUTO);
-        log_parse_environment();
-        log_open();
-
-        r = parse_argv(argc, argv);
-        if (r < 0)
-                return EXIT_FAILURE;
-        if (r == 0)
-                return EXIT_SUCCESS;
-
-#ifdef HAVE_GNUTLS
-        gnutls_global_set_log_function(log_func_gnutls);
-        log_reset_gnutls_level();
-#endif
-
-        n = sd_listen_fds(1);
-        if (n < 0) {
-                log_error("Failed to determine passed sockets: %s", strerror(-n));
-                goto finish;
-        } else if (n > 1) {
-                log_error("Can't listen on more than one socket.");
-                goto finish;
-        } else {
-                struct MHD_OptionItem opts[] = {
-                        { MHD_OPTION_NOTIFY_COMPLETED,
-                          (intptr_t) request_meta_free, NULL },
-                        { MHD_OPTION_EXTERNAL_LOGGER,
-                          (intptr_t) microhttpd_logger, NULL },
-                        { MHD_OPTION_END, 0, NULL },
-                        { MHD_OPTION_END, 0, NULL },
-                        { MHD_OPTION_END, 0, NULL },
-                        { MHD_OPTION_END, 0, NULL },
-                        { MHD_OPTION_END, 0, NULL }};
-                int opts_pos = 2;
-                int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
-
-                if (n > 0)
-                        opts[opts_pos++] = (struct MHD_OptionItem)
-                                {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
-                if (key_pem) {
-                        assert(cert_pem);
-                        opts[opts_pos++] = (struct MHD_OptionItem)
-                                {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
-                        opts[opts_pos++] = (struct MHD_OptionItem)
-                                {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
-                        flags |= MHD_USE_SSL;
-                }
-                if (trust_pem) {
-                        assert(flags & MHD_USE_SSL);
-                        opts[opts_pos++] = (struct MHD_OptionItem)
-                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
-                }
-
-                d = MHD_start_daemon(flags, 19531,
-                                     NULL, NULL,
-                                     request_handler, NULL,
-                                     MHD_OPTION_ARRAY, opts,
-                                     MHD_OPTION_END);
-        }
-
-        if (!d) {
-                log_error("Failed to start daemon!");
-                goto finish;
-        }
-
-        pause();
-
-        r = EXIT_SUCCESS;
-
-finish:
-        if (d)
-                MHD_stop_daemon(d);
-
-        return r;
-}
diff --git a/src/journal/journal-remote-parse.c b/src/journal/journal-remote-parse.c
deleted file mode 100644
index dbdf02a..0000000
--- a/src/journal/journal-remote-parse.c
+++ /dev/null
@@ -1,439 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Zbigniew Jędrzejewski-Szmek
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include "journal-remote-parse.h"
-#include "journald-native.h"
-
-#define LINE_CHUNK 1024u
-
-void source_free(RemoteSource *source) {
-        if (!source)
-                return;
-
-        if (source->fd >= 0) {
-                log_debug("Closing fd:%d (%s)", source->fd, source->name);
-                close(source->fd);
-        }
-        free(source->name);
-        free(source->buf);
-        iovw_free_contents(&source->iovw);
-        free(source);
-}
-
-static int get_line(RemoteSource *source, char **line, size_t *size) {
-        ssize_t n, remain;
-        char *c = NULL;
-        char *newbuf = NULL;
-        size_t newsize = 0;
-
-        assert(source);
-        assert(source->state == STATE_LINE);
-        assert(source->filled <= source->size);
-        assert(source->buf == NULL || source->size > 0);
-
-        if (source->buf)
-                c = memchr(source->buf, '\n', source->filled);
-
-        if (c != NULL)
-                goto docopy;
-
- resize:
-        if (source->fd < 0)
-                /* we have to wait for some data to come to us */
-                return -EWOULDBLOCK;
-
-        if (source->size - source->filled < LINE_CHUNK) {
-                // XXX: add check for maximum line length
-
-                if (!GREEDY_REALLOC(source->buf, source->size,
-                                    source->filled + LINE_CHUNK))
-                        return log_oom();
-        }
-        assert(source->size - source->filled >= LINE_CHUNK);
-
-        n = read(source->fd, source->buf + source->filled,
-                 source->size - source->filled);
-        if (n < 0) {
-                if (errno != EAGAIN && errno != EWOULDBLOCK)
-                        log_error("read(%d, ..., %zd): %m", source->fd,
-                                  source->size - source->filled);
-                return -errno;
-        } else if (n == 0)
-                return 0;
-
-        c = memchr(source->buf + source->filled, '\n', n);
-        source->filled += n;
-
-        if (c == NULL)
-                goto resize;
-
- docopy:
-        *line = source->buf;
-        *size = c + 1 - source->buf;
-
-        /* Check if something remains */
-        remain = source->buf + source->filled - c - 1;
-        assert(remain >= 0);
-        if (remain) {
-                newsize = MAX(remain, LINE_CHUNK);
-                newbuf = malloc(newsize);
-                if (!newbuf)
-                        return log_oom();
-                memcpy(newbuf, c + 1, remain);
-        }
-        source->buf = newbuf;
-        source->size = newsize;
-        source->filled = remain;
-
-        return 1;
-}
-
-int push_data(RemoteSource *source, const char *data, size_t size) {
-        assert(source);
-        assert(source->state != STATE_EOF);
-
-        if (!GREEDY_REALLOC(source->buf, source->size,
-                            source->filled + size))
-                return log_oom();
-
-        memcpy(source->buf + source->filled, data, size);
-        source->filled += size;
-
-        return 0;
-}
-
-static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
-        int n;
-        char *newbuf = NULL;
-        size_t newsize = 0, remain;
-
-        assert(source);
-        assert(source->state == STATE_DATA_START ||
-               source->state == STATE_DATA ||
-               source->state == STATE_DATA_FINISH);
-        assert(size <= DATA_SIZE_MAX);
-        assert(source->filled <= source->size);
-        assert(source->buf != NULL || source->size == 0);
-        assert(source->buf == NULL || source->size > 0);
-        assert(data);
-
-        while(source->filled < size) {
-                if (source->fd < 0)
-                        /* we have to wait for some data to come to us */
-                        return -EWOULDBLOCK;
-
-                if (!GREEDY_REALLOC(source->buf, source->size, size))
-                        return log_oom();
-
-                n = read(source->fd, source->buf + source->filled,
-                         source->size - source->filled);
-                if (n < 0) {
-                        if (errno != EAGAIN && errno != EWOULDBLOCK)
-                                log_error("read(%d, ..., %zd): %m", source->fd,
-                                          source->size - source->filled);
-                        return -errno;
-                } else if (n == 0)
-                        return 0;
-
-                source->filled += n;
-        }
-
-        *data = source->buf;
-
-        /* Check if something remains */
-        assert(size <= source->filled);
-        remain = source->filled - size;
-        if (remain) {
-                newsize = MAX(remain, LINE_CHUNK);
-                newbuf = malloc(newsize);
-                if (!newbuf)
-                        return log_oom();
-                memcpy(newbuf, source->buf + size, remain);
-        }
-        source->buf = newbuf;
-        source->size = newsize;
-        source->filled = remain;
-
-        return 1;
-}
-
-static int get_data_size(RemoteSource *source) {
-        int r;
-        _cleanup_free_ void *data = NULL;
-
-        assert(source);
-        assert(source->state == STATE_DATA_START);
-        assert(source->data_size == 0);
-
-        r = fill_fixed_size(source, &data, sizeof(uint64_t));
-        if (r <= 0)
-                return r;
-
-        source->data_size = le64toh( *(uint64_t *) data );
-        if (source->data_size > DATA_SIZE_MAX) {
-                log_error("Stream declares field with size %zu > %u == DATA_SIZE_MAX",
-                          source->data_size, DATA_SIZE_MAX);
-                return -EINVAL;
-        }
-        if (source->data_size == 0)
-                log_warning("Binary field with zero length");
-
-        return 1;
-}
-
-static int get_data_data(RemoteSource *source, void **data) {
-        int r;
-
-        assert(source);
-        assert(data);
-        assert(source->state == STATE_DATA);
-
-        r = fill_fixed_size(source, data, source->data_size);
-        if (r <= 0)
-                return r;
-
-        return 1;
-}
-
-static int get_data_newline(RemoteSource *source) {
-        int r;
-        _cleanup_free_ char *data = NULL;
-
-        assert(source);
-        assert(source->state == STATE_DATA_FINISH);
-
-        r = fill_fixed_size(source, (void**) &data, 1);
-        if (r <= 0)
-                return r;
-
-        assert(data);
-        if (*data != '\n') {
-                log_error("expected newline, got '%c'", *data);
-                return -EINVAL;
-        }
-
-        return 1;
-}
-
-static int process_dunder(RemoteSource *source, char *line, size_t n) {
-        const char *timestamp;
-        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)
-                /* ignore __CURSOR */
-                return 1;
-
-        timestamp = startswith(line, "__REALTIME_TIMESTAMP=");
-        if (timestamp) {
-                long long unsigned x;
-                line[n-1] = '\0';
-                r = safe_atollu(timestamp, &x);
-                if (r < 0)
-                        log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp);
-                else
-                        source->ts.realtime = x;
-                return r < 0 ? r : 1;
-        }
-
-        timestamp = startswith(line, "__MONOTONIC_TIMESTAMP=");
-        if (timestamp) {
-                long long unsigned x;
-                line[n-1] = '\0';
-                r = safe_atollu(timestamp, &x);
-                if (r < 0)
-                        log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp);
-                else
-                        source->ts.monotonic = x;
-                return r < 0 ? r : 1;
-        }
-
-        timestamp = startswith(line, "__");
-        if (timestamp) {
-                log_notice("Unknown dunder line %s", line);
-                return 1;
-        }
-
-        /* no dunder */
-        return 0;
-}
-
-int process_data(RemoteSource *source) {
-        int r;
-
-        switch(source->state) {
-        case STATE_LINE: {
-                char *line, *sep;
-                size_t n;
-
-                assert(source->data_size == 0);
-
-                r = get_line(source, &line, &n);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        source->state = STATE_EOF;
-                        return r;
-                }
-                assert(n > 0);
-                assert(line[n-1] == '\n');
-
-                if (n == 1) {
-                        log_debug("Received empty line, event is ready");
-                        free(line);
-                        return 1;
-                }
-
-                r = process_dunder(source, line, n);
-                if (r != 0) {
-                        free(line);
-                        return r < 0 ? r : 0;
-                }
-
-                /* MESSAGE=xxx\n
-                   or
-                   COREDUMP\n
-                   LLLLLLLL0011223344...\n
-                */
-                sep = memchr(line, '=', n);
-                if (sep)
-                        /* chomp newline */
-                        n--;
-                else
-                        /* replace \n with = */
-                        line[n-1] = '=';
-                log_debug("Received: %.*s", (int) n, line);
-
-                r = iovw_put(&source->iovw, line, n);
-                if (r < 0) {
-                        log_error("Failed to put line in iovect");
-                        free(line);
-                        return r;
-                }
-
-                if (!sep)
-                        source->state = STATE_DATA_START;
-                return 0; /* continue */
-        }
-
-        case STATE_DATA_START:
-                assert(source->data_size == 0);
-
-                r = get_data_size(source);
-                log_debug("get_data_size() -> %d", r);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        source->state = STATE_EOF;
-                        return 0;
-                }
-
-                source->state = source->data_size > 0 ?
-                        STATE_DATA : STATE_DATA_FINISH;
-
-                return 0; /* continue */
-
-        case STATE_DATA: {
-                void *data;
-
-                assert(source->data_size > 0);
-
-                r = get_data_data(source, &data);
-                log_debug("get_data_data() -> %d", r);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        source->state = STATE_EOF;
-                        return 0;
-                }
-
-                assert(data);
-
-                r = iovw_put(&source->iovw, data, source->data_size);
-                if (r < 0) {
-                        log_error("failed to put binary buffer in iovect");
-                        return r;
-                }
-
-                source->state = STATE_DATA_FINISH;
-
-                return 0; /* continue */
-        }
-
-        case STATE_DATA_FINISH:
-                r = get_data_newline(source);
-                log_debug("get_data_newline() -> %d", r);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        source->state = STATE_EOF;
-                        return 0;
-                }
-
-                source->data_size = 0;
-                source->state = STATE_LINE;
-
-                return 0; /* continue */
-        default:
-                assert_not_reached("wtf?");
-        }
-}
-
-int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal) {
-        int r;
-
-        assert(source);
-        assert(writer);
-
-        r = process_data(source);
-        if (r <= 0)
-                return r;
-
-        /* We have a full event */
-        log_info("Received a full event from source@%p fd:%d (%s)",
-                 source, source->fd, source->name);
-
-        if (!source->iovw.count) {
-                log_warning("Entry with no payload, skipping");
-                goto freeing;
-        }
-
-        assert(source->iovw.iovec);
-        assert(source->iovw.count);
-
-        r = writer_write(writer, &source->iovw, &source->ts, compress, seal);
-        if (r < 0)
-                log_error("Failed to write entry of %zu bytes: %s",
-                          iovw_size(&source->iovw), strerror(-r));
-        else
-                r = 1;
-
- freeing:
-        iovw_free_contents(&source->iovw);
-        return r;
-}
diff --git a/src/journal/journal-remote-parse.h b/src/journal/journal-remote-parse.h
deleted file mode 100644
index c1506d1..0000000
--- a/src/journal/journal-remote-parse.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Zbigniew Jędrzejewski-Szmek
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#pragma once
-
-#include "sd-event.h"
-#include "journal-remote-write.h"
-
-typedef enum {
-        STATE_LINE = 0,    /* waiting to read, or reading line */
-        STATE_DATA_START,  /* reading binary data header */
-        STATE_DATA,        /* reading binary data */
-        STATE_DATA_FINISH, /* expecting newline */
-        STATE_EOF,         /* done */
-} source_state;
-
-typedef struct RemoteSource {
-        char* name;
-        int fd;
-
-        char *buf;
-        size_t size;
-        size_t filled;
-        size_t data_size;
-
-        struct iovec_wrapper iovw;
-
-        source_state state;
-        dual_timestamp ts;
-
-        sd_event_source *event;
-} RemoteSource;
-
-static inline int source_non_empty(RemoteSource *source) {
-        assert(source);
-
-        return source->filled > 0;
-}
-
-void source_free(RemoteSource *source);
-int process_data(RemoteSource *source);
-int push_data(RemoteSource *source, const char *data, size_t size);
-int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal);
diff --git a/src/journal/journal-remote-write.c b/src/journal/journal-remote-write.c
deleted file mode 100644
index 4d142bd..0000000
--- a/src/journal/journal-remote-write.c
+++ /dev/null
@@ -1,124 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2012 Zbigniew Jędrzejewski-Szmek
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include "journal-remote-write.h"
-
-int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
-        if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
-                return log_oom();
-
-        iovw->iovec[iovw->count++] = (struct iovec) {data, len};
-        return 0;
-}
-
-void iovw_free_contents(struct iovec_wrapper *iovw) {
-        for (size_t j = 0; j < iovw->count; j++)
-                free(iovw->iovec[j].iov_base);
-        free(iovw->iovec);
-        iovw->iovec = NULL;
-        iovw->size_bytes = iovw->count = 0;
-}
-
-size_t iovw_size(struct iovec_wrapper *iovw) {
-        size_t n = 0, i;
-
-        for(i = 0; i < iovw->count; i++)
-                n += iovw->iovec[i].iov_len;
-
-        return n;
-}
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-static int do_rotate(JournalFile **f, bool compress, bool seal) {
-        int r = journal_file_rotate(f, compress, seal);
-        if (r < 0) {
-                if (*f)
-                        log_error("Failed to rotate %s: %s", (*f)->path,
-                                  strerror(-r));
-                else
-                        log_error("Failed to create rotated journal: %s",
-                                  strerror(-r));
-        }
-
-        return r;
-}
-
-int writer_init(Writer *s) {
-        assert(s);
-
-        s->journal = NULL;
-
-        memset(&s->metrics, 0xFF, sizeof(s->metrics));
-
-        s->mmap = mmap_cache_new();
-        if (!s->mmap)
-                return log_oom();
-
-        s->seqnum = 0;
-
-        return 0;
-}
-
-int writer_close(Writer *s) {
-        if (s->journal)
-                journal_file_close(s->journal);
-        if (s->mmap)
-                mmap_cache_unref(s->mmap);
-        return 0;
-}
-
-int writer_write(Writer *s,
-                 struct iovec_wrapper *iovw,
-                 dual_timestamp *ts,
-                 bool compress,
-                 bool seal) {
-        int r;
-
-        assert(s);
-        assert(iovw);
-        assert(iovw->count > 0);
-
-        if (journal_file_rotate_suggested(s->journal, 0)) {
-                log_info("%s: Journal header limits reached or header out-of-date, rotating",
-                         s->journal->path);
-                r = do_rotate(&s->journal, compress, seal);
-                if (r < 0)
-                        return r;
-        }
-
-        r = journal_file_append_entry(s->journal, ts, iovw->iovec, iovw->count,
-                                      &s->seqnum, NULL, NULL);
-        if (r >= 0)
-                return 1;
-
-        log_info("%s: Write failed, rotating", s->journal->path);
-        r = do_rotate(&s->journal, compress, seal);
-        if (r < 0)
-                return r;
-
-        log_debug("Retrying write.");
-        r = journal_file_append_entry(s->journal, ts, iovw->iovec, iovw->count,
-                                      &s->seqnum, NULL, NULL);
-        return r < 0 ? r : 1;
-}
diff --git a/src/journal/journal-remote-write.h b/src/journal/journal-remote-write.h
deleted file mode 100644
index 8798216..0000000
--- a/src/journal/journal-remote-write.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Zbigniew Jędrzejewski-Szmek
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#pragma once
-
-#include <stdlib.h>
-
-#include "journal-file.h"
-
-struct iovec_wrapper {
-        struct iovec *iovec;
-        size_t size_bytes;
-        size_t count;
-};
-
-int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len);
-void iovw_free_contents(struct iovec_wrapper *iovw);
-size_t iovw_size(struct iovec_wrapper *iovw);
-
-typedef struct Writer {
-        JournalFile *journal;
-        JournalMetrics metrics;
-        MMapCache *mmap;
-        uint64_t seqnum;
-} Writer;
-
-int writer_init(Writer *s);
-int writer_close(Writer *s);
-int writer_write(Writer *s,
-                 struct iovec_wrapper *iovw,
-                 dual_timestamp *ts,
-                 bool compress,
-                 bool seal);
diff --git a/src/journal/journal-remote.c b/src/journal/journal-remote.c
deleted file mode 100644
index 9db4692..0000000
--- a/src/journal/journal-remote.c
+++ /dev/null
@@ -1,1283 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2012 Zbigniew Jędrzejewski-Szmek
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/prctl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <getopt.h>
-
-#include "sd-daemon.h"
-#include "sd-event.h"
-#include "journal-file.h"
-#include "journald-native.h"
-#include "socket-util.h"
-#include "mkdir.h"
-#include "build.h"
-#include "macro.h"
-#include "strv.h"
-#include "fileio.h"
-#include "microhttpd-util.h"
-
-#ifdef HAVE_GNUTLS
-#include <gnutls/gnutls.h>
-#endif
-
-#include "journal-remote-parse.h"
-#include "journal-remote-write.h"
-
-#define REMOTE_JOURNAL_PATH "/var/log/journal/" SD_ID128_FORMAT_STR "/remote-%s.journal"
-
-static char* arg_output = NULL;
-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 char *key_pem = NULL;
-static char *cert_pem = NULL;
-static char *trust_pem = NULL;
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-static int spawn_child(const char* child, char** argv) {
-        int fd[2];
-        pid_t parent_pid, child_pid;
-        int r;
-
-        if (pipe(fd) < 0) {
-                log_error("Failed to create pager pipe: %m");
-                return -errno;
-        }
-
-        parent_pid = getpid();
-
-        child_pid = fork();
-        if (child_pid < 0) {
-                r = -errno;
-                log_error("Failed to fork: %m");
-                safe_close_pair(fd);
-                return r;
-        }
-
-        /* In the child */
-        if (child_pid == 0) {
-                r = dup2(fd[1], STDOUT_FILENO);
-                if (r < 0) {
-                        log_error("Failed to dup pipe to stdout: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                safe_close_pair(fd);
-
-                /* Make sure the child goes away when the parent dies */
-                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
-                        _exit(EXIT_FAILURE);
-
-                /* Check whether our parent died before we were able
-                 * to set the death signal */
-                if (getppid() != parent_pid)
-                        _exit(EXIT_SUCCESS);
-
-                execvp(child, argv);
-                log_error("Failed to exec child %s: %m", child);
-                _exit(EXIT_FAILURE);
-        }
-
-        r = close(fd[1]);
-        if (r < 0)
-                log_warning("Failed to close write end of pipe: %m");
-
-        return fd[0];
-}
-
-static int spawn_curl(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("Failed to spawn curl: %m");
-        return r;
-}
-
-static int spawn_getter(char *getter, char *url) {
-        int r;
-        _cleanup_strv_free_ char **words = NULL;
-
-        assert(getter);
-        words = strv_split_quoted(getter);
-        if (!words)
-                return log_oom();
-
-        r = spawn_child(words[0], words);
-        if (r < 0)
-                log_error("Failed to spawn getter %s: %m", getter);
-
-        return r;
-}
-
-static int open_output(Writer *s, const char* url) {
-        _cleanup_free_ char *name, *output = NULL;
-        char *c;
-        int r;
-
-        assert(url);
-        name = strdup(url);
-        if (!name)
-                return log_oom();
-
-        for(c = name; *c; c++) {
-                if (*c == '/' || *c == ':' || *c == ' ')
-                        *c = '~';
-                else if (*c == '?') {
-                        *c = '\0';
-                        break;
-                }
-        }
-
-        if (!arg_output) {
-                sd_id128_t machine;
-                r = sd_id128_get_machine(&machine);
-                if (r < 0) {
-                        log_error("failed to determine machine ID128: %s", strerror(-r));
-                        return r;
-                }
-
-                r = asprintf(&output, REMOTE_JOURNAL_PATH,
-                             SD_ID128_FORMAT_VAL(machine), name);
-                if (r < 0)
-                        return log_oom();
-        } else {
-                r = is_dir(arg_output, true);
-                if (r > 0) {
-                        r = asprintf(&output,
-                                     "%s/remote-%s.journal", arg_output, name);
-                        if (r < 0)
-                                return log_oom();
-                } else {
-                        output = strdup(arg_output);
-                        if (!output)
-                                return log_oom();
-                }
-        }
-
-        r = journal_file_open_reliably(output,
-                                       O_RDWR|O_CREAT, 0640,
-                                       arg_compress, arg_seal,
-                                       &s->metrics,
-                                       s->mmap,
-                                       NULL, &s->journal);
-        if (r < 0)
-                log_error("Failed to open output journal %s: %s",
-                          arg_output, strerror(-r));
-        else
-                log_info("Opened output file %s", s->journal->path);
-        return r;
-}
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-typedef struct MHDDaemonWrapper {
-        uint64_t fd;
-        struct MHD_Daemon *daemon;
-
-        sd_event_source *event;
-} MHDDaemonWrapper;
-
-typedef struct RemoteServer {
-        RemoteSource **sources;
-        size_t sources_size;
-        size_t active;
-
-        sd_event *events;
-        sd_event_source *sigterm_event, *sigint_event, *listen_event;
-
-        Writer writer;
-
-        Hashmap *daemons;
-} RemoteServer;
-
-/* This should go away as soon as µhttpd allows state to be passed around. */
-static RemoteServer *server;
-
-static int dispatch_raw_source_event(sd_event_source *event,
-                                     int fd,
-                                     uint32_t revents,
-                                     void *userdata);
-static int dispatch_raw_connection_event(sd_event_source *event,
-                                         int fd,
-                                         uint32_t revents,
-                                         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, RemoteSource **source) {
-        assert(fd >= 0);
-        assert(source);
-
-        if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
-                return log_oom();
-
-        if (s->sources[fd] == NULL) {
-                s->sources[fd] = new0(RemoteSource, 1);
-                if (!s->sources[fd])
-                        return log_oom();
-                s->sources[fd]->fd = -1;
-                s->active++;
-        }
-
-        *source = s->sources[fd];
-        return 0;
-}
-
-static int remove_source(RemoteServer *s, int fd) {
-        RemoteSource *source;
-
-        assert(s);
-        assert(fd >= 0 && fd < (ssize_t) s->sources_size);
-
-        source = s->sources[fd];
-        if (source) {
-                source_free(source);
-                s->sources[fd] = NULL;
-                s->active--;
-        }
-
-        close(fd);
-
-        return 0;
-}
-
-static int add_source(RemoteServer *s, int fd, const char* name) {
-        RemoteSource *source = NULL;
-        _cleanup_free_ char *realname = NULL;
-        int r;
-
-        assert(s);
-        assert(fd >= 0);
-
-        if (name) {
-                realname = strdup(name);
-                if (!realname)
-                        return log_oom();
-        } else {
-                r = asprintf(&realname, "fd:%d", fd);
-                if (r < 0)
-                        return log_oom();
-        }
-
-        log_debug("Creating source for fd:%d (%s)", fd, realname);
-
-        r = get_source_for_fd(s, fd, &source);
-        if (r < 0) {
-                log_error("Failed to create source for fd:%d (%s)", fd, realname);
-                return r;
-        }
-        assert(source);
-        assert(source->fd < 0);
-        source->fd = fd;
-
-        r = sd_event_add_io(s->events, &source->event,
-                            fd, EPOLLIN, dispatch_raw_source_event, s);
-        if (r < 0) {
-                log_error("Failed to register event source for fd:%d: %s",
-                          fd, strerror(-r));
-                goto error;
-        }
-
-        return 1; /* work to do */
-
- error:
-        remove_source(s, fd);
-        return r;
-}
-
-static int add_raw_socket(RemoteServer *s, int fd) {
-        int r;
-
-        r = sd_event_add_io(s->events, &s->listen_event, fd, EPOLLIN,
-                            dispatch_raw_connection_event, s);
-        if (r < 0) {
-                close(fd);
-                return r;
-        }
-
-        s->active ++;
-        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 RemoteSource *request_meta(void **connection_cls) {
-        RemoteSource *source;
-
-        assert(connection_cls);
-        if (*connection_cls)
-                return *connection_cls;
-
-        source = new0(RemoteSource, 1);
-        if (!source)
-                return NULL;
-        source->fd = -1;
-
-        log_debug("Added RemoteSource as connection metadata %p", source);
-
-        *connection_cls = source;
-        return source;
-}
-
-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;
-
-        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;
-        int r;
-
-        assert(source);
-
-        log_debug("request_handler_upload: connection %p, %zu bytes",
-                  connection, *upload_data_size);
-
-        if (*upload_data_size) {
-                log_info("Received %zu bytes", *upload_data_size);
-
-                r = push_data(source, upload_data, *upload_data_size);
-                if (r < 0) {
-                        log_error("Failed to store received data of size %zu: %s",
-                                  *upload_data_size, strerror(-r));
-                        return mhd_respond_oom(connection);
-                }
-                *upload_data_size = 0;
-        } else
-                finished = true;
-
-        while (true) {
-                r = process_source(source, &server->writer, arg_compress, arg_seal);
-                if (r == -E2BIG)
-                        log_warning("Entry too big, skipped");
-                else if (r == -EAGAIN || r == -EWOULDBLOCK)
-                        break;
-                else if (r < 0) {
-                        log_warning("Failed to process data for connection %p", connection);
-                        return mhd_respondf(connection, MHD_HTTP_UNPROCESSABLE_ENTITY,
-                                            "Processing failed: %s", strerror(-r));
-                }
-        }
-
-        if (!finished)
-                return MHD_YES;
-
-        /* The upload is finished */
-
-        if (source_non_empty(source)) {
-                log_warning("EOF reached with incomplete data");
-                return mhd_respond(connection, MHD_HTTP_EXPECTATION_FAILED,
-                                   "Trailing data not processed.");
-        }
-
-        return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n");
-};
-
-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;
-
-        assert(connection);
-        assert(connection_cls);
-        assert(url);
-        assert(method);
-
-        log_debug("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_METHOD_NOT_ACCEPTABLE,
-                                   "Unsupported method.\n");
-
-        if (!streq(url, "/upload"))
-                return mhd_respond(connection, MHD_HTTP_NOT_FOUND,
-                                   "Not found.\n");
-
-        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.\n");
-
-        if (trust_pem) {
-                r = check_permissions(connection, &code);
-                if (r < 0)
-                        return code;
-        }
-
-        if (!request_meta(connection_cls))
-                return respond_oom(connection);
-        return MHD_YES;
-}
-
-static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
-        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_END},
-                { MHD_OPTION_END},
-                { MHD_OPTION_END},
-                { MHD_OPTION_END}};
-        int opts_pos = 3;
-        int flags =
-                MHD_USE_DEBUG |
-                MHD_USE_PEDANTIC_CHECKS |
-                MHD_USE_EPOLL_LINUX_ONLY |
-                MHD_USE_DUAL_STACK;
-
-        const union MHD_DaemonInfo *info;
-        int r, epoll_fd;
-        MHDDaemonWrapper *d;
-
-        assert(fd >= 0);
-
-        r = fd_nonblock(fd, true);
-        if (r < 0) {
-                log_error("Failed to make fd:%d nonblocking: %s", fd, strerror(-r));
-                return r;
-        }
-
-        if (https) {
-                opts[opts_pos++] = (struct MHD_OptionItem)
-                        {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
-                opts[opts_pos++] = (struct MHD_OptionItem)
-                        {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
-
-                flags |= MHD_USE_SSL;
-
-                if (trust_pem)
-                        opts[opts_pos++] = (struct MHD_OptionItem)
-                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
-        }
-
-        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)",
-                  https ? "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 = -ENOTSUP;
-                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->event,
-                            epoll_fd, EPOLLIN, dispatch_http_event, d);
-        if (r < 0) {
-                log_error("Failed to add event callback: %s", strerror(-r));
-                goto error;
-        }
-
-        r = hashmap_ensure_allocated(&s->daemons, uint64_hash_func, uint64_compare_func);
-        if (r < 0) {
-                log_oom();
-                goto error;
-        }
-
-        r = hashmap_put(s->daemons, &d->fd, d);
-        if (r < 0) {
-                log_error("Failed to add daemon to hashmap: %s", strerror(-r));
-                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,
-                                   bool https) {
-        int fd;
-
-        fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
-        if (fd < 0)
-                return fd;
-
-        return setup_microhttpd_server(s, fd, https);
-}
-
-static int dispatch_http_event(sd_event_source *event,
-                               int fd,
-                               uint32_t revents,
-                               void *userdata) {
-        MHDDaemonWrapper *d = userdata;
-        int r;
-
-        assert(d);
-
-        log_info("%s", __func__);
-
-        r = MHD_run(d->daemon);
-        if (r == MHD_NO) {
-                log_error("MHD_run failed!");
-                // XXX: unregister daemon
-                return -EINVAL;
-        }
-
-        return 1; /* work to do */
-}
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-static int dispatch_sigterm(sd_event_source *event,
-                            const struct signalfd_siginfo *si,
-                            void *userdata) {
-        RemoteServer *s = userdata;
-
-        assert(s);
-
-        log_received_signal(LOG_INFO, si);
-
-        sd_event_exit(s->events, 0);
-        return 0;
-}
-
-static int setup_signals(RemoteServer *s) {
-        sigset_t mask;
-        int r;
-
-        assert(s);
-
-        assert_se(sigemptyset(&mask) == 0);
-        sigset_add_many(&mask, SIGINT, SIGTERM, -1);
-        assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
-
-        r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, dispatch_sigterm, s);
-        if (r < 0)
-                return r;
-
-        r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, dispatch_sigterm, s);
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-static int fd_fd(const char *spec) {
-        int fd, r;
-
-        r = safe_atoi(spec, &fd);
-        if (r < 0)
-                return r;
-
-        if (fd >= 0)
-                return -ENOENT;
-
-        return -fd;
-}
-
-
-static int remoteserver_init(RemoteServer *s) {
-        int r, n, fd;
-        const char *output_name = NULL;
-        char **file;
-
-        assert(s);
-
-        sd_event_default(&s->events);
-
-        setup_signals(s);
-
-        assert(server == NULL);
-        server = s;
-
-        n = sd_listen_fds(true);
-        if (n < 0) {
-                log_error("Failed to read listening file descriptors from environment: %s",
-                          strerror(-n));
-                return n;
-        } else
-                log_info("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, false)) {
-                        log_info("Received a listening socket (fd:%d)", fd);
-
-                        if (fd == http_socket)
-                                r = setup_microhttpd_server(s, fd, false);
-                        else if (fd == https_socket)
-                                r = setup_microhttpd_server(s, fd, true);
-                        else
-                                r = add_raw_socket(s, fd);
-                } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
-                        log_info("Received a connection socket (fd:%d)", fd);
-
-                        r = add_source(s, fd, NULL);
-                } else {
-                        log_error("Unknown socket passed on fd:%d", fd);
-
-                        return -EINVAL;
-                }
-
-                if(r < 0) {
-                        log_error("Failed to register socket (fd:%d): %s",
-                                  fd, strerror(-r));
-                        return r;
-                }
-
-                output_name = "socket";
-        }
-
-        if (arg_url) {
-                _cleanup_free_ char *url = NULL;
-                _cleanup_strv_free_ char **urlv = strv_new(arg_url, "/entries", NULL);
-                if (!urlv)
-                        return log_oom();
-                url = strv_join(urlv, "");
-                if (!url)
-                        return log_oom();
-
-                if (arg_getter) {
-                        log_info("Spawning getter %s...", url);
-                        fd = spawn_getter(arg_getter, url);
-                } else {
-                        log_info("Spawning curl %s...", url);
-                        fd = spawn_curl(url);
-                }
-                if (fd < 0)
-                        return fd;
-
-                r = add_source(s, fd, arg_url);
-                if (r < 0)
-                        return r;
-
-                output_name = arg_url;
-        }
-
-        if (arg_listen_raw) {
-                log_info("Listening on a socket...");
-                r = setup_raw_socket(s, arg_listen_raw);
-                if (r < 0)
-                        return r;
-
-                output_name = arg_listen_raw;
-        }
-
-        if (arg_listen_http) {
-                r = setup_microhttpd_socket(s, arg_listen_http, false);
-                if (r < 0)
-                        return r;
-
-                output_name = arg_listen_http;
-        }
-
-        if (arg_listen_https) {
-                r = setup_microhttpd_socket(s, arg_listen_https, true);
-                if (r < 0)
-                        return r;
-
-                output_name = arg_listen_https;
-        }
-
-        STRV_FOREACH(file, arg_files) {
-                if (streq(*file, "-")) {
-                        log_info("Reading standard input...");
-
-                        fd = STDIN_FILENO;
-                        output_name = "stdin";
-                } else {
-                        log_info("Reading file %s...", *file);
-
-                        fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
-                        if (fd < 0) {
-                                log_error("Failed to open %s: %m", *file);
-                                return -errno;
-                        }
-                        output_name = *file;
-                }
-
-                r = add_source(s, fd, output_name);
-                if (r < 0)
-                        return r;
-        }
-
-        if (s->active == 0) {
-                log_error("Zarro sources specified");
-                return -EINVAL;
-        }
-
-        if (!!n + !!arg_url + !!arg_listen_raw + !!arg_files)
-                output_name = "multiple";
-
-        r = writer_init(&s->writer);
-        if (r < 0)
-                return r;
-
-        r = open_output(&s->writer, output_name);
-        return r;
-}
-
-static int server_destroy(RemoteServer *s) {
-        int r;
-        size_t i;
-        MHDDaemonWrapper *d;
-
-        r = writer_close(&s->writer);
-
-        while ((d = hashmap_steal_first(s->daemons))) {
-                MHD_stop_daemon(d->daemon);
-                sd_event_source_unref(d->event);
-                free(d);
-        }
-
-        hashmap_free(s->daemons);
-
-        assert(s->sources_size == 0 || s->sources);
-        for (i = 0; i < s->sources_size; i++)
-                remove_source(s, i);
-
-        free(s->sources);
-
-        sd_event_source_unref(s->sigterm_event);
-        sd_event_source_unref(s->sigint_event);
-        sd_event_source_unref(s->listen_event);
-        sd_event_unref(s->events);
-
-        /* fds that we're listening on remain open... */
-
-        return r;
-}
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-static int dispatch_raw_source_event(sd_event_source *event,
-                                     int fd,
-                                     uint32_t revents,
-                                     void *userdata) {
-
-        RemoteServer *s = userdata;
-        RemoteSource *source;
-        int r;
-
-        assert(fd >= 0 && fd < (ssize_t) s->sources_size);
-        source = s->sources[fd];
-        assert(source->fd == fd);
-
-        r = process_source(source, &s->writer, arg_compress, arg_seal);
-        if (source->state == STATE_EOF) {
-                log_info("EOF reached with source fd:%d (%s)",
-                         source->fd, source->name);
-                if (source_non_empty(source))
-                        log_warning("EOF reached with incomplete data");
-                remove_source(s, source->fd);
-                log_info("%zd active source remaining", s->active);
-        } else if (r == -E2BIG) {
-                log_error("Entry too big, skipped");
-                r = 1;
-        }
-
-        return r;
-}
-
-static int accept_connection(const char* type, int fd, SocketAddress *addr) {
-        int fd2, r;
-
-        log_debug("Accepting new %s connection on fd:%d", type, fd);
-        fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC);
-        if (fd2 < 0) {
-                log_error("accept() on fd:%d failed: %m", fd);
-                return -errno;
-        }
-
-        switch(socket_address_family(addr)) {
-        case AF_INET:
-        case AF_INET6: {
-                char* _cleanup_free_ a = NULL;
-
-                r = socket_address_print(addr, &a);
-                if (r < 0) {
-                        log_error("socket_address_print(): %s", strerror(-r));
-                        close(fd2);
-                        return r;
-                }
-
-                log_info("Accepted %s %s connection from %s",
-                         type,
-                         socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
-                         a);
-
-                return fd2;
-        };
-        default:
-                log_error("Rejected %s connection with unsupported family %d",
-                          type, socket_address_family(addr));
-                close(fd2);
-
-                return -EINVAL;
-        }
-}
-
-static int dispatch_raw_connection_event(sd_event_source *event,
-                                         int fd,
-                                         uint32_t revents,
-                                         void *userdata) {
-        RemoteServer *s = userdata;
-        int fd2;
-        SocketAddress addr = {
-                .size = sizeof(union sockaddr_union),
-                .type = SOCK_STREAM,
-        };
-
-        fd2 = accept_connection("raw", fd, &addr);
-        if (fd2 < 0)
-                return fd2;
-
-        return add_source(s, fd2, NULL);
-}
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-static int help(void) {
-        printf("%s [OPTIONS...] {FILE|-}...\n\n"
-               "Write external journal events to a journal file.\n\n"
-               "Options:\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"
-               "  --[no-]compress      Use XZ-compression in the output journal (default: yes)\n"
-               "  --[no-]seal          Use Event sealing in the output journal (default: no)\n"
-               "  --key=FILENAME       Specify key in PEM format\n"
-               "  --cert=FILENAME      Specify certificate in PEM format\n"
-               "  --trust=FILENAME     Specify CA certificate in PEM format\n"
-               "  --gnutls-log=CATEGORY...\n"
-               "                       Specify a list of gnutls logging categories\n"
-               "  -h --help            Show this help and exit\n"
-               "  --version            Print version string and exit\n"
-               "\n"
-               "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
-               , program_invocation_short_name);
-
-        return 0;
-}
-
-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_COMPRESS,
-                ARG_NO_COMPRESS,
-                ARG_SEAL,
-                ARG_NO_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'              },
-                { "compress",     no_argument,       NULL, ARG_COMPRESS     },
-                { "no-compress",  no_argument,       NULL, ARG_NO_COMPRESS  },
-                { "seal",         no_argument,       NULL, ARG_SEAL         },
-                { "no-seal",      no_argument,       NULL, ARG_NO_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;
-
-        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:
-                        puts(PACKAGE_STRING);
-                        puts(SYSTEMD_FEATURES);
-                        return 0 /* done */;
-
-                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 = fd_fd(optarg);
-                        if (r >= 0)
-                                http_socket = r;
-                        else if (r == -ENOENT)
-                                arg_listen_http = optarg;
-                        else {
-                                log_error("Invalid port/fd specification %s: %s",
-                                          optarg, strerror(-r));
-                                return -EINVAL;
-                        }
-
-                        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 = fd_fd(optarg);
-                        if (r >= 0)
-                                https_socket = r;
-                        else if (r == -ENOENT)
-                                arg_listen_https = optarg;
-                        else {
-                                log_error("Invalid port/fd specification %s: %s",
-                                          optarg, strerror(-r));
-                                return -EINVAL;
-                        }
-
-                        break;
-
-                case ARG_KEY:
-                        if (key_pem) {
-                                log_error("Key file specified twice");
-                                return -EINVAL;
-                        }
-                        r = read_full_file(optarg, &key_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read key file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(key_pem);
-                        break;
-
-                case ARG_CERT:
-                        if (cert_pem) {
-                                log_error("Certificate file specified twice");
-                                return -EINVAL;
-                        }
-                        r = read_full_file(optarg, &cert_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read certificate file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(cert_pem);
-                        break;
-
-                case ARG_TRUST:
-#ifdef HAVE_GNUTLS
-                        if (trust_pem) {
-                                log_error("CA certificate file specified twice");
-                                return -EINVAL;
-                        }
-                        r = read_full_file(optarg, &trust_pem, NULL);
-                        if (r < 0) {
-                                log_error("Failed to read CA certificate file: %s", strerror(-r));
-                                return r;
-                        }
-                        assert(trust_pem);
-                        break;
-#else
-                        log_error("Option --trust is not available.");
-                        return -EINVAL;
-#endif
-
-                case 'o':
-                        if (arg_output) {
-                                log_error("cannot use --output/-o more than once");
-                                return -EINVAL;
-                        }
-
-                        arg_output = optarg;
-                        break;
-
-                case ARG_COMPRESS:
-                        arg_compress = true;
-                        break;
-                case ARG_NO_COMPRESS:
-                        arg_compress = false;
-                        break;
-                case ARG_SEAL:
-                        arg_seal = true;
-                        break;
-                case ARG_NO_SEAL:
-                        arg_seal = false;
-                        break;
-
-                case ARG_GNUTLS_LOG: {
-#ifdef HAVE_GNUTLS
-                        char *word, *state;
-                        size_t size;
-
-                        FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) {
-                                char *cat;
-
-                                cat = strndup(word, size);
-                                if (!cat)
-                                        return log_oom();
-
-                                if (strv_consume(&arg_gnutls_log, cat) < 0)
-                                        return log_oom();
-                        }
-                        break;
-#else
-                        log_error("Option --gnutls-log is not available.");
-                        return -EINVAL;
-#endif
-                }
-
-                case '?':
-                        return -EINVAL;
-
-                default:
-                        log_error("Unknown option code %c", c);
-                        return -EINVAL;
-                }
-
-        if (arg_listen_https && !(key_pem && cert_pem)) {
-                log_error("Options --key and --cert must be used when https sources are specified");
-                return -EINVAL;
-        }
-
-        if (optind < argc)
-                arg_files = argv + optind;
-
-        return 1 /* work to do */;
-}
-
-static int setup_gnutls_logger(char **categories) {
-        if (!arg_listen_http && !arg_listen_https)
-                return 0;
-
-#ifdef HAVE_GNUTLS
-        {
-                char **cat;
-                int r;
-
-                gnutls_global_set_log_function(log_func_gnutls);
-
-                if (categories)
-                        STRV_FOREACH(cat, categories) {
-                                r = log_enable_gnutls_category(*cat);
-                                if (r < 0)
-                                        return r;
-                        }
-                else
-                        log_reset_gnutls_level();
-        }
-#endif
-
-        return 0;
-}
-
-int main(int argc, char **argv) {
-        RemoteServer s = {};
-        int r, r2;
-
-        log_set_max_level(LOG_DEBUG);
-        log_show_color(true);
-        log_parse_environment();
-
-        r = parse_argv(argc, argv);
-        if (r <= 0)
-                return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
-
-        r = setup_gnutls_logger(arg_gnutls_log);
-        if (r < 0)
-                return EXIT_FAILURE;
-
-        if (remoteserver_init(&s) < 0)
-                return EXIT_FAILURE;
-
-        log_debug("%s running as pid "PID_FMT,
-                  program_invocation_short_name, getpid());
-        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("Failed to run event loop: %s", strerror(-r));
-                        break;
-                }
-        }
-
-        log_info("Finishing after writing %" PRIu64 " entries", s.writer.seqnum);
-        r2 = server_destroy(&s);
-
-        sd_notify(false, "STATUS=Shutting down...");
-
-        return r >= 0 && r2 >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
-}
diff --git a/src/journal/microhttpd-util.c b/src/journal/microhttpd-util.c
deleted file mode 100644
index d046686..0000000
--- a/src/journal/microhttpd-util.c
+++ /dev/null
@@ -1,298 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2012 Lennart Poettering
-  Copyright 2012 Zbigniew Jędrzejewski-Szmek
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include <stddef.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "microhttpd-util.h"
-#include "log.h"
-#include "macro.h"
-#include "util.h"
-#include "strv.h"
-
-#ifdef HAVE_GNUTLS
-#include <gnutls/gnutls.h>
-#include <gnutls/x509.h>
-#endif
-
-void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
-        char *f;
-
-        f = strappenda("microhttpd: ", fmt);
-
-        DISABLE_WARNING_FORMAT_NONLITERAL;
-        log_metav(LOG_INFO, NULL, 0, NULL, f, ap);
-        REENABLE_WARNING;
-}
-
-
-static int mhd_respond_internal(struct MHD_Connection *connection,
-                                enum MHD_RequestTerminationCode code,
-                                char *buffer,
-                                size_t size,
-                                enum MHD_ResponseMemoryMode mode) {
-        struct MHD_Response *response;
-        int r;
-
-        assert(connection);
-
-        response = MHD_create_response_from_buffer(size, buffer, mode);
-        if (!response)
-                return MHD_NO;
-
-        log_debug("Queing response %u: %s", code, buffer);
-        MHD_add_response_header(response, "Content-Type", "text/plain");
-        r = MHD_queue_response(connection, code, response);
-        MHD_destroy_response(response);
-
-        return r;
-}
-
-int mhd_respond(struct MHD_Connection *connection,
-                enum MHD_RequestTerminationCode code,
-                const char *message) {
-
-        return mhd_respond_internal(connection, code,
-                                    (char*) message, strlen(message),
-                                    MHD_RESPMEM_PERSISTENT);
-}
-
-int mhd_respond_oom(struct MHD_Connection *connection) {
-        return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE,  "Out of memory.\n");
-}
-
-int mhd_respondf(struct MHD_Connection *connection,
-                 enum MHD_RequestTerminationCode code,
-                 const char *format, ...) {
-
-        char *m;
-        int r;
-        va_list ap;
-
-        assert(connection);
-        assert(format);
-
-        va_start(ap, format);
-        r = vasprintf(&m, format, ap);
-        va_end(ap);
-
-        if (r < 0)
-                return respond_oom(connection);
-
-        return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
-}
-
-#ifdef HAVE_GNUTLS
-
-static struct {
-        const char *const names[4];
-        int level;
-        bool enabled;
-} gnutls_log_map[] = {
-        { {"0"},                  LOG_DEBUG },
-        { {"1", "audit"},         LOG_WARNING, true}, /* gnutls session audit */
-        { {"2", "assert"},        LOG_DEBUG },        /* gnutls assert log */
-        { {"3", "hsk", "ext"},    LOG_DEBUG },        /* gnutls handshake log */
-        { {"4", "rec"},           LOG_DEBUG },        /* gnutls record log */
-        { {"5", "dtls"},          LOG_DEBUG },        /* gnutls DTLS log */
-        { {"6", "buf"},           LOG_DEBUG },
-        { {"7", "write", "read"}, LOG_DEBUG },
-        { {"8"},                  LOG_DEBUG },
-        { {"9", "enc", "int"},    LOG_DEBUG },
-};
-
-void log_func_gnutls(int level, const char *message) {
-        assert_se(message);
-
-        if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
-                if (gnutls_log_map[level].enabled)
-                        log_meta(gnutls_log_map[level].level, NULL, 0, NULL,
-                                 "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
-        } else {
-                log_debug("Received GNUTLS message with unknown level %d.", level);
-                log_meta(LOG_DEBUG, NULL, 0, NULL, "gnutls: %s", message);
-        }
-}
-
-int log_enable_gnutls_category(const char *cat) {
-        unsigned i;
-
-        if (streq(cat, "all")) {
-                for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
-                        gnutls_log_map[i].enabled = true;
-                log_reset_gnutls_level();
-                return 0;
-        } else
-                for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
-                        if (strv_contains((char**)gnutls_log_map[i].names, cat)) {
-                                gnutls_log_map[i].enabled = true;
-                                log_reset_gnutls_level();
-                                return 0;
-                        }
-        log_error("No such log category: %s", cat);
-        return -EINVAL;
-}
-
-void log_reset_gnutls_level(void) {
-        int i;
-
-        for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
-                if (gnutls_log_map[i].enabled) {
-                        log_debug("Setting gnutls log level to %d", i);
-                        gnutls_global_set_log_level(i);
-                        break;
-                }
-}
-
-static int verify_cert_authorized(gnutls_session_t session) {
-        unsigned status;
-        gnutls_certificate_type_t type;
-        gnutls_datum_t out;
-        int r;
-
-        r = gnutls_certificate_verify_peers2(session, &status);
-        if (r < 0) {
-                log_error("gnutls_certificate_verify_peers2 failed: %s", strerror(-r));
-                return r;
-        }
-
-        type = gnutls_certificate_type_get(session);
-        r = gnutls_certificate_verification_status_print(status, type, &out, 0);
-        if (r < 0) {
-                log_error("gnutls_certificate_verification_status_print failed: %s", strerror(-r));
-                return r;
-        }
-
-        log_info("Certificate status: %s", out.data);
-
-        return status == 0 ? 0 : -EPERM;
-}
-
-static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
-        const gnutls_datum_t *pcert;
-        unsigned listsize;
-        gnutls_x509_crt_t cert;
-        int r;
-
-        assert(session);
-        assert(client_cert);
-
-        pcert = gnutls_certificate_get_peers(session, &listsize);
-        if (!pcert || !listsize) {
-                log_error("Failed to retrieve certificate chain");
-                return -EINVAL;
-        }
-
-        r = gnutls_x509_crt_init(&cert);
-        if (r < 0) {
-                log_error("Failed to initialize client certificate");
-                return r;
-        }
-
-        /* Note that by passing values between 0 and listsize here, you
-           can get access to the CA's certs */
-        r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
-        if (r < 0) {
-                log_error("Failed to import client certificate");
-                gnutls_x509_crt_deinit(cert);
-                return r;
-        }
-
-        *client_cert = cert;
-        return 0;
-}
-
-static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
-        size_t len = 0;
-        int r;
-
-        assert(buf);
-        assert(*buf == NULL);
-
-        r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
-        if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
-                log_error("gnutls_x509_crt_get_dn failed");
-                return r;
-        }
-
-        *buf = malloc(len);
-        if (!*buf)
-                return log_oom();
-
-        gnutls_x509_crt_get_dn(client_cert, *buf, &len);
-        return 0;
-}
-
-int check_permissions(struct MHD_Connection *connection, int *code) {
-        const union MHD_ConnectionInfo *ci;
-        gnutls_session_t session;
-        gnutls_x509_crt_t client_cert;
-        _cleanup_free_ char *buf = NULL;
-        int r;
-
-        assert(connection);
-        assert(code);
-
-        *code = 0;
-
-        ci = MHD_get_connection_info(connection,
-                                     MHD_CONNECTION_INFO_GNUTLS_SESSION);
-        if (!ci) {
-                log_error("MHD_get_connection_info failed: session is unencrypted");
-                *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
-                                    "Encrypted connection is required");
-                return -EPERM;
-        }
-        session = ci->tls_session;
-        assert(session);
-
-        r = get_client_cert(session, &client_cert);
-        if (r < 0) {
-                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
-                                    "Authorization through certificate is required");
-                return -EPERM;
-        }
-
-        r = get_auth_dn(client_cert, &buf);
-        if (r < 0) {
-                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
-                                    "Failed to determine distinguished name from certificate");
-                return -EPERM;
-        }
-
-        log_info("Connection from %s", buf);
-
-        r = verify_cert_authorized(session);
-        if (r < 0) {
-                log_warning("Client is not authorized");
-                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
-                                    "Client certificate not signed by recognized authority");
-        }
-        return r;
-}
-
-#else
-int check_permissions(struct MHD_Connection *connection, int *code) {
-        return -EPERM;
-}
-#endif
diff --git a/src/journal/microhttpd-util.h b/src/journal/microhttpd-util.h
deleted file mode 100644
index 4186da8..0000000
--- a/src/journal/microhttpd-util.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2012 Zbigniew Jędrzejewski-Szmek
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#pragma once
-
-#include <stdarg.h>
-#include <microhttpd.h>
-
-#include "macro.h"
-
-void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0);
-
-/* respond_oom() must be usable with return, hence this form. */
-#define respond_oom(connection) log_oom(), mhd_respond_oom(connection)
-
-int mhd_respondf(struct MHD_Connection *connection,
-                 unsigned code,
-                 const char *format, ...) _printf_(3,4);
-
-int mhd_respond(struct MHD_Connection *connection,
-                unsigned code,
-                const char *message);
-
-int mhd_respond_oom(struct MHD_Connection *connection);
-
-int check_permissions(struct MHD_Connection *connection, int *code);
-
-#ifdef HAVE_GNUTLS
-void log_func_gnutls(int level, const char *message);
-int log_enable_gnutls_category(const char *cat);
-void log_reset_gnutls_level(void);
-
-/* This is additionally filtered by our internal log level, so it
- * should be set fairly high to capture all potentially interesting
- * events without overwhelming detail.
- */
-#endif

commit 5937a4d4f263a5a3254e081c6dbb1ea2f860d471
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sat Mar 29 11:58:11 2014 -0400

    microhttp-util: rework gnutls logging

diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
index c682666..db07700 100644
--- a/src/journal/journal-gatewayd.c
+++ b/src/journal/journal-gatewayd.c
@@ -989,7 +989,7 @@ int main(int argc, char *argv[]) {
 
 #ifdef HAVE_GNUTLS
         gnutls_global_set_log_function(log_func_gnutls);
-        gnutls_global_set_log_level(GNUTLS_LOG_LEVEL);
+        log_reset_gnutls_level();
 #endif
 
         n = sd_listen_fds(1);
diff --git a/src/journal/journal-remote.c b/src/journal/journal-remote.c
index a31dc2c..9db4692 100644
--- a/src/journal/journal-remote.c
+++ b/src/journal/journal-remote.c
@@ -63,6 +63,7 @@ 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 char *key_pem = NULL;
 static char *cert_pem = NULL;
@@ -969,6 +970,11 @@ static int help(void) {
                "  -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
                "  --[no-]compress      Use XZ-compression in the output journal (default: yes)\n"
                "  --[no-]seal          Use Event sealing in the output journal (default: no)\n"
+               "  --key=FILENAME       Specify key in PEM format\n"
+               "  --cert=FILENAME      Specify certificate in PEM format\n"
+               "  --trust=FILENAME     Specify CA certificate in PEM format\n"
+               "  --gnutls-log=CATEGORY...\n"
+               "                       Specify a list of gnutls logging categories\n"
                "  -h --help            Show this help and exit\n"
                "  --version            Print version string and exit\n"
                "\n"
@@ -993,6 +999,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_KEY,
                 ARG_CERT,
                 ARG_TRUST,
+                ARG_GNUTLS_LOG,
         };
 
         static const struct option options[] = {
@@ -1011,6 +1018,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "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   },
                 {}
         };
 
@@ -1136,6 +1144,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 #else
                         log_error("Option --trust is not available.");
+                        return -EINVAL;
 #endif
 
                 case 'o':
@@ -1160,6 +1169,28 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_seal = false;
                         break;
 
+                case ARG_GNUTLS_LOG: {
+#ifdef HAVE_GNUTLS
+                        char *word, *state;
+                        size_t size;
+
+                        FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) {
+                                char *cat;
+
+                                cat = strndup(word, size);
+                                if (!cat)
+                                        return log_oom();
+
+                                if (strv_consume(&arg_gnutls_log, cat) < 0)
+                                        return log_oom();
+                        }
+                        break;
+#else
+                        log_error("Option --gnutls-log is not available.");
+                        return -EINVAL;
+#endif
+                }
+
                 case '?':
                         return -EINVAL;
 
@@ -1179,13 +1210,26 @@ static int parse_argv(int argc, char *argv[]) {
         return 1 /* work to do */;
 }
 
-static int setup_gnutls_logger(void) {
+static int setup_gnutls_logger(char **categories) {
         if (!arg_listen_http && !arg_listen_https)
                 return 0;
 
 #ifdef HAVE_GNUTLS
-        gnutls_global_set_log_function(log_func_gnutls);
-        gnutls_global_set_log_level(GNUTLS_LOG_LEVEL);
+        {
+                char **cat;
+                int r;
+
+                gnutls_global_set_log_function(log_func_gnutls);
+
+                if (categories)
+                        STRV_FOREACH(cat, categories) {
+                                r = log_enable_gnutls_category(*cat);
+                                if (r < 0)
+                                        return r;
+                        }
+                else
+                        log_reset_gnutls_level();
+        }
 #endif
 
         return 0;
@@ -1203,7 +1247,7 @@ int main(int argc, char **argv) {
         if (r <= 0)
                 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 
-        r = setup_gnutls_logger();
+        r = setup_gnutls_logger(arg_gnutls_log);
         if (r < 0)
                 return EXIT_FAILURE;
 
diff --git a/src/journal/microhttpd-util.c b/src/journal/microhttpd-util.c
index 007cb5d..d046686 100644
--- a/src/journal/microhttpd-util.c
+++ b/src/journal/microhttpd-util.c
@@ -28,6 +28,7 @@
 #include "log.h"
 #include "macro.h"
 #include "util.h"
+#include "strv.h"
 
 #ifdef HAVE_GNUTLS
 #include <gnutls/gnutls.h>
@@ -103,35 +104,64 @@ int mhd_respondf(struct MHD_Connection *connection,
 
 #ifdef HAVE_GNUTLS
 
-static int log_level_map[] = {
-        LOG_DEBUG,
-        LOG_WARNING, /* gnutls session audit */
-        LOG_DEBUG,   /* gnutls debug log */
-        LOG_WARNING, /* gnutls assert log */
-        LOG_INFO,    /* gnutls handshake log */
-        LOG_DEBUG,   /* gnutls record log */
-        LOG_DEBUG,   /* gnutls dtls log */
-        LOG_DEBUG,
-        LOG_DEBUG,
-        LOG_DEBUG,
-        LOG_DEBUG,   /* gnutls hard log */
-        LOG_DEBUG,   /* gnutls read log */
-        LOG_DEBUG,   /* gnutls write log */
-        LOG_DEBUG,   /* gnutls io log */
-        LOG_DEBUG,   /* gnutls buffers log */
+static struct {
+        const char *const names[4];
+        int level;
+        bool enabled;
+} gnutls_log_map[] = {
+        { {"0"},                  LOG_DEBUG },
+        { {"1", "audit"},         LOG_WARNING, true}, /* gnutls session audit */
+        { {"2", "assert"},        LOG_DEBUG },        /* gnutls assert log */
+        { {"3", "hsk", "ext"},    LOG_DEBUG },        /* gnutls handshake log */
+        { {"4", "rec"},           LOG_DEBUG },        /* gnutls record log */
+        { {"5", "dtls"},          LOG_DEBUG },        /* gnutls DTLS log */
+        { {"6", "buf"},           LOG_DEBUG },
+        { {"7", "write", "read"}, LOG_DEBUG },
+        { {"8"},                  LOG_DEBUG },
+        { {"9", "enc", "int"},    LOG_DEBUG },
 };
 
 void log_func_gnutls(int level, const char *message) {
-        int ourlevel;
-
         assert_se(message);
 
-        if (0 <= level && level < (int) ELEMENTSOF(log_level_map))
-                ourlevel = log_level_map[level];
-        else
-                ourlevel = LOG_DEBUG;
+        if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
+                if (gnutls_log_map[level].enabled)
+                        log_meta(gnutls_log_map[level].level, NULL, 0, NULL,
+                                 "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
+        } else {
+                log_debug("Received GNUTLS message with unknown level %d.", level);
+                log_meta(LOG_DEBUG, NULL, 0, NULL, "gnutls: %s", message);
+        }
+}
+
+int log_enable_gnutls_category(const char *cat) {
+        unsigned i;
+
+        if (streq(cat, "all")) {
+                for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
+                        gnutls_log_map[i].enabled = true;
+                log_reset_gnutls_level();
+                return 0;
+        } else
+                for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
+                        if (strv_contains((char**)gnutls_log_map[i].names, cat)) {
+                                gnutls_log_map[i].enabled = true;
+                                log_reset_gnutls_level();
+                                return 0;
+                        }
+        log_error("No such log category: %s", cat);
+        return -EINVAL;
+}
+
+void log_reset_gnutls_level(void) {
+        int i;
 
-        log_meta(ourlevel, NULL, 0, NULL, "gnutls: %s", message);
+        for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
+                if (gnutls_log_map[i].enabled) {
+                        log_debug("Setting gnutls log level to %d", i);
+                        gnutls_global_set_log_level(i);
+                        break;
+                }
 }
 
 static int verify_cert_authorized(gnutls_session_t session) {
diff --git a/src/journal/microhttpd-util.h b/src/journal/microhttpd-util.h
index df4d003..4186da8 100644
--- a/src/journal/microhttpd-util.h
+++ b/src/journal/microhttpd-util.h
@@ -45,10 +45,11 @@ int check_permissions(struct MHD_Connection *connection, int *code);
 
 #ifdef HAVE_GNUTLS
 void log_func_gnutls(int level, const char *message);
+int log_enable_gnutls_category(const char *cat);
+void log_reset_gnutls_level(void);
 
 /* This is additionally filtered by our internal log level, so it
  * should be set fairly high to capture all potentially interesting
  * events without overwhelming detail.
  */
-#define GNUTLS_LOG_LEVEL 6
 #endif

commit b3306e9c3c1e036396bc6bf74555eecea3f45ad9
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Mar 30 14:20:34 2014 -0400

    journal: allow files with no data whatsoever
    
    If a file was opened for writing, and then closed immediately without
    actually writing any entries, on subsequent opening, it would be
    considered "corrupted". This should be totally fine, and even in
    read mode, an empty file can become non-empty later on.

diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c
index dc041dd..6679ea4 100644
--- a/src/journal/journal-file.c
+++ b/src/journal/journal-file.c
@@ -279,12 +279,6 @@ static int journal_file_verify_header(JournalFile *f) {
             !VALID64(le64toh(f->header->entry_array_offset)))
                 return -ENODATA;
 
-        if (le64toh(f->header->data_hash_table_offset) < le64toh(f->header->header_size) ||
-            le64toh(f->header->field_hash_table_offset) < le64toh(f->header->header_size) ||
-            le64toh(f->header->tail_object_offset) < le64toh(f->header->header_size) ||
-            le64toh(f->header->entry_array_offset) < le64toh(f->header->header_size))
-                return -ENODATA;
-
         if (f->writable) {
                 uint8_t state;
                 sd_id128_t machine_id;



More information about the systemd-commits mailing list