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 return mhd_respond_oom(connection);
418 *upload_data_size = 0;
423 r = process_source(source, &server->writer, arg_compress, arg_seal);
424 if (r == -EAGAIN || r == -EWOULDBLOCK)
427 log_warning("Failed to process data for connection %p", connection);
429 return mhd_respondf(connection,
430 MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
431 "Entry is too large, maximum is %u bytes.\n",
434 return mhd_respondf(connection,
435 MHD_HTTP_UNPROCESSABLE_ENTITY,
436 "Processing failed: %s.", strerror(-r));
443 /* The upload is finished */
445 if (source_non_empty(source)) {
446 log_warning("EOF reached with incomplete data");
447 return mhd_respond(connection, MHD_HTTP_EXPECTATION_FAILED,
448 "Trailing data not processed.");
451 return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n");
454 static int request_handler(
456 struct MHD_Connection *connection,
460 const char *upload_data,
461 size_t *upload_data_size,
462 void **connection_cls) {
468 assert(connection_cls);
472 log_debug("Handling a connection %s %s %s", method, url, version);
475 return process_http_upload(connection,
476 upload_data, upload_data_size,
479 if (!streq(method, "POST"))
480 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
481 "Unsupported method.\n");
483 if (!streq(url, "/upload"))
484 return mhd_respond(connection, MHD_HTTP_NOT_FOUND,
487 header = MHD_lookup_connection_value(connection,
488 MHD_HEADER_KIND, "Content-Type");
489 if (!header || !streq(header, "application/vnd.fdo.journal"))
490 return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
491 "Content-Type: application/vnd.fdo.journal"
495 r = check_permissions(connection, &code);
500 if (!request_meta(connection_cls))
501 return respond_oom(connection);
505 static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
506 struct MHD_OptionItem opts[] = {
507 { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free},
508 { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger},
509 { MHD_OPTION_LISTEN_SOCKET, fd},
517 MHD_USE_PEDANTIC_CHECKS |
518 MHD_USE_EPOLL_LINUX_ONLY |
521 const union MHD_DaemonInfo *info;
527 r = fd_nonblock(fd, true);
529 log_error("Failed to make fd:%d nonblocking: %s", fd, strerror(-r));
534 opts[opts_pos++] = (struct MHD_OptionItem)
535 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
536 opts[opts_pos++] = (struct MHD_OptionItem)
537 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
539 flags |= MHD_USE_SSL;
542 opts[opts_pos++] = (struct MHD_OptionItem)
543 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
546 d = new(MHDDaemonWrapper, 1);
550 d->fd = (uint64_t) fd;
552 d->daemon = MHD_start_daemon(flags, 0,
554 request_handler, NULL,
555 MHD_OPTION_ARRAY, opts,
558 log_error("Failed to start µhttp daemon");
563 log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)",
564 https ? "HTTPS" : "HTTP", fd, d);
567 info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
569 log_error("µhttp returned NULL daemon info");
574 epoll_fd = info->listen_fd;
576 log_error("µhttp epoll fd is invalid");
581 r = sd_event_add_io(s->events, &d->event,
582 epoll_fd, EPOLLIN, dispatch_http_event, d);
584 log_error("Failed to add event callback: %s", strerror(-r));
588 r = hashmap_ensure_allocated(&s->daemons, uint64_hash_func, uint64_compare_func);
594 r = hashmap_put(s->daemons, &d->fd, d);
596 log_error("Failed to add daemon to hashmap: %s", strerror(-r));
604 MHD_stop_daemon(d->daemon);
610 static int setup_microhttpd_socket(RemoteServer *s,
615 fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
619 return setup_microhttpd_server(s, fd, https);
622 static int dispatch_http_event(sd_event_source *event,
626 MHDDaemonWrapper *d = userdata;
631 r = MHD_run(d->daemon);
633 log_error("MHD_run failed!");
634 // XXX: unregister daemon
638 return 1; /* work to do */
641 /**********************************************************************
642 **********************************************************************
643 **********************************************************************/
645 static int dispatch_sigterm(sd_event_source *event,
646 const struct signalfd_siginfo *si,
648 RemoteServer *s = userdata;
652 log_received_signal(LOG_INFO, si);
654 sd_event_exit(s->events, 0);
658 static int setup_signals(RemoteServer *s) {
664 assert_se(sigemptyset(&mask) == 0);
665 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
666 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
668 r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, dispatch_sigterm, s);
672 r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, dispatch_sigterm, s);
679 static int fd_fd(const char *spec) {
682 r = safe_atoi(spec, &fd);
693 static int remoteserver_init(RemoteServer *s) {
695 const char *output_name = NULL;
700 sd_event_default(&s->events);
704 assert(server == NULL);
707 n = sd_listen_fds(true);
709 log_error("Failed to read listening file descriptors from environment: %s",
713 log_info("Received %d descriptors", n);
715 if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) {
716 log_error("Received fewer sockets than expected");
720 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
721 if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
722 log_info("Received a listening socket (fd:%d)", fd);
724 if (fd == http_socket)
725 r = setup_microhttpd_server(s, fd, false);
726 else if (fd == https_socket)
727 r = setup_microhttpd_server(s, fd, true);
729 r = add_raw_socket(s, fd);
730 } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
731 log_info("Received a connection socket (fd:%d)", fd);
733 r = add_source(s, fd, NULL);
735 log_error("Unknown socket passed on fd:%d", fd);
741 log_error("Failed to register socket (fd:%d): %s",
746 output_name = "socket";
750 _cleanup_free_ char *url = NULL;
751 _cleanup_strv_free_ char **urlv = strv_new(arg_url, "/entries", NULL);
754 url = strv_join(urlv, "");
759 log_info("Spawning getter %s...", url);
760 fd = spawn_getter(arg_getter, url);
762 log_info("Spawning curl %s...", url);
763 fd = spawn_curl(url);
768 r = add_source(s, fd, arg_url);
772 output_name = arg_url;
775 if (arg_listen_raw) {
776 log_info("Listening on a socket...");
777 r = setup_raw_socket(s, arg_listen_raw);
781 output_name = arg_listen_raw;
784 if (arg_listen_http) {
785 r = setup_microhttpd_socket(s, arg_listen_http, false);
789 output_name = arg_listen_http;
792 if (arg_listen_https) {
793 r = setup_microhttpd_socket(s, arg_listen_https, true);
797 output_name = arg_listen_https;
800 STRV_FOREACH(file, arg_files) {
801 if (streq(*file, "-")) {
802 log_info("Reading standard input...");
805 output_name = "stdin";
807 log_info("Reading file %s...", *file);
809 fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
811 log_error("Failed to open %s: %m", *file);
817 r = add_source(s, fd, output_name);
822 if (s->active == 0) {
823 log_error("Zarro sources specified");
827 if (!!n + !!arg_url + !!arg_listen_raw + !!arg_files)
828 output_name = "multiple";
830 r = writer_init(&s->writer);
834 r = open_output(&s->writer, output_name);
838 static int server_destroy(RemoteServer *s) {
843 r = writer_close(&s->writer);
845 while ((d = hashmap_steal_first(s->daemons))) {
846 MHD_stop_daemon(d->daemon);
847 sd_event_source_unref(d->event);
851 hashmap_free(s->daemons);
853 assert(s->sources_size == 0 || s->sources);
854 for (i = 0; i < s->sources_size; i++)
859 sd_event_source_unref(s->sigterm_event);
860 sd_event_source_unref(s->sigint_event);
861 sd_event_source_unref(s->listen_event);
862 sd_event_unref(s->events);
864 /* fds that we're listening on remain open... */
869 /**********************************************************************
870 **********************************************************************
871 **********************************************************************/
873 static int dispatch_raw_source_event(sd_event_source *event,
878 RemoteServer *s = userdata;
879 RemoteSource *source;
882 assert(fd >= 0 && fd < (ssize_t) s->sources_size);
883 source = s->sources[fd];
884 assert(source->fd == fd);
886 r = process_source(source, &s->writer, arg_compress, arg_seal);
887 if (source->state == STATE_EOF) {
888 log_info("EOF reached with source fd:%d (%s)",
889 source->fd, source->name);
890 if (source_non_empty(source))
891 log_warning("EOF reached with incomplete data");
892 remove_source(s, source->fd);
893 log_info("%zd active source remaining", s->active);
894 } else if (r == -E2BIG) {
895 log_error("Entry too big, skipped");
902 static int accept_connection(const char* type, int fd, SocketAddress *addr) {
905 log_debug("Accepting new %s connection on fd:%d", type, fd);
906 fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC);
908 log_error("accept() on fd:%d failed: %m", fd);
912 switch(socket_address_family(addr)) {
915 char* _cleanup_free_ a = NULL;
917 r = socket_address_print(addr, &a);
919 log_error("socket_address_print(): %s", strerror(-r));
924 log_info("Accepted %s %s connection from %s",
926 socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
932 log_error("Rejected %s connection with unsupported family %d",
933 type, socket_address_family(addr));
940 static int dispatch_raw_connection_event(sd_event_source *event,
944 RemoteServer *s = userdata;
946 SocketAddress addr = {
947 .size = sizeof(union sockaddr_union),
951 fd2 = accept_connection("raw", fd, &addr);
955 return add_source(s, fd2, NULL);
958 /**********************************************************************
959 **********************************************************************
960 **********************************************************************/
962 static void help(void) {
963 printf("%s [OPTIONS...] {FILE|-}...\n\n"
964 "Write external journal events to a journal file.\n\n"
966 " --url=URL Read events from systemd-journal-gatewayd at URL\n"
967 " --getter=COMMAND Read events from the output of COMMAND\n"
968 " --listen-raw=ADDR Listen for connections at ADDR\n"
969 " --listen-http=ADDR Listen for HTTP connections at ADDR\n"
970 " --listen-https=ADDR Listen for HTTPS connections at ADDR\n"
971 " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
972 " --[no-]compress Use XZ-compression in the output journal (default: yes)\n"
973 " --[no-]seal Use Event sealing in the output journal (default: no)\n"
974 " --key=FILENAME Specify key in PEM format\n"
975 " --cert=FILENAME Specify certificate in PEM format\n"
976 " --trust=FILENAME Specify CA certificate in PEM format\n"
977 " --gnutls-log=CATEGORY...\n"
978 " Specify a list of gnutls logging categories\n"
979 " -h --help Show this help and exit\n"
980 " --version Print version string and exit\n"
982 "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
983 , program_invocation_short_name);
986 static int parse_argv(int argc, char *argv[]) {
1004 static const struct option options[] = {
1005 { "help", no_argument, NULL, 'h' },
1006 { "version", no_argument, NULL, ARG_VERSION },
1007 { "url", required_argument, NULL, ARG_URL },
1008 { "getter", required_argument, NULL, ARG_GETTER },
1009 { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW },
1010 { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP },
1011 { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
1012 { "output", required_argument, NULL, 'o' },
1013 { "compress", no_argument, NULL, ARG_COMPRESS },
1014 { "no-compress", no_argument, NULL, ARG_NO_COMPRESS },
1015 { "seal", no_argument, NULL, ARG_SEAL },
1016 { "no-seal", no_argument, NULL, ARG_NO_SEAL },
1017 { "key", required_argument, NULL, ARG_KEY },
1018 { "cert", required_argument, NULL, ARG_CERT },
1019 { "trust", required_argument, NULL, ARG_TRUST },
1020 { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG },
1029 while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
1033 return 0 /* done */;
1036 puts(PACKAGE_STRING);
1037 puts(SYSTEMD_FEATURES);
1038 return 0 /* done */;
1042 log_error("cannot currently set more than one --url");
1051 log_error("cannot currently use --getter more than once");
1055 arg_getter = optarg;
1058 case ARG_LISTEN_RAW:
1059 if (arg_listen_raw) {
1060 log_error("cannot currently use --listen-raw more than once");
1064 arg_listen_raw = optarg;
1067 case ARG_LISTEN_HTTP:
1068 if (arg_listen_http || http_socket >= 0) {
1069 log_error("cannot currently use --listen-http more than once");
1076 else if (r == -ENOENT)
1077 arg_listen_http = optarg;
1079 log_error("Invalid port/fd specification %s: %s",
1080 optarg, strerror(-r));
1086 case ARG_LISTEN_HTTPS:
1087 if (arg_listen_https || https_socket >= 0) {
1088 log_error("cannot currently use --listen-https more than once");
1095 else if (r == -ENOENT)
1096 arg_listen_https = optarg;
1098 log_error("Invalid port/fd specification %s: %s",
1099 optarg, strerror(-r));
1107 log_error("Key file specified twice");
1110 r = read_full_file(optarg, &key_pem, NULL);
1112 log_error("Failed to read key file: %s", strerror(-r));
1120 log_error("Certificate file specified twice");
1123 r = read_full_file(optarg, &cert_pem, NULL);
1125 log_error("Failed to read certificate file: %s", strerror(-r));
1134 log_error("CA certificate file specified twice");
1137 r = read_full_file(optarg, &trust_pem, NULL);
1139 log_error("Failed to read CA certificate file: %s", strerror(-r));
1145 log_error("Option --trust is not available.");
1151 log_error("cannot use --output/-o more than once");
1155 arg_output = optarg;
1159 arg_compress = true;
1161 case ARG_NO_COMPRESS:
1162 arg_compress = false;
1171 case ARG_GNUTLS_LOG: {
1176 FOREACH_WORD_SEPARATOR(word, size, optarg, ",", state) {
1179 cat = strndup(word, size);
1183 if (strv_consume(&arg_gnutls_log, cat) < 0)
1188 log_error("Option --gnutls-log is not available.");
1197 log_error("Unknown option code %c", c);
1201 if (arg_listen_https && !(key_pem && cert_pem)) {
1202 log_error("Options --key and --cert must be used when using HTTPS.");
1207 arg_files = argv + optind;
1209 return 1 /* work to do */;
1212 static int setup_gnutls_logger(char **categories) {
1213 if (!arg_listen_http && !arg_listen_https)
1221 gnutls_global_set_log_function(log_func_gnutls);
1224 STRV_FOREACH(cat, categories) {
1225 r = log_enable_gnutls_category(*cat);
1230 log_reset_gnutls_level();
1237 int main(int argc, char **argv) {
1238 RemoteServer s = {};
1241 log_show_color(true);
1242 log_parse_environment();
1244 r = parse_argv(argc, argv);
1246 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
1248 r = setup_gnutls_logger(arg_gnutls_log);
1250 return EXIT_FAILURE;
1252 if (remoteserver_init(&s) < 0)
1253 return EXIT_FAILURE;
1255 sd_event_set_watchdog(s.events, true);
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;