]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/importd.c
importd: when listing transfers, show progress percentage
[thirdparty/systemd.git] / src / import / importd.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2015 Lennart Poettering
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <sys/prctl.h>
23
24 #include "sd-bus.h"
25 #include "util.h"
26 #include "strv.h"
27 #include "bus-util.h"
28 #include "bus-common-errors.h"
29 #include "def.h"
30 #include "socket-util.h"
31 #include "mkdir.h"
32 #include "import-util.h"
33
34 typedef struct Transfer Transfer;
35 typedef struct Manager Manager;
36
37 typedef enum TransferType {
38 TRANSFER_TAR,
39 TRANSFER_RAW,
40 TRANSFER_DKR,
41 _TRANSFER_TYPE_MAX,
42 _TRANSFER_TYPE_INVALID = -1,
43 } TransferType;
44
45 struct Transfer {
46 Manager *manager;
47
48 uint32_t id;
49 char *object_path;
50
51 TransferType type;
52 ImportVerify verify;
53
54 char *remote;
55 char *local;
56 bool force_local;
57
58 char *dkr_index_url;
59
60 pid_t pid;
61
62 int log_fd;
63
64 char log_message[LINE_MAX];
65 size_t log_message_size;
66
67 sd_event_source *pid_event_source;
68 sd_event_source *log_event_source;
69
70 unsigned n_canceled;
71 unsigned progress_percent;
72 };
73
74 struct Manager {
75 sd_event *event;
76 sd_bus *bus;
77
78 uint32_t current_transfer_id;
79 Hashmap *transfers;
80
81 Hashmap *polkit_registry;
82
83 int notify_fd;
84
85 sd_event_source *notify_event_source;
86 };
87
88 #define TRANSFERS_MAX 64
89
90 static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = {
91 [TRANSFER_TAR] = "tar",
92 [TRANSFER_RAW] = "raw",
93 [TRANSFER_DKR] = "dkr",
94 };
95
96 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType);
97
98 static Transfer *transfer_unref(Transfer *t) {
99 if (!t)
100 return NULL;
101
102 if (t->manager)
103 hashmap_remove(t->manager->transfers, UINT32_TO_PTR(t->id));
104
105 sd_event_source_unref(t->pid_event_source);
106 sd_event_source_unref(t->log_event_source);
107
108 free(t->remote);
109 free(t->local);
110 free(t->dkr_index_url);
111 free(t->object_path);
112
113 if (t->pid > 0) {
114 (void) kill_and_sigcont(t->pid, SIGKILL);
115 (void) wait_for_terminate(t->pid, NULL);
116 }
117
118 safe_close(t->log_fd);
119
120 free(t);
121 return NULL;
122 }
123
124 DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_unref);
125
126 static int transfer_new(Manager *m, Transfer **ret) {
127 _cleanup_(transfer_unrefp) Transfer *t = NULL;
128 uint32_t id;
129 int r;
130
131 assert(m);
132 assert(ret);
133
134 if (hashmap_size(m->transfers) >= TRANSFERS_MAX)
135 return -E2BIG;
136
137 r = hashmap_ensure_allocated(&m->transfers, &trivial_hash_ops);
138 if (r < 0)
139 return r;
140
141 t = new0(Transfer, 1);
142 if (!t)
143 return -ENOMEM;
144
145 t->type = _TRANSFER_TYPE_INVALID;
146 t->log_fd = -1;
147
148 id = m->current_transfer_id + 1;
149
150 if (asprintf(&t->object_path, "/org/freedesktop/import1/transfer/_%" PRIu32, id) < 0)
151 return -ENOMEM;
152
153 r = hashmap_put(m->transfers, UINT32_TO_PTR(id), t);
154 if (r < 0)
155 return r;
156
157 m->current_transfer_id = id;
158
159 t->manager = m;
160 t->id = id;
161
162 *ret = t;
163 t = NULL;
164
165 return 0;
166 }
167
168 static void transfer_send_log_line(Transfer *t, const char *line) {
169 int r, priority = LOG_INFO;
170
171 assert(t);
172 assert(line);
173
174 syslog_parse_priority(&line, &priority, true);
175
176 log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line);
177
178 r = sd_bus_emit_signal(
179 t->manager->bus,
180 t->object_path,
181 "org.freedesktop.import1.Transfer",
182 "LogMessage",
183 "us",
184 priority,
185 line);
186 if (r < 0)
187 log_error_errno(r, "Cannot emit message: %m");
188 }
189
190 static void transfer_send_logs(Transfer *t, bool flush) {
191 assert(t);
192
193 /* Try to send out all log messages, if we can. But if we
194 * can't we remove the messages from the buffer, but don't
195 * fail */
196
197 while (t->log_message_size > 0) {
198 _cleanup_free_ char *n = NULL;
199 char *e;
200
201 if (t->log_message_size >= sizeof(t->log_message))
202 e = t->log_message + sizeof(t->log_message);
203 else {
204 char *a, *b;
205
206 a = memchr(t->log_message, 0, t->log_message_size);
207 b = memchr(t->log_message, '\n', t->log_message_size);
208
209 if (a && b)
210 e = a < b ? a : b;
211 else if (a)
212 e = a;
213 else
214 e = b;
215 }
216
217 if (!e) {
218 if (!flush)
219 return;
220
221 e = t->log_message + t->log_message_size;
222 }
223
224 n = strndup(t->log_message, e - t->log_message);
225
226 /* Skip over NUL and newlines */
227 while (e < t->log_message + t->log_message_size && (*e == 0 || *e == '\n'))
228 e++;
229
230 memmove(t->log_message, e, t->log_message + sizeof(t->log_message) - e);
231 t->log_message_size -= e - t->log_message;
232
233 if (!n) {
234 log_oom();
235 continue;
236 }
237
238 if (isempty(n))
239 continue;
240
241 transfer_send_log_line(t, n);
242 }
243 }
244
245 static int transfer_finalize(Transfer *t, bool success) {
246 int r;
247
248 assert(t);
249
250 transfer_send_logs(t, true);
251
252 r = sd_bus_emit_signal(
253 t->manager->bus,
254 "/org/freedesktop/import1",
255 "org.freedesktop.import1.Manager",
256 "TransferRemoved",
257 "uos",
258 t->id,
259 t->object_path,
260 success ? "done" :
261 t->n_canceled > 0 ? "canceled" : "failed");
262
263 if (r < 0)
264 log_error_errno(r, "Cannot emit message: %m");
265
266 transfer_unref(t);
267 return 0;
268 }
269
270 static int transfer_cancel(Transfer *t) {
271 int r;
272
273 assert(t);
274
275 r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL);
276 if (r < 0)
277 return r;
278
279 t->n_canceled++;
280 return 0;
281 }
282
283 static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userdata) {
284 Transfer *t = userdata;
285 bool success = false;
286
287 assert(s);
288 assert(t);
289
290 if (si->si_code == CLD_EXITED) {
291 if (si->si_status != 0)
292 log_error("Import process failed with exit code %i.", si->si_status);
293 else {
294 log_debug("Import process succeeded.");
295 success = true;
296 }
297
298 } else if (si->si_code == CLD_KILLED ||
299 si->si_code == CLD_DUMPED)
300
301 log_error("Import process terminated by signal %s.", signal_to_string(si->si_status));
302 else
303 log_error("Import process failed due to unknown reason.");
304
305 t->pid = 0;
306
307 return transfer_finalize(t, success);
308 }
309
310 static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
311 Transfer *t = userdata;
312 ssize_t l;
313
314 assert(s);
315 assert(t);
316
317 l = read(fd, t->log_message + t->log_message_size, sizeof(t->log_message) - t->log_message_size);
318 if (l <= 0) {
319 /* EOF/read error. We just close the pipe here, and
320 * close the watch, waiting for the SIGCHLD to arrive,
321 * before we do anything else. */
322
323 if (l < 0)
324 log_error_errno(errno, "Failed to read log message: %m");
325
326 t->log_event_source = sd_event_source_unref(t->log_event_source);
327 return 0;
328 }
329
330 t->log_message_size += l;
331
332 transfer_send_logs(t, false);
333
334 return 0;
335 }
336
337 static int transfer_start(Transfer *t) {
338 _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
339 int r;
340
341 assert(t);
342 assert(t->pid <= 0);
343
344 if (pipe2(pipefd, O_CLOEXEC) < 0)
345 return -errno;
346
347 t->pid = fork();
348 if (t->pid < 0)
349 return -errno;
350 if (t->pid == 0) {
351 const char *cmd[] = {
352 "systemd-pull",
353 transfer_type_to_string(t->type),
354 "--verify",
355 NULL, /* verify argument */
356 NULL, /* maybe --force */
357 NULL, /* maybe --dkr-index-url */
358 NULL, /* the actual URL */
359 NULL, /* remote */
360 NULL, /* local */
361 NULL
362 };
363 int null_fd;
364 unsigned k = 3;
365
366 /* Child */
367
368 reset_all_signal_handlers();
369 reset_signal_mask();
370 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
371
372 pipefd[0] = safe_close(pipefd[0]);
373
374 if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) {
375 log_error_errno(errno, "Failed to dup2() fd: %m");
376 _exit(EXIT_FAILURE);
377 }
378
379 if (dup2(pipefd[1], STDERR_FILENO) != STDERR_FILENO) {
380 log_error_errno(errno, "Failed to dup2() fd: %m");
381 _exit(EXIT_FAILURE);
382 }
383
384 if (pipefd[1] != STDOUT_FILENO && pipefd[1] != STDERR_FILENO)
385 pipefd[1] = safe_close(pipefd[1]);
386
387 null_fd = open("/dev/null", O_RDONLY|O_NOCTTY);
388 if (null_fd < 0) {
389 log_error_errno(errno, "Failed to open /dev/null: %m");
390 _exit(EXIT_FAILURE);
391 }
392
393 if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) {
394 log_error_errno(errno, "Failed to dup2() fd: %m");
395 _exit(EXIT_FAILURE);
396 }
397
398 if (null_fd != STDIN_FILENO)
399 safe_close(null_fd);
400
401 fd_cloexec(STDIN_FILENO, false);
402 fd_cloexec(STDOUT_FILENO, false);
403 fd_cloexec(STDERR_FILENO, false);
404
405 setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1);
406 setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1);
407
408 cmd[k++] = import_verify_to_string(t->verify);
409 if (t->force_local)
410 cmd[k++] = "--force";
411
412 if (t->dkr_index_url) {
413 cmd[k++] = "--dkr-index-url";
414 cmd[k++] = t->dkr_index_url;
415 }
416
417 cmd[k++] = t->remote;
418 if (t->local)
419 cmd[k++] = t->local;
420 cmd[k] = NULL;
421
422 execv(SYSTEMD_PULL_PATH, (char * const *) cmd);
423 log_error_errno(errno, "Failed to execute import tool: %m");
424 _exit(EXIT_FAILURE);
425 }
426
427 pipefd[1] = safe_close(pipefd[1]);
428 t->log_fd = pipefd[0];
429 pipefd[0] = -1;
430
431 r = sd_event_add_child(t->manager->event, &t->pid_event_source, t->pid, WEXITED, transfer_on_pid, t);
432 if (r < 0)
433 return r;
434
435 r = sd_event_add_io(t->manager->event, &t->log_event_source, t->log_fd, EPOLLIN, transfer_on_log, t);
436 if (r < 0)
437 return r;
438
439 /* Make sure always process logging before SIGCHLD */
440 r = sd_event_source_set_priority(t->log_event_source, SD_EVENT_PRIORITY_NORMAL -5);
441 if (r < 0)
442 return r;
443
444 r = sd_bus_emit_signal(
445 t->manager->bus,
446 "/org/freedesktop/import1",
447 "org.freedesktop.import1.Manager",
448 "TransferNew",
449 "uo",
450 t->id,
451 t->object_path);
452 if (r < 0)
453 return r;
454
455 return 0;
456 }
457
458 static Manager *manager_unref(Manager *m) {
459 Transfer *t;
460
461 if (!m)
462 return NULL;
463
464 sd_event_source_unref(m->notify_event_source);
465 safe_close(m->notify_fd);
466
467 while ((t = hashmap_first(m->transfers)))
468 transfer_unref(t);
469
470 hashmap_free(m->transfers);
471
472 bus_verify_polkit_async_registry_free(m->polkit_registry);
473
474 sd_bus_close(m->bus);
475 sd_bus_unref(m->bus);
476 sd_event_unref(m->event);
477
478 free(m);
479 return NULL;
480 }
481
482 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
483
484 static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
485
486 char buf[NOTIFY_BUFFER_MAX+1];
487 struct iovec iovec = {
488 .iov_base = buf,
489 .iov_len = sizeof(buf)-1,
490 };
491 union {
492 struct cmsghdr cmsghdr;
493 uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
494 CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)];
495 } control = {};
496 struct msghdr msghdr = {
497 .msg_iov = &iovec,
498 .msg_iovlen = 1,
499 .msg_control = &control,
500 .msg_controllen = sizeof(control),
501 };
502 struct ucred *ucred = NULL;
503 Manager *m = userdata;
504 struct cmsghdr *cmsg;
505 unsigned percent;
506 char *p, *e;
507 Transfer *t;
508 Iterator i;
509 ssize_t n;
510 int r;
511
512 n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
513 if (n < 0) {
514 if (errno == EAGAIN || errno == EINTR)
515 return 0;
516
517 return -errno;
518 }
519
520 for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg; cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
521 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
522 close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
523 log_warning("Somebody sent us unexpected fds, ignoring.");
524 return 0;
525 } else if (cmsg->cmsg_level == SOL_SOCKET &&
526 cmsg->cmsg_type == SCM_CREDENTIALS &&
527 cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
528
529 ucred = (struct ucred*) CMSG_DATA(cmsg);
530 }
531 }
532
533 if (msghdr.msg_flags & MSG_TRUNC) {
534 log_warning("Got overly long notification datagram, ignoring.");
535 return 0;
536 }
537
538 if (!ucred || ucred->pid <= 0) {
539 log_warning("Got notification datagram lacking credential information, ignoring.");
540 return 0;
541 }
542
543 HASHMAP_FOREACH(t, m->transfers, i)
544 if (ucred->pid == t->pid)
545 break;
546
547 if (!t) {
548 log_warning("Got notification datagram from unexpected peer, ignoring.");
549 return 0;
550 }
551
552 buf[n] = 0;
553
554 p = startswith(buf, "X_IMPORT_PROGRESS=");
555 if (!p) {
556 p = strstr(buf, "\nX_IMPORT_PROGRESS=");
557 if (!p)
558 return 0;
559
560 p += 19;
561 }
562
563 e = strchrnul(p, '\n');
564 *e = 0;
565
566 r = safe_atou(p, &percent);
567 if (r < 0 || percent > 100) {
568 log_warning("Got invalid percent value, ignoring.");
569 return 0;
570 }
571
572 t->progress_percent = percent;
573
574 log_debug("Got percentage from client: %u%%", percent);
575 return 0;
576 }
577
578 static int manager_new(Manager **ret) {
579 _cleanup_(manager_unrefp) Manager *m = NULL;
580 static const union sockaddr_union sa = {
581 .un.sun_family = AF_UNIX,
582 .un.sun_path = "/run/systemd/import/notify",
583 };
584 static const int one = 1;
585 int r;
586
587 assert(ret);
588
589 m = new0(Manager, 1);
590 if (!m)
591 return -ENOMEM;
592
593 r = sd_event_default(&m->event);
594 if (r < 0)
595 return r;
596
597 sd_event_set_watchdog(m->event, true);
598
599 r = sd_bus_default_system(&m->bus);
600 if (r < 0)
601 return r;
602
603 m->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
604 if (m->notify_fd < 0)
605 return -errno;
606
607 (void) mkdir_parents_label(sa.un.sun_path, 0755);
608 (void) unlink(sa.un.sun_path);
609
610 if (bind(m->notify_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)) < 0)
611 return -errno;
612
613 if (setsockopt(m->notify_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0)
614 return -errno;
615
616 r = sd_event_add_io(m->event, &m->notify_event_source, m->notify_fd, EPOLLIN, manager_on_notify, m);
617 if (r < 0)
618 return r;
619
620 *ret = m;
621 m = NULL;
622
623 return 0;
624 }
625
626 static Transfer *manager_find(Manager *m, TransferType type, const char *dkr_index_url, const char *remote) {
627 Transfer *t;
628 Iterator i;
629
630 assert(m);
631 assert(type >= 0);
632 assert(type < _TRANSFER_TYPE_MAX);
633
634 HASHMAP_FOREACH(t, m->transfers, i) {
635
636 if (t->type == type &&
637 streq_ptr(t->remote, remote) &&
638 streq_ptr(t->dkr_index_url, dkr_index_url))
639 return t;
640 }
641
642 return NULL;
643 }
644
645 static int method_pull_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
646 _cleanup_(transfer_unrefp) Transfer *t = NULL;
647 const char *remote, *local, *verify, *object;
648 Manager *m = userdata;
649 ImportVerify v;
650 TransferType type;
651 int force, r;
652 uint32_t id;
653
654 assert(bus);
655 assert(msg);
656 assert(m);
657
658 r = bus_verify_polkit_async(
659 msg,
660 CAP_SYS_ADMIN,
661 "org.freedesktop.import1.pull",
662 false,
663 &m->polkit_registry,
664 error);
665 if (r < 0)
666 return r;
667 if (r == 0)
668 return 1; /* Will call us back */
669
670 r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force);
671 if (r < 0)
672 return r;
673
674 if (!http_url_is_valid(remote))
675 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "URL %s is invalid", remote);
676
677 if (isempty(local))
678 local = NULL;
679 else if (!machine_name_is_valid(local))
680 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
681
682 if (isempty(verify))
683 v = IMPORT_VERIFY_SIGNATURE;
684 else
685 v = import_verify_from_string(verify);
686 if (v < 0)
687 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
688
689 type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_TAR : TRANSFER_RAW;
690
691 if (manager_find(m, type, NULL, remote))
692 return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
693
694 r = transfer_new(m, &t);
695 if (r < 0)
696 return r;
697
698 t->type = type;
699 t->verify = v;
700 t->force_local = force;
701
702 t->remote = strdup(remote);
703 if (!t->remote)
704 return -ENOMEM;
705
706 t->local = strdup(local);
707 if (!t->local)
708 return -ENOMEM;
709
710 r = transfer_start(t);
711 if (r < 0)
712 return r;
713
714 object = t->object_path;
715 id = t->id;
716 t = NULL;
717
718 return sd_bus_reply_method_return(msg, "uo", id, object);
719 }
720
721 static int method_pull_dkr(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
722 _cleanup_(transfer_unrefp) Transfer *t = NULL;
723 const char *index_url, *remote, *tag, *local, *verify, *object;
724 Manager *m = userdata;
725 ImportVerify v;
726 int force, r;
727 uint32_t id;
728
729 assert(bus);
730 assert(msg);
731 assert(m);
732
733 r = bus_verify_polkit_async(
734 msg,
735 CAP_SYS_ADMIN,
736 "org.freedesktop.import1.pull",
737 false,
738 &m->polkit_registry,
739 error);
740 if (r < 0)
741 return r;
742 if (r == 0)
743 return 1; /* Will call us back */
744
745 r = sd_bus_message_read(msg, "sssssb", &index_url, &remote, &tag, &local, &verify, &force);
746 if (r < 0)
747 return r;
748
749 if (isempty(index_url))
750 index_url = DEFAULT_DKR_INDEX_URL;
751 if (!index_url)
752 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL must be specified.");
753 if (!http_url_is_valid(index_url))
754 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL %s is invalid", index_url);
755
756 if (!dkr_name_is_valid(remote))
757 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Remote name %s is not valid", remote);
758
759 if (isempty(tag))
760 tag = "latest";
761 else if (!dkr_tag_is_valid(tag))
762 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Tag %s is not valid", tag);
763
764 if (isempty(local))
765 local = NULL;
766 else if (!machine_name_is_valid(local))
767 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
768
769 if (isempty(verify))
770 v = IMPORT_VERIFY_SIGNATURE;
771 else
772 v = import_verify_from_string(verify);
773 if (v < 0)
774 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
775
776 if (v != IMPORT_VERIFY_NO)
777 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "DKR does not support verification.");
778
779 if (manager_find(m, TRANSFER_DKR, index_url, remote))
780 return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
781
782 r = transfer_new(m, &t);
783 if (r < 0)
784 return r;
785
786 t->type = TRANSFER_DKR;
787 t->verify = v;
788 t->force_local = force;
789
790 t->dkr_index_url = strdup(index_url);
791 if (!t->dkr_index_url)
792 return -ENOMEM;
793
794 t->remote = strjoin(remote, ":", tag, NULL);
795 if (!t->remote)
796 return -ENOMEM;
797
798 t->local = strdup(local);
799 if (!t->local)
800 return -ENOMEM;
801
802 r = transfer_start(t);
803 if (r < 0)
804 return r;
805
806 object = t->object_path;
807 id = t->id;
808 t = NULL;
809
810 return sd_bus_reply_method_return(msg, "uo", id, object);
811 }
812
813 static int method_list_transfers(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
814 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
815 Manager *m = userdata;
816 Transfer *t;
817 Iterator i;
818 int r;
819
820 assert(bus);
821 assert(msg);
822 assert(m);
823
824 r = sd_bus_message_new_method_return(msg, &reply);
825 if (r < 0)
826 return r;
827
828 r = sd_bus_message_open_container(reply, 'a', "(usssdo)");
829 if (r < 0)
830 return r;
831
832 HASHMAP_FOREACH(t, m->transfers, i) {
833
834 r = sd_bus_message_append(
835 reply,
836 "(usssdo)",
837 t->id,
838 transfer_type_to_string(t->type),
839 t->remote,
840 t->local,
841 (double) t->progress_percent / 100.0,
842 t->object_path);
843 if (r < 0)
844 return r;
845 }
846
847 r = sd_bus_message_close_container(reply);
848 if (r < 0)
849 return r;
850
851 return sd_bus_send(bus, reply, NULL);
852 }
853
854 static int method_cancel(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
855 Transfer *t = userdata;
856 int r;
857
858 assert(bus);
859 assert(msg);
860 assert(t);
861
862 r = bus_verify_polkit_async(
863 msg,
864 CAP_SYS_ADMIN,
865 "org.freedesktop.import1.pull",
866 false,
867 &t->manager->polkit_registry,
868 error);
869 if (r < 0)
870 return r;
871 if (r == 0)
872 return 1; /* Will call us back */
873
874 r = transfer_cancel(t);
875 if (r < 0)
876 return r;
877
878 return sd_bus_reply_method_return(msg, NULL);
879 }
880
881 static int method_cancel_transfer(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
882 Manager *m = userdata;
883 Transfer *t;
884 uint32_t id;
885 int r;
886
887 assert(bus);
888 assert(msg);
889 assert(m);
890
891 r = bus_verify_polkit_async(
892 msg,
893 CAP_SYS_ADMIN,
894 "org.freedesktop.import1.pull",
895 false,
896 &m->polkit_registry,
897 error);
898 if (r < 0)
899 return r;
900 if (r == 0)
901 return 1; /* Will call us back */
902
903 r = sd_bus_message_read(msg, "u", &id);
904 if (r < 0)
905 return r;
906 if (id <= 0)
907 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid transfer id");
908
909 t = hashmap_get(m->transfers, UINT32_TO_PTR(id));
910 if (!t)
911 return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_TRANSFER, "No transfer by id %" PRIu32, id);
912
913 r = transfer_cancel(t);
914 if (r < 0)
915 return r;
916
917 return sd_bus_reply_method_return(msg, NULL);
918 }
919
920 static int property_get_progress(
921 sd_bus *bus,
922 const char *path,
923 const char *interface,
924 const char *property,
925 sd_bus_message *reply,
926 void *userdata,
927 sd_bus_error *error) {
928
929 Transfer *t = userdata;
930
931 assert(bus);
932 assert(reply);
933 assert(t);
934
935 return sd_bus_message_append(reply, "d", (double) t->progress_percent / 100.0);
936 }
937
938 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, transfer_type, TransferType);
939 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_verify, import_verify, ImportVerify);
940
941 static const sd_bus_vtable transfer_vtable[] = {
942 SD_BUS_VTABLE_START(0),
943 SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Transfer, id), SD_BUS_VTABLE_PROPERTY_CONST),
944 SD_BUS_PROPERTY("Local", "s", NULL, offsetof(Transfer, local), SD_BUS_VTABLE_PROPERTY_CONST),
945 SD_BUS_PROPERTY("Remote", "s", NULL, offsetof(Transfer, remote), SD_BUS_VTABLE_PROPERTY_CONST),
946 SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Transfer, type), SD_BUS_VTABLE_PROPERTY_CONST),
947 SD_BUS_PROPERTY("Verify", "s", property_get_verify, offsetof(Transfer, verify), SD_BUS_VTABLE_PROPERTY_CONST),
948 SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0),
949 SD_BUS_METHOD("Cancel", NULL, NULL, method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
950 SD_BUS_SIGNAL("LogMessage", "us", 0),
951 SD_BUS_VTABLE_END,
952 };
953
954 static const sd_bus_vtable manager_vtable[] = {
955 SD_BUS_VTABLE_START(0),
956 SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
957 SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
958 SD_BUS_METHOD("PullDkr", "sssssb", "uo", method_pull_dkr, SD_BUS_VTABLE_UNPRIVILEGED),
959 SD_BUS_METHOD("ListTransfers", NULL, "a(usssdo)", method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED),
960 SD_BUS_METHOD("CancelTransfer", "u", NULL, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED),
961 SD_BUS_SIGNAL("TransferNew", "uo", 0),
962 SD_BUS_SIGNAL("TransferRemoved", "uos", 0),
963 SD_BUS_VTABLE_END,
964 };
965
966 static int transfer_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
967 Manager *m = userdata;
968 Transfer *t;
969 const char *p;
970 uint32_t id;
971 int r;
972
973 assert(bus);
974 assert(path);
975 assert(interface);
976 assert(found);
977 assert(m);
978
979 p = startswith(path, "/org/freedesktop/import1/transfer/_");
980 if (!p)
981 return 0;
982
983 r = safe_atou32(p, &id);
984 if (r < 0 || id == 0)
985 return 0;
986
987 t = hashmap_get(m->transfers, UINT32_TO_PTR(id));
988 if (!t)
989 return 0;
990
991 *found = t;
992 return 1;
993 }
994
995 static int transfer_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
996 _cleanup_strv_free_ char **l = NULL;
997 Manager *m = userdata;
998 Transfer *t;
999 unsigned k = 0;
1000 Iterator i;
1001
1002 l = new0(char*, hashmap_size(m->transfers) + 1);
1003 if (!l)
1004 return -ENOMEM;
1005
1006 HASHMAP_FOREACH(t, m->transfers, i) {
1007
1008 l[k] = strdup(t->object_path);
1009 if (!l[k])
1010 return -ENOMEM;
1011
1012 k++;
1013 }
1014
1015 *nodes = l;
1016 l = NULL;
1017
1018 return 1;
1019 }
1020
1021 static int manager_add_bus_objects(Manager *m) {
1022 int r;
1023
1024 assert(m);
1025
1026 r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", manager_vtable, m);
1027 if (r < 0)
1028 return log_error_errno(r, "Failed to register object: %m");
1029
1030 r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/import1/transfer", "org.freedesktop.import1.Transfer", transfer_vtable, transfer_object_find, m);
1031 if (r < 0)
1032 return log_error_errno(r, "Failed to register object: %m");
1033
1034 r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/import1/transfer", transfer_node_enumerator, m);
1035 if (r < 0)
1036 return log_error_errno(r, "Failed to add transfer enumerator: %m");
1037
1038 r = sd_bus_request_name(m->bus, "org.freedesktop.import1", 0);
1039 if (r < 0)
1040 return log_error_errno(r, "Failed to register name: %m");
1041
1042 r = sd_bus_attach_event(m->bus, m->event, 0);
1043 if (r < 0)
1044 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1045
1046 return 0;
1047 }
1048
1049 static bool manager_check_idle(void *userdata) {
1050 Manager *m = userdata;
1051
1052 return hashmap_isempty(m->transfers);
1053 }
1054
1055 static int manager_run(Manager *m) {
1056 assert(m);
1057
1058 return bus_event_loop_with_idle(
1059 m->event,
1060 m->bus,
1061 "org.freedesktop.import1",
1062 DEFAULT_EXIT_USEC,
1063 manager_check_idle,
1064 m);
1065 }
1066
1067 int main(int argc, char *argv[]) {
1068 _cleanup_(manager_unrefp) Manager *m = NULL;
1069 int r;
1070
1071 log_set_target(LOG_TARGET_AUTO);
1072 log_parse_environment();
1073 log_open();
1074
1075 umask(0022);
1076
1077 if (argc != 1) {
1078 log_error("This program takes no arguments.");
1079 r = -EINVAL;
1080 goto finish;
1081 }
1082
1083 assert_se(sigprocmask_many(SIG_BLOCK, SIGCHLD, -1) >= 0);
1084
1085 r = manager_new(&m);
1086 if (r < 0) {
1087 log_error_errno(r, "Failed to allocate manager object: %m");
1088 goto finish;
1089 }
1090
1091 r = manager_add_bus_objects(m);
1092 if (r < 0)
1093 goto finish;
1094
1095 r = manager_run(m);
1096 if (r < 0) {
1097 log_error_errno(r, "Failed to run event loop: %m");
1098 goto finish;
1099 }
1100
1101 finish:
1102 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1103 }