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 "import-util.h"
32 typedef struct Transfer Transfer;
33 typedef struct Manager Manager;
35 typedef enum TransferType {
40 _TRANSFER_TYPE_INVALID = -1,
62 char log_message[LINE_MAX];
63 size_t log_message_size;
65 sd_event_source *pid_event_source;
66 sd_event_source *log_event_source;
75 uint32_t current_transfer_id;
78 Hashmap *polkit_registry;
81 #define TRANSFERS_MAX 64
83 static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = {
84 [TRANSFER_TAR] = "tar",
85 [TRANSFER_RAW] = "raw",
86 [TRANSFER_DKR] = "dkr",
89 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(transfer_type, TransferType);
91 static Transfer *transfer_unref(Transfer *t) {
96 hashmap_remove(t->manager->transfers, UINT32_TO_PTR(t->id));
98 sd_event_source_unref(t->pid_event_source);
99 sd_event_source_unref(t->log_event_source);
103 free(t->dkr_index_url);
104 free(t->object_path);
107 (void) kill_and_sigcont(t->pid, SIGKILL);
108 (void) wait_for_terminate(t->pid, NULL);
111 safe_close(t->log_fd);
117 DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_unref);
119 static int transfer_new(Manager *m, Transfer **ret) {
120 _cleanup_(transfer_unrefp) Transfer *t = NULL;
127 if (hashmap_size(m->transfers) >= TRANSFERS_MAX)
130 r = hashmap_ensure_allocated(&m->transfers, &trivial_hash_ops);
134 t = new0(Transfer, 1);
138 t->type = _TRANSFER_TYPE_INVALID;
141 id = m->current_transfer_id + 1;
143 if (asprintf(&t->object_path, "/org/freedesktop/import1/transfer/_%" PRIu32, id) < 0)
146 r = hashmap_put(m->transfers, UINT32_TO_PTR(id), t);
150 m->current_transfer_id = id;
161 static void transfer_send_log_line(Transfer *t, const char *line) {
162 int r, priority = LOG_INFO;
167 syslog_parse_priority(&line, &priority, true);
169 log_full(priority, "(transfer%" PRIu32 "): %s", t->id, line);
171 r = sd_bus_emit_signal(
174 "org.freedesktop.import1.Transfer",
180 log_error_errno(r, "Cannot emit message: %m");
183 static void transfer_send_logs(Transfer *t, bool flush) {
186 /* Try to send out all log messages, if we can. But if we
187 * can't we remove the messages from the buffer, but don't
190 while (t->log_message_size > 0) {
191 _cleanup_free_ char *n = NULL;
194 if (t->log_message_size >= sizeof(t->log_message))
195 e = t->log_message + sizeof(t->log_message);
199 a = memchr(t->log_message, 0, t->log_message_size);
200 b = memchr(t->log_message, '\n', t->log_message_size);
214 e = t->log_message + t->log_message_size;
217 n = strndup(t->log_message, e - t->log_message);
219 /* Skip over NUL and newlines */
220 while (e < t->log_message + t->log_message_size && (*e == 0 || *e == '\n'))
223 memmove(t->log_message, e, t->log_message + sizeof(t->log_message) - e);
224 t->log_message_size -= e - t->log_message;
234 transfer_send_log_line(t, n);
238 static int transfer_finalize(Transfer *t, bool success) {
243 transfer_send_logs(t, true);
245 r = sd_bus_emit_signal(
247 "/org/freedesktop/import1",
248 "org.freedesktop.import1.Manager",
254 t->n_canceled > 0 ? "canceled" : "failed");
257 log_error_errno(r, "Cannot emit message: %m");
263 static int transfer_cancel(Transfer *t) {
268 r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL);
276 static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userdata) {
277 Transfer *t = userdata;
278 bool success = false;
283 if (si->si_code == CLD_EXITED) {
284 if (si->si_status != 0)
285 log_error("Import process failed with exit code %i.", si->si_status);
287 log_debug("Import process succeeded.");
291 } else if (si->si_code == CLD_KILLED ||
292 si->si_code == CLD_DUMPED)
294 log_error("Import process terminated by signal %s.", signal_to_string(si->si_status));
296 log_error("Import process failed due to unknown reason.");
300 return transfer_finalize(t, success);
303 static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
304 Transfer *t = userdata;
310 l = read(fd, t->log_message + t->log_message_size, sizeof(t->log_message) - t->log_message_size);
312 /* EOF/read error. We just close the pipe here, and
313 * close the watch, waiting for the SIGCHLD to arrive,
314 * before we do anything else. */
317 log_error_errno(errno, "Failed to read log message: %m");
319 t->log_event_source = sd_event_source_unref(t->log_event_source);
323 t->log_message_size += l;
325 transfer_send_logs(t, false);
330 static int transfer_start(Transfer *t) {
331 _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
337 if (pipe2(pipefd, O_CLOEXEC) < 0)
344 const char *cmd[] = {
346 t->type == TRANSFER_TAR ? "pull-tar" :
347 t->type == TRANSFER_RAW ? "pull-raw" :
350 NULL, /* verify argument */
351 NULL, /* maybe --force */
352 NULL, /* maybe --dkr-index-url */
353 NULL, /* the actual URL */
363 reset_all_signal_handlers();
365 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
367 pipefd[0] = safe_close(pipefd[0]);
369 if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) {
370 log_error_errno(errno, "Failed to dup2() fd: %m");
374 if (dup2(pipefd[1], STDERR_FILENO) != STDERR_FILENO) {
375 log_error_errno(errno, "Failed to dup2() fd: %m");
379 if (pipefd[1] != STDOUT_FILENO && pipefd[1] != STDERR_FILENO)
380 pipefd[1] = safe_close(pipefd[1]);
382 null_fd = open("/dev/null", O_RDONLY|O_NOCTTY);
384 log_error_errno(errno, "Failed to open /dev/null: %m");
388 if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) {
389 log_error_errno(errno, "Failed to dup2() fd: %m");
393 if (null_fd != STDIN_FILENO)
396 fd_cloexec(STDIN_FILENO, false);
397 fd_cloexec(STDOUT_FILENO, false);
398 fd_cloexec(STDERR_FILENO, false);
400 putenv((char*) "SYSTEMD_LOG_TARGET=console-prefixed");
402 cmd[k++] = import_verify_to_string(t->verify);
404 cmd[k++] = "--force";
406 if (t->dkr_index_url) {
407 cmd[k++] = "--dkr-index-url";
408 cmd[k++] = t->dkr_index_url;
411 cmd[k++] = t->remote;
416 execv(SYSTEMD_IMPORT_PATH, (char * const *) cmd);
417 log_error_errno(errno, "Failed to execute import tool: %m");
421 pipefd[1] = safe_close(pipefd[1]);
422 t->log_fd = pipefd[0];
425 r = sd_event_add_child(t->manager->event, &t->pid_event_source, t->pid, WEXITED, transfer_on_pid, t);
429 r = sd_event_add_io(t->manager->event, &t->log_event_source, t->log_fd, EPOLLIN, transfer_on_log, t);
433 /* Make sure always process logging before SIGCHLD */
434 r = sd_event_source_set_priority(t->log_event_source, SD_EVENT_PRIORITY_NORMAL -5);
438 r = sd_bus_emit_signal(
440 "/org/freedesktop/import1",
441 "org.freedesktop.import1.Manager",
452 static Manager *manager_unref(Manager *m) {
458 while ((t = hashmap_first(m->transfers)))
461 hashmap_free(m->transfers);
463 bus_verify_polkit_async_registry_free(m->polkit_registry);
465 sd_bus_close(m->bus);
466 sd_bus_unref(m->bus);
467 sd_event_unref(m->event);
473 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
475 static int manager_new(Manager **ret) {
476 _cleanup_(manager_unrefp) Manager *m = NULL;
481 m = new0(Manager, 1);
485 r = sd_event_default(&m->event);
489 sd_event_set_watchdog(m->event, true);
491 r = sd_bus_default_system(&m->bus);
501 static Transfer *manager_find(Manager *m, TransferType type, const char *dkr_index_url, const char *remote) {
507 assert(type < _TRANSFER_TYPE_MAX);
509 HASHMAP_FOREACH(t, m->transfers, i) {
511 if (t->type == type &&
512 streq_ptr(t->remote, remote) &&
513 streq_ptr(t->dkr_index_url, dkr_index_url))
520 static int method_pull_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
521 _cleanup_(transfer_unrefp) Transfer *t = NULL;
522 const char *remote, *local, *verify, *object;
523 Manager *m = userdata;
533 r = bus_verify_polkit_async(
536 "org.freedesktop.import1.pull",
543 return 1; /* Will call us back */
545 r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force);
549 if (!http_url_is_valid(remote))
550 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "URL %s is invalid", remote);
554 else if (!machine_name_is_valid(local))
555 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
558 v = IMPORT_VERIFY_SIGNATURE;
560 v = import_verify_from_string(verify);
562 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
564 type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_TAR : TRANSFER_RAW;
566 if (manager_find(m, type, NULL, remote))
567 return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
569 r = transfer_new(m, &t);
575 t->force_local = force;
577 t->remote = strdup(remote);
581 t->local = strdup(local);
585 r = transfer_start(t);
589 object = t->object_path;
593 return sd_bus_reply_method_return(msg, "uo", id, object);
596 static int method_pull_dkr(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
597 _cleanup_(transfer_unrefp) Transfer *t = NULL;
598 const char *index_url, *remote, *tag, *local, *verify, *object;
599 Manager *m = userdata;
608 r = bus_verify_polkit_async(
611 "org.freedesktop.import1.pull",
618 return 1; /* Will call us back */
620 r = sd_bus_message_read(msg, "sssssb", &index_url, &remote, &tag, &local, &verify, &force);
624 if (isempty(index_url))
625 index_url = DEFAULT_DKR_INDEX_URL;
627 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL must be specified.");
628 if (!http_url_is_valid(index_url))
629 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL %s is invalid", index_url);
631 if (!dkr_name_is_valid(remote))
632 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Remote name %s is not valid", remote);
636 else if (!dkr_tag_is_valid(tag))
637 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Tag %s is not valid", tag);
641 else if (!machine_name_is_valid(local))
642 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
645 v = IMPORT_VERIFY_SIGNATURE;
647 v = import_verify_from_string(verify);
649 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
651 if (v != IMPORT_VERIFY_NO)
652 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "DKR does not support verification.");
654 if (manager_find(m, TRANSFER_DKR, index_url, remote))
655 return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
657 r = transfer_new(m, &t);
661 t->type = TRANSFER_DKR;
663 t->force_local = force;
665 t->dkr_index_url = strdup(index_url);
666 if (!t->dkr_index_url)
669 t->remote = strjoin(remote, ":", tag, NULL);
673 t->local = strdup(local);
677 r = transfer_start(t);
681 object = t->object_path;
685 return sd_bus_reply_method_return(msg, "uo", id, object);
688 static int method_list_transfers(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
689 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
690 Manager *m = userdata;
699 r = sd_bus_message_new_method_return(msg, &reply);
703 r = sd_bus_message_open_container(reply, 'a', "(ussso)");
707 HASHMAP_FOREACH(t, m->transfers, i) {
709 r = sd_bus_message_append(
713 transfer_type_to_string(t->type),
721 r = sd_bus_message_close_container(reply);
725 return sd_bus_send(bus, reply, NULL);
728 static int method_cancel(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
729 Transfer *t = userdata;
736 r = bus_verify_polkit_async(
739 "org.freedesktop.import1.pull",
741 &t->manager->polkit_registry,
746 return 1; /* Will call us back */
748 r = transfer_cancel(t);
752 return sd_bus_reply_method_return(msg, NULL);
755 static int method_cancel_transfer(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
756 Manager *m = userdata;
765 r = bus_verify_polkit_async(
768 "org.freedesktop.import1.pull",
775 return 1; /* Will call us back */
777 r = sd_bus_message_read(msg, "u", &id);
781 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid transfer id");
783 t = hashmap_get(m->transfers, UINT32_TO_PTR(id));
785 return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_TRANSFER, "Not transfer by id %" PRIu32, id);
787 r = transfer_cancel(t);
791 return sd_bus_reply_method_return(msg, NULL);
794 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, transfer_type, TransferType);
795 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_verify, import_verify, ImportVerify);
797 static const sd_bus_vtable transfer_vtable[] = {
798 SD_BUS_VTABLE_START(0),
799 SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Transfer, id), SD_BUS_VTABLE_PROPERTY_CONST),
800 SD_BUS_PROPERTY("Local", "s", NULL, offsetof(Transfer, local), SD_BUS_VTABLE_PROPERTY_CONST),
801 SD_BUS_PROPERTY("Remote", "s", NULL, offsetof(Transfer, remote), SD_BUS_VTABLE_PROPERTY_CONST),
802 SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Transfer, type), SD_BUS_VTABLE_PROPERTY_CONST),
803 SD_BUS_PROPERTY("Verify", "s", property_get_verify, offsetof(Transfer, verify), SD_BUS_VTABLE_PROPERTY_CONST),
804 SD_BUS_METHOD("Cancel", NULL, NULL, method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
805 SD_BUS_SIGNAL("LogMessage", "us", 0),
809 static const sd_bus_vtable manager_vtable[] = {
810 SD_BUS_VTABLE_START(0),
811 SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
812 SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
813 SD_BUS_METHOD("PullDkr", "sssssb", "uo", method_pull_dkr, SD_BUS_VTABLE_UNPRIVILEGED),
814 SD_BUS_METHOD("ListTransfers", NULL, "a(ussso)", method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED),
815 SD_BUS_METHOD("CancelTransfer", "u", NULL, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED),
816 SD_BUS_SIGNAL("TransferNew", "uo", 0),
817 SD_BUS_SIGNAL("TransferRemoved", "uos", 0),
821 static int transfer_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
822 Manager *m = userdata;
834 p = startswith(path, "/org/freedesktop/import1/transfer/_");
838 r = safe_atou32(p, &id);
839 if (r < 0 || id == 0)
842 t = hashmap_get(m->transfers, UINT32_TO_PTR(id));
850 static int transfer_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
851 _cleanup_strv_free_ char **l = NULL;
852 Manager *m = userdata;
857 l = new0(char*, hashmap_size(m->transfers) + 1);
861 HASHMAP_FOREACH(t, m->transfers, i) {
863 l[k] = strdup(t->object_path);
876 static int manager_add_bus_objects(Manager *m) {
881 r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", manager_vtable, m);
883 return log_error_errno(r, "Failed to register object: %m");
885 r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/import1/transfer", "org.freedesktop.import1.Transfer", transfer_vtable, transfer_object_find, m);
887 return log_error_errno(r, "Failed to register object: %m");
889 r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/import1/transfer", transfer_node_enumerator, m);
891 return log_error_errno(r, "Failed to add transfer enumerator: %m");
893 r = sd_bus_request_name(m->bus, "org.freedesktop.import1", 0);
895 return log_error_errno(r, "Failed to register name: %m");
897 r = sd_bus_attach_event(m->bus, m->event, 0);
899 return log_error_errno(r, "Failed to attach bus to event loop: %m");
904 static bool manager_check_idle(void *userdata) {
905 Manager *m = userdata;
907 return hashmap_isempty(m->transfers);
910 static int manager_run(Manager *m) {
913 return bus_event_loop_with_idle(
916 "org.freedesktop.import1",
922 int main(int argc, char *argv[]) {
923 _cleanup_(manager_unrefp) Manager *m = NULL;
926 log_set_target(LOG_TARGET_AUTO);
927 log_parse_environment();
933 log_error("This program takes no arguments.");
938 assert_se(sigprocmask_many(SIG_BLOCK, SIGCHLD, -1) >= 0);
942 log_error_errno(r, "Failed to allocate manager object: %m");
946 r = manager_add_bus_objects(m);
952 log_error_errno(r, "Failed to run event loop: %m");
957 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;