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