1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Lennart Poettering
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/>.
22 #include <sys/prctl.h>
28 #include "bus-common-errors.h"
30 #include "socket-util.h"
32 #include "import-util.h"
35 typedef struct Transfer Transfer;
36 typedef struct Manager Manager;
38 typedef enum TransferType {
43 _TRANSFER_TYPE_INVALID = -1,
65 char log_message[LINE_MAX];
66 size_t log_message_size;
68 sd_event_source *pid_event_source;
69 sd_event_source *log_event_source;
72 unsigned progress_percent;
79 uint32_t current_transfer_id;
82 Hashmap *polkit_registry;
86 sd_event_source *notify_event_source;
89 #define TRANSFERS_MAX 64
91 static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = {
92 [TRANSFER_TAR] = "tar",
93 [TRANSFER_RAW] = "raw",
94 [TRANSFER_DKR] = "dkr",
97 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType);
99 static Transfer *transfer_unref(Transfer *t) {
104 hashmap_remove(t->manager->transfers, UINT32_TO_PTR(t->id));
106 sd_event_source_unref(t->pid_event_source);
107 sd_event_source_unref(t->log_event_source);
111 free(t->dkr_index_url);
112 free(t->object_path);
115 (void) kill_and_sigcont(t->pid, SIGKILL);
116 (void) wait_for_terminate(t->pid, NULL);
119 safe_close(t->log_fd);
125 DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_unref);
127 static int transfer_new(Manager *m, Transfer **ret) {
128 _cleanup_(transfer_unrefp) Transfer *t = NULL;
135 if (hashmap_size(m->transfers) >= TRANSFERS_MAX)
138 r = hashmap_ensure_allocated(&m->transfers, &trivial_hash_ops);
142 t = new0(Transfer, 1);
146 t->type = _TRANSFER_TYPE_INVALID;
149 id = m->current_transfer_id + 1;
151 if (asprintf(&t->object_path, "/org/freedesktop/import1/transfer/_%" PRIu32, id) < 0)
154 r = hashmap_put(m->transfers, UINT32_TO_PTR(id), t);
158 m->current_transfer_id = id;
169 static void transfer_send_log_line(Transfer *t, const char *line) {
170 int r, priority = LOG_INFO;
175 syslog_parse_priority(&line, &priority, true);
177 log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line);
179 r = sd_bus_emit_signal(
182 "org.freedesktop.import1.Transfer",
188 log_error_errno(r, "Cannot emit message: %m");
191 static void transfer_send_logs(Transfer *t, bool flush) {
194 /* Try to send out all log messages, if we can. But if we
195 * can't we remove the messages from the buffer, but don't
198 while (t->log_message_size > 0) {
199 _cleanup_free_ char *n = NULL;
202 if (t->log_message_size >= sizeof(t->log_message))
203 e = t->log_message + sizeof(t->log_message);
207 a = memchr(t->log_message, 0, t->log_message_size);
208 b = memchr(t->log_message, '\n', t->log_message_size);
222 e = t->log_message + t->log_message_size;
225 n = strndup(t->log_message, e - t->log_message);
227 /* Skip over NUL and newlines */
228 while (e < t->log_message + t->log_message_size && (*e == 0 || *e == '\n'))
231 memmove(t->log_message, e, t->log_message + sizeof(t->log_message) - e);
232 t->log_message_size -= e - t->log_message;
242 transfer_send_log_line(t, n);
246 static int transfer_finalize(Transfer *t, bool success) {
251 transfer_send_logs(t, true);
253 r = sd_bus_emit_signal(
255 "/org/freedesktop/import1",
256 "org.freedesktop.import1.Manager",
262 t->n_canceled > 0 ? "canceled" : "failed");
265 log_error_errno(r, "Cannot emit message: %m");
271 static int transfer_cancel(Transfer *t) {
276 r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL);
284 static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userdata) {
285 Transfer *t = userdata;
286 bool success = false;
291 if (si->si_code == CLD_EXITED) {
292 if (si->si_status != 0)
293 log_error("Import process failed with exit code %i.", si->si_status);
295 log_debug("Import process succeeded.");
299 } else if (si->si_code == CLD_KILLED ||
300 si->si_code == CLD_DUMPED)
302 log_error("Import process terminated by signal %s.", signal_to_string(si->si_status));
304 log_error("Import process failed due to unknown reason.");
308 return transfer_finalize(t, success);
311 static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
312 Transfer *t = userdata;
318 l = read(fd, t->log_message + t->log_message_size, sizeof(t->log_message) - t->log_message_size);
320 /* EOF/read error. We just close the pipe here, and
321 * close the watch, waiting for the SIGCHLD to arrive,
322 * before we do anything else. */
325 log_error_errno(errno, "Failed to read log message: %m");
327 t->log_event_source = sd_event_source_unref(t->log_event_source);
331 t->log_message_size += l;
333 transfer_send_logs(t, false);
338 static int transfer_start(Transfer *t) {
339 _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
345 if (pipe2(pipefd, O_CLOEXEC) < 0)
352 const char *cmd[] = {
354 transfer_type_to_string(t->type),
356 NULL, /* verify argument */
357 NULL, /* maybe --force */
358 NULL, /* maybe --dkr-index-url */
359 NULL, /* the actual URL */
369 reset_all_signal_handlers();
371 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
373 pipefd[0] = safe_close(pipefd[0]);
375 if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) {
376 log_error_errno(errno, "Failed to dup2() fd: %m");
380 if (dup2(pipefd[1], STDERR_FILENO) != STDERR_FILENO) {
381 log_error_errno(errno, "Failed to dup2() fd: %m");
385 if (pipefd[1] != STDOUT_FILENO && pipefd[1] != STDERR_FILENO)
386 pipefd[1] = safe_close(pipefd[1]);
388 null_fd = open("/dev/null", O_RDONLY|O_NOCTTY);
390 log_error_errno(errno, "Failed to open /dev/null: %m");
394 if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) {
395 log_error_errno(errno, "Failed to dup2() fd: %m");
399 if (null_fd != STDIN_FILENO)
402 fd_cloexec(STDIN_FILENO, false);
403 fd_cloexec(STDOUT_FILENO, false);
404 fd_cloexec(STDERR_FILENO, false);
406 setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1);
407 setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1);
409 cmd[k++] = import_verify_to_string(t->verify);
411 cmd[k++] = "--force";
413 if (t->dkr_index_url) {
414 cmd[k++] = "--dkr-index-url";
415 cmd[k++] = t->dkr_index_url;
418 cmd[k++] = t->remote;
423 execv(SYSTEMD_PULL_PATH, (char * const *) cmd);
424 log_error_errno(errno, "Failed to execute import tool: %m");
428 pipefd[1] = safe_close(pipefd[1]);
429 t->log_fd = pipefd[0];
432 r = sd_event_add_child(t->manager->event, &t->pid_event_source, t->pid, WEXITED, transfer_on_pid, t);
436 r = sd_event_add_io(t->manager->event, &t->log_event_source, t->log_fd, EPOLLIN, transfer_on_log, t);
440 /* Make sure always process logging before SIGCHLD */
441 r = sd_event_source_set_priority(t->log_event_source, SD_EVENT_PRIORITY_NORMAL -5);
445 r = sd_bus_emit_signal(
447 "/org/freedesktop/import1",
448 "org.freedesktop.import1.Manager",
459 static Manager *manager_unref(Manager *m) {
465 sd_event_source_unref(m->notify_event_source);
466 safe_close(m->notify_fd);
468 while ((t = hashmap_first(m->transfers)))
471 hashmap_free(m->transfers);
473 bus_verify_polkit_async_registry_free(m->polkit_registry);
475 sd_bus_close(m->bus);
476 sd_bus_unref(m->bus);
477 sd_event_unref(m->event);
483 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
485 static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
487 char buf[NOTIFY_BUFFER_MAX+1];
488 struct iovec iovec = {
490 .iov_len = sizeof(buf)-1,
493 struct cmsghdr cmsghdr;
494 uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
495 CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)];
497 struct msghdr msghdr = {
500 .msg_control = &control,
501 .msg_controllen = sizeof(control),
503 struct ucred *ucred = NULL;
504 Manager *m = userdata;
505 struct cmsghdr *cmsg;
513 n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
515 if (errno == EAGAIN || errno == EINTR)
521 for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg; cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
522 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
523 close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
524 log_warning("Somebody sent us unexpected fds, ignoring.");
526 } else if (cmsg->cmsg_level == SOL_SOCKET &&
527 cmsg->cmsg_type == SCM_CREDENTIALS &&
528 cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
530 ucred = (struct ucred*) CMSG_DATA(cmsg);
534 if (msghdr.msg_flags & MSG_TRUNC) {
535 log_warning("Got overly long notification datagram, ignoring.");
539 if (!ucred || ucred->pid <= 0) {
540 log_warning("Got notification datagram lacking credential information, ignoring.");
544 HASHMAP_FOREACH(t, m->transfers, i)
545 if (ucred->pid == t->pid)
549 log_warning("Got notification datagram from unexpected peer, ignoring.");
555 p = startswith(buf, "X_IMPORT_PROGRESS=");
557 p = strstr(buf, "\nX_IMPORT_PROGRESS=");
564 e = strchrnul(p, '\n');
567 r = safe_atou(p, &percent);
568 if (r < 0 || percent > 100) {
569 log_warning("Got invalid percent value, ignoring.");
573 t->progress_percent = percent;
575 log_debug("Got percentage from client: %u%%", percent);
579 static int manager_new(Manager **ret) {
580 _cleanup_(manager_unrefp) Manager *m = NULL;
581 static const union sockaddr_union sa = {
582 .un.sun_family = AF_UNIX,
583 .un.sun_path = "/run/systemd/import/notify",
585 static const int one = 1;
590 m = new0(Manager, 1);
594 r = sd_event_default(&m->event);
598 sd_event_set_watchdog(m->event, true);
600 r = sd_bus_default_system(&m->bus);
604 m->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
605 if (m->notify_fd < 0)
608 (void) mkdir_parents_label(sa.un.sun_path, 0755);
609 (void) unlink(sa.un.sun_path);
611 if (bind(m->notify_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)) < 0)
614 if (setsockopt(m->notify_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0)
617 r = sd_event_add_io(m->event, &m->notify_event_source, m->notify_fd, EPOLLIN, manager_on_notify, m);
627 static Transfer *manager_find(Manager *m, TransferType type, const char *dkr_index_url, const char *remote) {
633 assert(type < _TRANSFER_TYPE_MAX);
635 HASHMAP_FOREACH(t, m->transfers, i) {
637 if (t->type == type &&
638 streq_ptr(t->remote, remote) &&
639 streq_ptr(t->dkr_index_url, dkr_index_url))
646 static int method_pull_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
647 _cleanup_(transfer_unrefp) Transfer *t = NULL;
648 const char *remote, *local, *verify, *object;
649 Manager *m = userdata;
659 r = bus_verify_polkit_async(
662 "org.freedesktop.import1.pull",
670 return 1; /* Will call us back */
672 r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force);
676 if (!http_url_is_valid(remote))
677 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "URL %s is invalid", remote);
681 else if (!machine_name_is_valid(local))
682 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
685 v = IMPORT_VERIFY_SIGNATURE;
687 v = import_verify_from_string(verify);
689 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
691 type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_TAR : TRANSFER_RAW;
693 if (manager_find(m, type, NULL, remote))
694 return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
696 r = transfer_new(m, &t);
702 t->force_local = force;
704 t->remote = strdup(remote);
708 t->local = strdup(local);
712 r = transfer_start(t);
716 object = t->object_path;
720 return sd_bus_reply_method_return(msg, "uo", id, object);
723 static int method_pull_dkr(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
724 _cleanup_(transfer_unrefp) Transfer *t = NULL;
725 const char *index_url, *remote, *tag, *local, *verify, *object;
726 Manager *m = userdata;
735 r = bus_verify_polkit_async(
738 "org.freedesktop.import1.pull",
746 return 1; /* Will call us back */
748 r = sd_bus_message_read(msg, "sssssb", &index_url, &remote, &tag, &local, &verify, &force);
752 if (isempty(index_url))
753 index_url = DEFAULT_DKR_INDEX_URL;
755 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL must be specified.");
756 if (!http_url_is_valid(index_url))
757 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL %s is invalid", index_url);
759 if (!dkr_name_is_valid(remote))
760 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Remote name %s is not valid", remote);
764 else if (!dkr_tag_is_valid(tag))
765 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Tag %s is not valid", tag);
769 else if (!machine_name_is_valid(local))
770 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
773 v = IMPORT_VERIFY_SIGNATURE;
775 v = import_verify_from_string(verify);
777 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
779 if (v != IMPORT_VERIFY_NO)
780 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "DKR does not support verification.");
782 if (manager_find(m, TRANSFER_DKR, index_url, remote))
783 return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
785 r = transfer_new(m, &t);
789 t->type = TRANSFER_DKR;
791 t->force_local = force;
793 t->dkr_index_url = strdup(index_url);
794 if (!t->dkr_index_url)
797 t->remote = strjoin(remote, ":", tag, NULL);
801 t->local = strdup(local);
805 r = transfer_start(t);
809 object = t->object_path;
813 return sd_bus_reply_method_return(msg, "uo", id, object);
816 static int method_list_transfers(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
817 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
818 Manager *m = userdata;
827 r = sd_bus_message_new_method_return(msg, &reply);
831 r = sd_bus_message_open_container(reply, 'a', "(usssdo)");
835 HASHMAP_FOREACH(t, m->transfers, i) {
837 r = sd_bus_message_append(
841 transfer_type_to_string(t->type),
844 (double) t->progress_percent / 100.0,
850 r = sd_bus_message_close_container(reply);
854 return sd_bus_send(bus, reply, NULL);
857 static int method_cancel(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
858 Transfer *t = userdata;
865 r = bus_verify_polkit_async(
868 "org.freedesktop.import1.pull",
871 &t->manager->polkit_registry,
876 return 1; /* Will call us back */
878 r = transfer_cancel(t);
882 return sd_bus_reply_method_return(msg, NULL);
885 static int method_cancel_transfer(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
886 Manager *m = userdata;
895 r = bus_verify_polkit_async(
898 "org.freedesktop.import1.pull",
906 return 1; /* Will call us back */
908 r = sd_bus_message_read(msg, "u", &id);
912 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid transfer id");
914 t = hashmap_get(m->transfers, UINT32_TO_PTR(id));
916 return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_TRANSFER, "No transfer by id %" PRIu32, id);
918 r = transfer_cancel(t);
922 return sd_bus_reply_method_return(msg, NULL);
925 static int property_get_progress(
928 const char *interface,
929 const char *property,
930 sd_bus_message *reply,
932 sd_bus_error *error) {
934 Transfer *t = userdata;
940 return sd_bus_message_append(reply, "d", (double) t->progress_percent / 100.0);
943 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, transfer_type, TransferType);
944 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_verify, import_verify, ImportVerify);
946 static const sd_bus_vtable transfer_vtable[] = {
947 SD_BUS_VTABLE_START(0),
948 SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Transfer, id), SD_BUS_VTABLE_PROPERTY_CONST),
949 SD_BUS_PROPERTY("Local", "s", NULL, offsetof(Transfer, local), SD_BUS_VTABLE_PROPERTY_CONST),
950 SD_BUS_PROPERTY("Remote", "s", NULL, offsetof(Transfer, remote), SD_BUS_VTABLE_PROPERTY_CONST),
951 SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Transfer, type), SD_BUS_VTABLE_PROPERTY_CONST),
952 SD_BUS_PROPERTY("Verify", "s", property_get_verify, offsetof(Transfer, verify), SD_BUS_VTABLE_PROPERTY_CONST),
953 SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0),
954 SD_BUS_METHOD("Cancel", NULL, NULL, method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
955 SD_BUS_SIGNAL("LogMessage", "us", 0),
959 static const sd_bus_vtable manager_vtable[] = {
960 SD_BUS_VTABLE_START(0),
961 SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
962 SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
963 SD_BUS_METHOD("PullDkr", "sssssb", "uo", method_pull_dkr, SD_BUS_VTABLE_UNPRIVILEGED),
964 SD_BUS_METHOD("ListTransfers", NULL, "a(usssdo)", method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED),
965 SD_BUS_METHOD("CancelTransfer", "u", NULL, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED),
966 SD_BUS_SIGNAL("TransferNew", "uo", 0),
967 SD_BUS_SIGNAL("TransferRemoved", "uos", 0),
971 static int transfer_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
972 Manager *m = userdata;
984 p = startswith(path, "/org/freedesktop/import1/transfer/_");
988 r = safe_atou32(p, &id);
989 if (r < 0 || id == 0)
992 t = hashmap_get(m->transfers, UINT32_TO_PTR(id));
1000 static int transfer_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
1001 _cleanup_strv_free_ char **l = NULL;
1002 Manager *m = userdata;
1007 l = new0(char*, hashmap_size(m->transfers) + 1);
1011 HASHMAP_FOREACH(t, m->transfers, i) {
1013 l[k] = strdup(t->object_path);
1026 static int manager_add_bus_objects(Manager *m) {
1031 r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", manager_vtable, m);
1033 return log_error_errno(r, "Failed to register object: %m");
1035 r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/import1/transfer", "org.freedesktop.import1.Transfer", transfer_vtable, transfer_object_find, m);
1037 return log_error_errno(r, "Failed to register object: %m");
1039 r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/import1/transfer", transfer_node_enumerator, m);
1041 return log_error_errno(r, "Failed to add transfer enumerator: %m");
1043 r = sd_bus_request_name(m->bus, "org.freedesktop.import1", 0);
1045 return log_error_errno(r, "Failed to register name: %m");
1047 r = sd_bus_attach_event(m->bus, m->event, 0);
1049 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1054 static bool manager_check_idle(void *userdata) {
1055 Manager *m = userdata;
1057 return hashmap_isempty(m->transfers);
1060 static int manager_run(Manager *m) {
1063 return bus_event_loop_with_idle(
1066 "org.freedesktop.import1",
1072 int main(int argc, char *argv[]) {
1073 _cleanup_(manager_unrefp) Manager *m = NULL;
1076 log_set_target(LOG_TARGET_AUTO);
1077 log_parse_environment();
1083 log_error("This program takes no arguments.");
1088 assert_se(sigprocmask_many(SIG_BLOCK, SIGCHLD, -1) >= 0);
1090 r = manager_new(&m);
1092 log_error_errno(r, "Failed to allocate manager object: %m");
1096 r = manager_add_bus_objects(m);
1102 log_error_errno(r, "Failed to run event loop: %m");
1107 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;