1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Zbigniew Jędrzejewski-Szmek
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include <sys/prctl.h>
29 #include <sys/socket.h>
31 #include <sys/types.h>
35 #include "sd-daemon.h"
37 #include "journal-file.h"
38 #include "journald-native.h"
39 #include "socket-util.h"
45 #include "microhttpd-util.h"
48 #include <gnutls/gnutls.h>
51 #include "journal-remote-parse.h"
52 #include "journal-remote-write.h"
54 #define REMOTE_JOURNAL_PATH "/var/log/journal/" SD_ID128_FORMAT_STR "/remote-%s.journal"
56 static char* arg_output = NULL;
57 static char* arg_url = NULL;
58 static char* arg_getter = NULL;
59 static char* arg_listen_raw = NULL;
60 static char* arg_listen_http = NULL;
61 static char* arg_listen_https = NULL;
62 static char** arg_files = NULL;
63 static int arg_compress = true;
64 static int arg_seal = false;
65 static int http_socket = -1, https_socket = -1;
66 static char** arg_gnutls_log = NULL;
68 static char *key_pem = NULL;
69 static char *cert_pem = NULL;
70 static char *trust_pem = NULL;
72 /**********************************************************************
73 **********************************************************************
74 **********************************************************************/
76 static int spawn_child(const char* child, char** argv) {
78 pid_t parent_pid, child_pid;
82 log_error("Failed to create pager pipe: %m");
86 parent_pid = getpid();
91 log_error("Failed to fork: %m");
98 r = dup2(fd[1], STDOUT_FILENO);
100 log_error("Failed to dup pipe to stdout: %m");
106 /* Make sure the child goes away when the parent dies */
107 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
110 /* Check whether our parent died before we were able
111 * to set the death signal */
112 if (getppid() != parent_pid)
116 log_error("Failed to exec child %s: %m", child);
122 log_warning("Failed to close write end of pipe: %m");
127 static int spawn_curl(char* url) {
128 char **argv = STRV_MAKE("curl",
129 "-HAccept: application/vnd.fdo.journal",
135 r = spawn_child("curl", argv);
137 log_error("Failed to spawn curl: %m");
141 static int spawn_getter(char *getter, char *url) {
143 _cleanup_strv_free_ char **words = NULL;
146 words = strv_split_quoted(getter);
150 r = spawn_child(words[0], words);
152 log_error("Failed to spawn getter %s: %m", getter);
157 static int open_output(Writer *s, const char* url) {
158 _cleanup_free_ char *name, *output = NULL;
167 for(c = name; *c; c++) {
168 if (*c == '/' || *c == ':' || *c == ' ')
170 else if (*c == '?') {
178 r = sd_id128_get_machine(&machine);
180 log_error("failed to determine machine ID128: %s", strerror(-r));
184 r = asprintf(&output, REMOTE_JOURNAL_PATH,
185 SD_ID128_FORMAT_VAL(machine), name);
189 r = is_dir(arg_output, true);
191 r = asprintf(&output,
192 "%s/remote-%s.journal", arg_output, name);
196 output = strdup(arg_output);
202 r = journal_file_open_reliably(output,
203 O_RDWR|O_CREAT, 0640,
204 arg_compress, arg_seal,
209 log_error("Failed to open output journal %s: %s",
210 arg_output, strerror(-r));
212 log_info("Opened output file %s", s->journal->path);
216 /**********************************************************************
217 **********************************************************************
218 **********************************************************************/
220 typedef struct MHDDaemonWrapper {
222 struct MHD_Daemon *daemon;
224 sd_event_source *event;
227 typedef struct RemoteServer {
228 RemoteSource **sources;
233 sd_event_source *sigterm_event, *sigint_event, *listen_event;
240 /* This should go away as soon as µhttpd allows state to be passed around. */
241 static RemoteServer *server;
243 static int dispatch_raw_source_event(sd_event_source *event,
247 static int dispatch_raw_connection_event(sd_event_source *event,
251 static int dispatch_http_event(sd_event_source *event,
256 static int get_source_for_fd(RemoteServer *s, int fd, RemoteSource **source) {
260 if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
263 if (s->sources[fd] == NULL) {
264 s->sources[fd] = new0(RemoteSource, 1);
267 s->sources[fd]->fd = -1;
271 *source = s->sources[fd];
275 static int remove_source(RemoteServer *s, int fd) {
276 RemoteSource *source;
279 assert(fd >= 0 && fd < (ssize_t) s->sources_size);
281 source = s->sources[fd];
284 s->sources[fd] = NULL;
293 static int add_source(RemoteServer *s, int fd, const char* name) {
294 RemoteSource *source = NULL;
295 _cleanup_free_ char *realname = NULL;
302 realname = strdup(name);
306 r = asprintf(&realname, "fd:%d", fd);
311 log_debug("Creating source for fd:%d (%s)", fd, realname);
313 r = get_source_for_fd(s, fd, &source);
315 log_error("Failed to create source for fd:%d (%s)", fd, realname);
319 assert(source->fd < 0);
322 r = sd_event_add_io(s->events, &source->event,
323 fd, EPOLLIN, dispatch_raw_source_event, s);
325 log_error("Failed to register event source for fd:%d: %s",
330 return 1; /* work to do */
333 remove_source(s, fd);
337 static int add_raw_socket(RemoteServer *s, int fd) {
340 r = sd_event_add_io(s->events, &s->listen_event, fd, EPOLLIN,
341 dispatch_raw_connection_event, s);
351 static int setup_raw_socket(RemoteServer *s, const char *address) {
354 fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
358 return add_raw_socket(s, fd);
361 /**********************************************************************
362 **********************************************************************
363 **********************************************************************/
365 static RemoteSource *request_meta(void **connection_cls) {
366 RemoteSource *source;
368 assert(connection_cls);
370 return *connection_cls;
372 source = new0(RemoteSource, 1);
377 log_debug("Added RemoteSource as connection metadata %p", source);
379 *connection_cls = source;
383 static void request_meta_free(void *cls,
384 struct MHD_Connection *connection,
385 void **connection_cls,
386 enum MHD_RequestTerminationCode toe) {
389 assert(connection_cls);
392 log_debug("Cleaning up connection metadata %p", s);
394 *connection_cls = NULL;
397 static int process_http_upload(
398 struct MHD_Connection *connection,
399 const char *upload_data,
400 size_t *upload_data_size,
401 RemoteSource *source) {
403 bool finished = false;
408 log_debug("request_handler_upload: connection %p, %zu bytes",
409 connection, *upload_data_size);
411 if (*upload_data_size) {
412 log_info("Received %zu bytes", *upload_data_size);
414 r = push_data(source, upload_data, *upload_data_size);
416 log_error("Failed to store received data of size %zu: %s",
417 *upload_data_size, strerror(-r));
418 return mhd_respond_oom(connection);
420 *upload_data_size = 0;
425 r = process_source(source, &server->writer, arg_compress, arg_seal);
427 log_warning("Entry too big, skipped");
428 else if (r == -EAGAIN || r == -EWOULDBLOCK)
431 log_warning("Failed to process data for connection %p", connection);
432 return mhd_respondf(connection, MHD_HTTP_UNPROCESSABLE_ENTITY,
433 "Processing failed: %s", strerror(-r));
440 /* The upload is finished */
442 if (source_non_empty(source)) {
443 log_warning("EOF reached with incomplete data");
444 return mhd_respond(connection, MHD_HTTP_EXPECTATION_FAILED,
445 "Trailing data not processed.");
448 return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n");
451 static int request_handler(
453 struct MHD_Connection *connection,
457 const char *upload_data,
458 size_t *upload_data_size,
459 void **connection_cls) {
465 assert(connection_cls);
469 log_debug("Handling a connection %s %s %s", method, url, version);
472 return process_http_upload(connection,
473 upload_data, upload_data_size,
476 if (!streq(method, "POST"))
477 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
478 "Unsupported method.\n");
480 if (!streq(url, "/upload"))
481 return mhd_respond(connection, MHD_HTTP_NOT_FOUND,
484 header = MHD_lookup_connection_value(connection,
485 MHD_HEADER_KIND, "Content-Type");
486 if (!header || !streq(header, "application/vnd.fdo.journal"))
487 return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
488 "Content-Type: application/vnd.fdo.journal"
492 r = check_permissions(connection, &code);
497 if (!request_meta(connection_cls))
498 return respond_oom(connection);
502 static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
503 struct MHD_OptionItem opts[] = {
504 { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free},
505 { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger},
506 { MHD_OPTION_LISTEN_SOCKET, fd},
514 MHD_USE_PEDANTIC_CHECKS |
515 MHD_USE_EPOLL_LINUX_ONLY |
518 const union MHD_DaemonInfo *info;
524 r = fd_nonblock(fd, true);
526 log_error("Failed to make fd:%d nonblocking: %s", fd, strerror(-r));
531 opts[opts_pos++] = (struct MHD_OptionItem)
532 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
533 opts[opts_pos++] = (struct MHD_OptionItem)
534 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
536 flags |= MHD_USE_SSL;
539 opts[opts_pos++] = (struct MHD_OptionItem)
540 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
543 d = new(MHDDaemonWrapper, 1);
547 d->fd = (uint64_t) fd;
549 d->daemon = MHD_start_daemon(flags, 0,
551 request_handler, NULL,
552 MHD_OPTION_ARRAY, opts,
555 log_error("Failed to start µhttp daemon");
560 log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)",
561 https ? "HTTPS" : "HTTP", fd, d);
564 info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
566 log_error("µhttp returned NULL daemon info");
571 epoll_fd = info->listen_fd;
573 log_error("µhttp epoll fd is invalid");
578 r = sd_event_add_io(s->events, &d->event,
579 epoll_fd, EPOLLIN, dispatch_http_event, d);
581 log_error("Failed to add event callback: %s", strerror(-r));
585 r = hashmap_ensure_allocated(&s->daemons, uint64_hash_func, uint64_compare_func);
591 r = hashmap_put(s->daemons, &d->fd, d);
593 log_error("Failed to add daemon to hashmap: %s", strerror(-r));
601 MHD_stop_daemon(d->daemon);
607 static int setup_microhttpd_socket(RemoteServer *s,
612 fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
616 return setup_microhttpd_server(s, fd, https);
619 static int dispatch_http_event(sd_event_source *event,
623 MHDDaemonWrapper *d = userdata;
628 log_info("%s", __func__);
630 r = MHD_run(d->daemon);
632 log_error("MHD_run failed!");
633 // XXX: unregister daemon
637 return 1; /* work to do */
640 /**********************************************************************
641 **********************************************************************
642 **********************************************************************/
644 static int dispatch_sigterm(sd_event_source *event,
645 const struct signalfd_siginfo *si,
647 RemoteServer *s = userdata;
651 log_received_signal(LOG_INFO, si);
653 sd_event_exit(s->events, 0);
657 static int setup_signals(RemoteServer *s) {
663 assert_se(sigemptyset(&mask) == 0);
664 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
665 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
667 r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, dispatch_sigterm, s);
671 r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, dispatch_sigterm, s);
678 static int fd_fd(const char *spec) {
681 r = safe_atoi(spec, &fd);
692 static int remoteserver_init(RemoteServer *s) {
694 const char *output_name = NULL;
699 sd_event_default(&s->events);
703 assert(server == NULL);
706 n = sd_listen_fds(true);
708 log_error("Failed to read listening file descriptors from environment: %s",
712 log_info("Received %d descriptors", n);
714 if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) {
715 log_error("Received fewer sockets than expected");
719 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
720 if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
721 log_info("Received a listening socket (fd:%d)", fd);
723 if (fd == http_socket)
724 r = setup_microhttpd_server(s, fd, false);
725 else if (fd == https_socket)
726 r = setup_microhttpd_server(s, fd, true);
728 r = add_raw_socket(s, fd);
729 } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
730 log_info("Received a connection socket (fd:%d)", fd);
732 r = add_source(s, fd, NULL);
734 log_error("Unknown socket passed on fd:%d", fd);
740 log_error("Failed to register socket (fd:%d): %s",
745 output_name = "socket";
749 _cleanup_free_ char *url = NULL;
750 _cleanup_strv_free_ char **urlv = strv_new(arg_url, "/entries", NULL);
753 url = strv_join(urlv, "");
758 log_info("Spawning getter %s...", url);
759 fd = spawn_getter(arg_getter, url);
761 log_info("Spawning curl %s...", url);
762 fd = spawn_curl(url);
767 r = add_source(s, fd, arg_url);
771 output_name = arg_url;
774 if (arg_listen_raw) {
775 log_info("Listening on a socket...");
776 r = setup_raw_socket(s, arg_listen_raw);
780 output_name = arg_listen_raw;
783 if (arg_listen_http) {
784 r = setup_microhttpd_socket(s, arg_listen_http, false);
788 output_name = arg_listen_http;
791 if (arg_listen_https) {
792 r = setup_microhttpd_socket(s, arg_listen_https, true);
796 output_name = arg_listen_https;
799 STRV_FOREACH(file, arg_files) {
800 if (streq(*file, "-")) {
801 log_info("Reading standard input...");
804 output_name = "stdin";
806 log_info("Reading file %s...", *file);
808 fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
810 log_error("Failed to open %s: %m", *file);
816 r = add_source(s, fd, output_name);
821 if (s->active == 0) {
822 log_error("Zarro sources specified");
826 if (!!n + !!arg_url + !!arg_listen_raw + !!arg_files)
827 output_name = "multiple";
829 r = writer_init(&s->writer);
833 r = open_output(&s->writer, output_name);
837 static int server_destroy(RemoteServer *s) {
842 r = writer_close(&s->writer);
844 while ((d = hashmap_steal_first(s->daemons))) {
845 MHD_stop_daemon(d->daemon);
846 sd_event_source_unref(d->event);
850 hashmap_free(s->daemons);
852 assert(s->sources_size == 0 || s->sources);
853 for (i = 0; i < s->sources_size; i++)
858 sd_event_source_unref(s->sigterm_event);
859 sd_event_source_unref(s->sigint_event);
860 sd_event_source_unref(s->listen_event);
861 sd_event_unref(s->events);
863 /* fds that we're listening on remain open... */
868 /**********************************************************************
869 **********************************************************************
870 **********************************************************************/
872 static int dispatch_raw_source_event(sd_event_source *event,
877 RemoteServer *s = userdata;
878 RemoteSource *source;
881 assert(fd >= 0 && fd < (ssize_t) s->sources_size);
882 source = s->sources[fd];
883 assert(source->fd == fd);
885 r = process_source(source, &s->writer, arg_compress, arg_seal);
886 if (source->state == STATE_EOF) {
887 log_info("EOF reached with source fd:%d (%s)",
888 source->fd, source->name);
889 if (source_non_empty(source))
890 log_warning("EOF reached with incomplete data");
891 remove_source(s, source->fd);
892 log_info("%zd active source remaining", s->active);
893 } else if (r == -E2BIG) {
894 log_error("Entry too big, skipped");
901 static int accept_connection(const char* type, int fd, SocketAddress *addr) {
904 log_debug("Accepting new %s connection on fd:%d", type, fd);
905 fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC);
907 log_error("accept() on fd:%d failed: %m", fd);
911 switch(socket_address_family(addr)) {
914 char* _cleanup_free_ a = NULL;
916 r = socket_address_print(addr, &a);
918 log_error("socket_address_print(): %s", strerror(-r));
923 log_info("Accepted %s %s connection from %s",
925 socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
931 log_error("Rejected %s connection with unsupported family %d",
932 type, socket_address_family(addr));
939 static int dispatch_raw_connection_event(sd_event_source *event,
943 RemoteServer *s = userdata;
945 SocketAddress addr = {
946 .size = sizeof(union sockaddr_union),
950 fd2 = accept_connection("raw", fd, &addr);
954 return add_source(s, fd2, NULL);
957 /**********************************************************************
958 **********************************************************************
959 **********************************************************************/
961 static int help(void) {
962 printf("%s [OPTIONS...] {FILE|-}...\n\n"
963 "Write external journal events to a journal file.\n\n"
965 " --url=URL Read events from systemd-journal-gatewayd at URL\n"
966 " --getter=COMMAND Read events from the output of COMMAND\n"
967 " --listen-raw=ADDR Listen for connections at ADDR\n"
968 " --listen-http=ADDR Listen for HTTP connections at ADDR\n"
969 " --listen-https=ADDR Listen for HTTPS connections at ADDR\n"
970 " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
971 " --[no-]compress Use XZ-compression in the output journal (default: yes)\n"
972 " --[no-]seal Use Event sealing in the output journal (default: no)\n"
973 " --key=FILENAME Specify key in PEM format\n"
974 " --cert=FILENAME Specify certificate in PEM format\n"
975 " --trust=FILENAME Specify CA certificate in PEM format\n"
976 " --gnutls-log=CATEGORY...\n"
977 " Specify a list of gnutls logging categories\n"
978 " -h --help Show this help and exit\n"
979 " --version Print version string and exit\n"
981 "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
982 , program_invocation_short_name);
987 static int parse_argv(int argc, char *argv[]) {
1005 static const struct option options[] = {
1006 { "help", no_argument, NULL, 'h' },
1007 { "version", no_argument, NULL, ARG_VERSION },
1008 { "url", required_argument, NULL, ARG_URL },
1009 { "getter", required_argument, NULL, ARG_GETTER },
1010 { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW },
1011 { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP },
1012 { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
1013 { "output", required_argument, NULL, 'o' },
1014 { "compress", no_argument, NULL, ARG_COMPRESS },
1015 { "no-compress", no_argument, NULL, ARG_NO_COMPRESS },
1016 { "seal", no_argument, NULL, ARG_SEAL },
1017 { "no-seal", no_argument, NULL, ARG_NO_SEAL },
1018 { "key", required_argument, NULL, ARG_KEY },
1019 { "cert", required_argument, NULL, ARG_CERT },
1020 { "trust", required_argument, NULL, ARG_TRUST },
1021 { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG },
1030 while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
1034 return 0 /* done */;
1037 puts(PACKAGE_STRING);
1038 puts(SYSTEMD_FEATURES);
1039 return 0 /* done */;
1043 log_error("cannot currently set more than one --url");
1052 log_error("cannot currently use --getter more than once");
1056 arg_getter = optarg;
1059 case ARG_LISTEN_RAW:
1060 if (arg_listen_raw) {
1061 log_error("cannot currently use --listen-raw more than once");
1065 arg_listen_raw = optarg;
1068 case ARG_LISTEN_HTTP:
1069 if (arg_listen_http || http_socket >= 0) {
1070 log_error("cannot currently use --listen-http more than once");
1077 else if (r == -ENOENT)
1078 arg_listen_http = optarg;
1080 log_error("Invalid port/fd specification %s: %s",
1081 optarg, strerror(-r));
1087 case ARG_LISTEN_HTTPS:
1088 if (arg_listen_https || https_socket >= 0) {
1089 log_error("cannot currently use --listen-https more than once");
1096 else if (r == -ENOENT)
1097 arg_listen_https = optarg;
1099 log_error("Invalid port/fd specification %s: %s",
1100 optarg, strerror(-r));
1108 log_error("Key file specified twice");
1111 r = read_full_file(optarg, &key_pem, NULL);
1113 log_error("Failed to read key file: %s", strerror(-r));
1121 log_error("Certificate file specified twice");
1124 r = read_full_file(optarg, &cert_pem, NULL);
1126 log_error("Failed to read certificate file: %s", strerror(-r));
1135 log_error("CA certificate file specified twice");
1138 r = read_full_file(optarg, &trust_pem, NULL);
1140 log_error("Failed to read CA certificate file: %s", strerror(-r));
1146 log_error("Option --trust is not available.");
1152 log_error("cannot use --output/-o more than once");
1156 arg_output = optarg;
1160 arg_compress = true;
1162 case ARG_NO_COMPRESS:
1163 arg_compress = false;
1172 case ARG_GNUTLS_LOG: {
1177 FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) {
1180 cat = strndup(word, size);
1184 if (strv_consume(&arg_gnutls_log, cat) < 0)
1189 log_error("Option --gnutls-log is not available.");
1198 log_error("Unknown option code %c", c);
1202 if (arg_listen_https && !(key_pem && cert_pem)) {
1203 log_error("Options --key and --cert must be used when https sources are specified");
1208 arg_files = argv + optind;
1210 return 1 /* work to do */;
1213 static int setup_gnutls_logger(char **categories) {
1214 if (!arg_listen_http && !arg_listen_https)
1222 gnutls_global_set_log_function(log_func_gnutls);
1225 STRV_FOREACH(cat, categories) {
1226 r = log_enable_gnutls_category(*cat);
1231 log_reset_gnutls_level();
1238 int main(int argc, char **argv) {
1239 RemoteServer s = {};
1242 log_set_max_level(LOG_DEBUG);
1243 log_show_color(true);
1244 log_parse_environment();
1246 r = parse_argv(argc, argv);
1248 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
1250 r = setup_gnutls_logger(arg_gnutls_log);
1252 return EXIT_FAILURE;
1254 if (remoteserver_init(&s) < 0)
1255 return EXIT_FAILURE;
1257 log_debug("%s running as pid "PID_FMT,
1258 program_invocation_short_name, getpid());
1261 "STATUS=Processing requests...");
1264 r = sd_event_get_state(s.events);
1267 if (r == SD_EVENT_FINISHED)
1270 r = sd_event_run(s.events, -1);
1272 log_error("Failed to run event loop: %s", strerror(-r));
1277 log_info("Finishing after writing %" PRIu64 " entries", s.writer.seqnum);
1278 r2 = server_destroy(&s);
1280 sd_notify(false, "STATUS=Shutting down...");
1282 return r >= 0 && r2 >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;