chiark / gitweb /
import: only define the _to_string() enum mapping function, thus making gcc shut up
[elogind.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 "import-util.h"
31
32 typedef struct Transfer Transfer;
33 typedef struct Manager Manager;
34
35 typedef enum TransferType {
36         TRANSFER_TAR,
37         TRANSFER_RAW,
38         TRANSFER_DKR,
39         _TRANSFER_TYPE_MAX,
40         _TRANSFER_TYPE_INVALID = -1,
41 } TransferType;
42
43 struct Transfer {
44         Manager *manager;
45
46         uint32_t id;
47         char *object_path;
48
49         TransferType type;
50         ImportVerify verify;
51
52         char *remote;
53         char *local;
54         bool force_local;
55
56         char *dkr_index_url;
57
58         pid_t pid;
59
60         int log_fd;
61
62         char log_message[LINE_MAX];
63         size_t log_message_size;
64
65         sd_event_source *pid_event_source;
66         sd_event_source *log_event_source;
67
68         unsigned n_canceled;
69 };
70
71 struct Manager {
72         sd_event *event;
73         sd_bus *bus;
74
75         uint32_t current_transfer_id;
76         Hashmap *transfers;
77
78         Hashmap *polkit_registry;
79 };
80
81 #define TRANSFERS_MAX 64
82
83 static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = {
84         [TRANSFER_TAR] = "tar",
85         [TRANSFER_RAW] = "raw",
86         [TRANSFER_DKR] = "dkr",
87 };
88
89 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType);
90
91 static Transfer *transfer_unref(Transfer *t) {
92         if (!t)
93                 return NULL;
94
95         if (t->manager)
96                 hashmap_remove(t->manager->transfers, UINT32_TO_PTR(t->id));
97
98         sd_event_source_unref(t->pid_event_source);
99         sd_event_source_unref(t->log_event_source);
100
101         free(t->remote);
102         free(t->local);
103         free(t->dkr_index_url);
104         free(t->object_path);
105
106         if (t->pid > 0) {
107                 (void) kill_and_sigcont(t->pid, SIGKILL);
108                 (void) wait_for_terminate(t->pid, NULL);
109         }
110
111         safe_close(t->log_fd);
112
113         free(t);
114         return NULL;
115 }
116
117 DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_unref);
118
119 static int transfer_new(Manager *m, Transfer **ret) {
120         _cleanup_(transfer_unrefp) Transfer *t = NULL;
121         uint32_t id;
122         int r;
123
124         assert(m);
125         assert(ret);
126
127         if (hashmap_size(m->transfers) >= TRANSFERS_MAX)
128                 return -E2BIG;
129
130         r = hashmap_ensure_allocated(&m->transfers, &trivial_hash_ops);
131         if (r < 0)
132                 return r;
133
134         t = new0(Transfer, 1);
135         if (!t)
136                 return -ENOMEM;
137
138         t->type = _TRANSFER_TYPE_INVALID;
139         t->log_fd = -1;
140
141         id = m->current_transfer_id + 1;
142
143         if (asprintf(&t->object_path, "/org/freedesktop/import1/transfer/_%" PRIu32, id) < 0)
144                 return -ENOMEM;
145
146         r = hashmap_put(m->transfers, UINT32_TO_PTR(id), t);
147         if (r < 0)
148                 return r;
149
150         m->current_transfer_id = id;
151
152         t->manager = m;
153         t->id = id;
154
155         *ret = t;
156         t = NULL;
157
158         return 0;
159 }
160
161 static void transfer_send_log_line(Transfer *t, const char *line) {
162         int r, priority = LOG_INFO;
163
164         assert(t);
165         assert(line);
166
167         syslog_parse_priority(&line, &priority, true);
168
169         log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line);
170
171         r = sd_bus_emit_signal(
172                         t->manager->bus,
173                         t->object_path,
174                         "org.freedesktop.import1.Transfer",
175                         "LogMessage",
176                         "us",
177                         priority,
178                         line);
179         if (r < 0)
180                 log_error_errno(r, "Cannot emit message: %m");
181  }
182
183 static void transfer_send_logs(Transfer *t, bool flush) {
184         assert(t);
185
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
188          * fail */
189
190         while (t->log_message_size > 0) {
191                 _cleanup_free_ char *n = NULL;
192                 char *e;
193
194                 if (t->log_message_size >= sizeof(t->log_message))
195                         e = t->log_message + sizeof(t->log_message);
196                 else {
197                         char *a, *b;
198
199                         a = memchr(t->log_message, 0, t->log_message_size);
200                         b = memchr(t->log_message, '\n', t->log_message_size);
201
202                         if (a && b)
203                                 e = a < b ? a : b;
204                         else if (a)
205                                 e = a;
206                         else
207                                 e = b;
208                 }
209
210                 if (!e) {
211                         if (!flush)
212                                 return;
213
214                         e = t->log_message + t->log_message_size;
215                 }
216
217                 n = strndup(t->log_message, e - t->log_message);
218
219                 /* Skip over NUL and newlines */
220                 while (e < t->log_message + t->log_message_size && (*e == 0 || *e == '\n'))
221                         e++;
222
223                 memmove(t->log_message, e, t->log_message + sizeof(t->log_message) - e);
224                 t->log_message_size -= e - t->log_message;
225
226                 if (!n) {
227                         log_oom();
228                         continue;
229                 }
230
231                 if (isempty(n))
232                         continue;
233
234                 transfer_send_log_line(t, n);
235         }
236 }
237
238 static int transfer_finalize(Transfer *t, bool success) {
239         int r;
240
241         assert(t);
242
243         transfer_send_logs(t, true);
244
245         r = sd_bus_emit_signal(
246                         t->manager->bus,
247                         "/org/freedesktop/import1",
248                         "org.freedesktop.import1.Manager",
249                         "TransferRemoved",
250                         "uos",
251                         t->id,
252                         t->object_path,
253                         success ? "done" :
254                         t->n_canceled > 0 ? "canceled" : "failed");
255
256         if (r < 0)
257                 log_error_errno(r, "Cannot emit message: %m");
258
259         transfer_unref(t);
260         return 0;
261 }
262
263 static int transfer_cancel(Transfer *t) {
264         int r;
265
266         assert(t);
267
268         r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL);
269         if (r < 0)
270                 return r;
271
272         t->n_canceled++;
273         return 0;
274 }
275
276 static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userdata) {
277         Transfer *t = userdata;
278         bool success = false;
279
280         assert(s);
281         assert(t);
282
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);
286                 else {
287                         log_debug("Import process succeeded.");
288                         success = true;
289                 }
290
291         } else if (si->si_code == CLD_KILLED ||
292                    si->si_code == CLD_DUMPED)
293
294                 log_error("Import process terminated by signal %s.", signal_to_string(si->si_status));
295         else
296                 log_error("Import process failed due to unknown reason.");
297
298         t->pid = 0;
299
300         return transfer_finalize(t, success);
301 }
302
303 static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
304         Transfer *t = userdata;
305         ssize_t l;
306
307         assert(s);
308         assert(t);
309
310         l = read(fd, t->log_message + t->log_message_size, sizeof(t->log_message) - t->log_message_size);
311         if (l <= 0) {
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. */
315
316                 if (l < 0)
317                         log_error_errno(errno, "Failed to read log message: %m");
318
319                 t->log_event_source = sd_event_source_unref(t->log_event_source);
320                 return 0;
321         }
322
323         t->log_message_size += l;
324
325         transfer_send_logs(t, false);
326
327         return 0;
328 }
329
330 static int transfer_start(Transfer *t) {
331         _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
332         int r;
333
334         assert(t);
335         assert(t->pid <= 0);
336
337         if (pipe2(pipefd, O_CLOEXEC) < 0)
338                 return -errno;
339
340         t->pid = fork();
341         if (t->pid < 0)
342                 return -errno;
343         if (t->pid == 0) {
344                 const char *cmd[] = {
345                         "systemd-pull",
346                         transfer_type_to_string(t->type),
347                         "--verify",
348                         NULL, /* verify argument */
349                         NULL, /* maybe --force */
350                         NULL, /* maybe --dkr-index-url */
351                         NULL, /* the actual URL */
352                         NULL, /* remote */
353                         NULL, /* local */
354                         NULL
355                 };
356                 int null_fd;
357                 unsigned k = 3;
358
359                 /* Child */
360
361                 reset_all_signal_handlers();
362                 reset_signal_mask();
363                 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
364
365                 pipefd[0] = safe_close(pipefd[0]);
366
367                 if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) {
368                         log_error_errno(errno, "Failed to dup2() fd: %m");
369                         _exit(EXIT_FAILURE);
370                 }
371
372                 if (dup2(pipefd[1], STDERR_FILENO) != STDERR_FILENO) {
373                         log_error_errno(errno, "Failed to dup2() fd: %m");
374                         _exit(EXIT_FAILURE);
375                 }
376
377                 if (pipefd[1] != STDOUT_FILENO && pipefd[1] != STDERR_FILENO)
378                         pipefd[1] = safe_close(pipefd[1]);
379
380                 null_fd = open("/dev/null", O_RDONLY|O_NOCTTY);
381                 if (null_fd < 0) {
382                         log_error_errno(errno, "Failed to open /dev/null: %m");
383                         _exit(EXIT_FAILURE);
384                 }
385
386                 if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) {
387                         log_error_errno(errno, "Failed to dup2() fd: %m");
388                         _exit(EXIT_FAILURE);
389                 }
390
391                 if (null_fd != STDIN_FILENO)
392                         safe_close(null_fd);
393
394                 fd_cloexec(STDIN_FILENO, false);
395                 fd_cloexec(STDOUT_FILENO, false);
396                 fd_cloexec(STDERR_FILENO, false);
397
398                 putenv((char*) "SYSTEMD_LOG_TARGET=console-prefixed");
399
400                 cmd[k++] = import_verify_to_string(t->verify);
401                 if (t->force_local)
402                         cmd[k++] = "--force";
403
404                 if (t->dkr_index_url) {
405                         cmd[k++] = "--dkr-index-url";
406                         cmd[k++] = t->dkr_index_url;
407                 }
408
409                 cmd[k++] = t->remote;
410                 if (t->local)
411                         cmd[k++] = t->local;
412                 cmd[k] = NULL;
413
414                 execv(SYSTEMD_PULL_PATH, (char * const *) cmd);
415                 log_error_errno(errno, "Failed to execute import tool: %m");
416                 _exit(EXIT_FAILURE);
417         }
418
419         pipefd[1] = safe_close(pipefd[1]);
420         t->log_fd = pipefd[0];
421         pipefd[0] = -1;
422
423         r = sd_event_add_child(t->manager->event, &t->pid_event_source, t->pid, WEXITED, transfer_on_pid, t);
424         if (r < 0)
425                 return r;
426
427         r = sd_event_add_io(t->manager->event, &t->log_event_source, t->log_fd, EPOLLIN, transfer_on_log, t);
428         if (r < 0)
429                 return r;
430
431         /* Make sure always process logging before SIGCHLD */
432         r = sd_event_source_set_priority(t->log_event_source, SD_EVENT_PRIORITY_NORMAL -5);
433         if (r < 0)
434                 return r;
435
436         r = sd_bus_emit_signal(
437                         t->manager->bus,
438                         "/org/freedesktop/import1",
439                         "org.freedesktop.import1.Manager",
440                         "TransferNew",
441                         "uo",
442                         t->id,
443                         t->object_path);
444         if (r < 0)
445                 return r;
446
447         return 0;
448 }
449
450 static Manager *manager_unref(Manager *m) {
451         Transfer *t;
452
453         if (!m)
454                 return NULL;
455
456         while ((t = hashmap_first(m->transfers)))
457                 transfer_unref(t);
458
459         hashmap_free(m->transfers);
460
461         bus_verify_polkit_async_registry_free(m->polkit_registry);
462
463         sd_bus_close(m->bus);
464         sd_bus_unref(m->bus);
465         sd_event_unref(m->event);
466
467         free(m);
468         return NULL;
469 }
470
471 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
472
473 static int manager_new(Manager **ret) {
474         _cleanup_(manager_unrefp) Manager *m = NULL;
475         int r;
476
477         assert(ret);
478
479         m = new0(Manager, 1);
480         if (!m)
481                 return -ENOMEM;
482
483         r = sd_event_default(&m->event);
484         if (r < 0)
485                 return r;
486
487         sd_event_set_watchdog(m->event, true);
488
489         r = sd_bus_default_system(&m->bus);
490         if (r < 0)
491                 return r;
492
493         *ret = m;
494         m = NULL;
495
496         return 0;
497 }
498
499 static Transfer *manager_find(Manager *m, TransferType type, const char *dkr_index_url, const char *remote) {
500         Transfer *t;
501         Iterator i;
502
503         assert(m);
504         assert(type >= 0);
505         assert(type < _TRANSFER_TYPE_MAX);
506
507         HASHMAP_FOREACH(t, m->transfers, i) {
508
509                 if (t->type == type &&
510                     streq_ptr(t->remote, remote) &&
511                     streq_ptr(t->dkr_index_url, dkr_index_url))
512                         return t;
513         }
514
515         return NULL;
516 }
517
518 static int method_pull_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
519         _cleanup_(transfer_unrefp) Transfer *t = NULL;
520         const char *remote, *local, *verify, *object;
521         Manager *m = userdata;
522         ImportVerify v;
523         TransferType type;
524         int force, r;
525         uint32_t id;
526
527         assert(bus);
528         assert(msg);
529         assert(m);
530
531         r = bus_verify_polkit_async(
532                         msg,
533                         CAP_SYS_ADMIN,
534                         "org.freedesktop.import1.pull",
535                         false,
536                         &m->polkit_registry,
537                         error);
538         if (r < 0)
539                 return r;
540         if (r == 0)
541                 return 1; /* Will call us back */
542
543         r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force);
544         if (r < 0)
545                 return r;
546
547         if (!http_url_is_valid(remote))
548                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "URL %s is invalid", remote);
549
550         if (isempty(local))
551                 local = NULL;
552         else if (!machine_name_is_valid(local))
553                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
554
555         if (isempty(verify))
556                 v = IMPORT_VERIFY_SIGNATURE;
557         else
558                 v = import_verify_from_string(verify);
559         if (v < 0)
560                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
561
562         type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_TAR : TRANSFER_RAW;
563
564         if (manager_find(m, type, NULL, remote))
565                 return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
566
567         r = transfer_new(m, &t);
568         if (r < 0)
569                 return r;
570
571         t->type = type;
572         t->verify = v;
573         t->force_local = force;
574
575         t->remote = strdup(remote);
576         if (!t->remote)
577                 return -ENOMEM;
578
579         t->local = strdup(local);
580         if (!t->local)
581                 return -ENOMEM;
582
583         r = transfer_start(t);
584         if (r < 0)
585                 return r;
586
587         object = t->object_path;
588         id = t->id;
589         t = NULL;
590
591         return sd_bus_reply_method_return(msg, "uo", id, object);
592 }
593
594 static int method_pull_dkr(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
595         _cleanup_(transfer_unrefp) Transfer *t = NULL;
596         const char *index_url, *remote, *tag, *local, *verify, *object;
597         Manager *m = userdata;
598         ImportVerify v;
599         int force, r;
600         uint32_t id;
601
602         assert(bus);
603         assert(msg);
604         assert(m);
605
606         r = bus_verify_polkit_async(
607                         msg,
608                         CAP_SYS_ADMIN,
609                         "org.freedesktop.import1.pull",
610                         false,
611                         &m->polkit_registry,
612                         error);
613         if (r < 0)
614                 return r;
615         if (r == 0)
616                 return 1; /* Will call us back */
617
618         r = sd_bus_message_read(msg, "sssssb", &index_url, &remote, &tag, &local, &verify, &force);
619         if (r < 0)
620                 return r;
621
622         if (isempty(index_url))
623                 index_url = DEFAULT_DKR_INDEX_URL;
624         if (!index_url)
625                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL must be specified.");
626         if (!http_url_is_valid(index_url))
627                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL %s is invalid", index_url);
628
629         if (!dkr_name_is_valid(remote))
630                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Remote name %s is not valid", remote);
631
632         if (isempty(tag))
633                 tag = "latest";
634         else if (!dkr_tag_is_valid(tag))
635                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Tag %s is not valid", tag);
636
637         if (isempty(local))
638                 local = NULL;
639         else if (!machine_name_is_valid(local))
640                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
641
642         if (isempty(verify))
643                 v = IMPORT_VERIFY_SIGNATURE;
644         else
645                 v = import_verify_from_string(verify);
646         if (v < 0)
647                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
648
649         if (v != IMPORT_VERIFY_NO)
650                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "DKR does not support verification.");
651
652         if (manager_find(m, TRANSFER_DKR, index_url, remote))
653                 return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
654
655         r = transfer_new(m, &t);
656         if (r < 0)
657                 return r;
658
659         t->type = TRANSFER_DKR;
660         t->verify = v;
661         t->force_local = force;
662
663         t->dkr_index_url = strdup(index_url);
664         if (!t->dkr_index_url)
665                 return -ENOMEM;
666
667         t->remote = strjoin(remote, ":", tag, NULL);
668         if (!t->remote)
669                 return -ENOMEM;
670
671         t->local = strdup(local);
672         if (!t->local)
673                 return -ENOMEM;
674
675         r = transfer_start(t);
676         if (r < 0)
677                 return r;
678
679         object = t->object_path;
680         id = t->id;
681         t = NULL;
682
683         return sd_bus_reply_method_return(msg, "uo", id, object);
684 }
685
686 static int method_list_transfers(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
687         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
688         Manager *m = userdata;
689         Transfer *t;
690         Iterator i;
691         int r;
692
693         assert(bus);
694         assert(msg);
695         assert(m);
696
697         r = sd_bus_message_new_method_return(msg, &reply);
698         if (r < 0)
699                 return r;
700
701         r = sd_bus_message_open_container(reply, 'a', "(ussso)");
702         if (r < 0)
703                 return r;
704
705         HASHMAP_FOREACH(t, m->transfers, i) {
706
707                 r = sd_bus_message_append(
708                                 reply,
709                                 "(ussso)",
710                                 t->id,
711                                 transfer_type_to_string(t->type),
712                                 t->remote,
713                                 t->local,
714                                 t->object_path);
715                 if (r < 0)
716                         return r;
717         }
718
719         r = sd_bus_message_close_container(reply);
720         if (r < 0)
721                 return r;
722
723         return sd_bus_send(bus, reply, NULL);
724 }
725
726 static int method_cancel(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
727         Transfer *t = userdata;
728         int r;
729
730         assert(bus);
731         assert(msg);
732         assert(t);
733
734         r = bus_verify_polkit_async(
735                         msg,
736                         CAP_SYS_ADMIN,
737                         "org.freedesktop.import1.pull",
738                         false,
739                         &t->manager->polkit_registry,
740                         error);
741         if (r < 0)
742                 return r;
743         if (r == 0)
744                 return 1; /* Will call us back */
745
746         r = transfer_cancel(t);
747         if (r < 0)
748                 return r;
749
750         return sd_bus_reply_method_return(msg, NULL);
751 }
752
753 static int method_cancel_transfer(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) {
754         Manager *m = userdata;
755         Transfer *t;
756         uint32_t id;
757         int r;
758
759         assert(bus);
760         assert(msg);
761         assert(m);
762
763         r = bus_verify_polkit_async(
764                         msg,
765                         CAP_SYS_ADMIN,
766                         "org.freedesktop.import1.pull",
767                         false,
768                         &m->polkit_registry,
769                         error);
770         if (r < 0)
771                 return r;
772         if (r == 0)
773                 return 1; /* Will call us back */
774
775         r = sd_bus_message_read(msg, "u", &id);
776         if (r < 0)
777                 return r;
778         if (id <= 0)
779                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid transfer id");
780
781         t = hashmap_get(m->transfers, UINT32_TO_PTR(id));
782         if (!t)
783                 return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_TRANSFER, "No transfer by id %" PRIu32, id);
784
785         r = transfer_cancel(t);
786         if (r < 0)
787                 return r;
788
789         return sd_bus_reply_method_return(msg, NULL);
790 }
791
792 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, transfer_type, TransferType);
793 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_verify, import_verify, ImportVerify);
794
795 static const sd_bus_vtable transfer_vtable[] = {
796         SD_BUS_VTABLE_START(0),
797         SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Transfer, id), SD_BUS_VTABLE_PROPERTY_CONST),
798         SD_BUS_PROPERTY("Local", "s", NULL, offsetof(Transfer, local), SD_BUS_VTABLE_PROPERTY_CONST),
799         SD_BUS_PROPERTY("Remote", "s", NULL, offsetof(Transfer, remote), SD_BUS_VTABLE_PROPERTY_CONST),
800         SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Transfer, type), SD_BUS_VTABLE_PROPERTY_CONST),
801         SD_BUS_PROPERTY("Verify", "s", property_get_verify, offsetof(Transfer, verify), SD_BUS_VTABLE_PROPERTY_CONST),
802         SD_BUS_METHOD("Cancel", NULL, NULL, method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
803         SD_BUS_SIGNAL("LogMessage", "us", 0),
804         SD_BUS_VTABLE_END,
805 };
806
807 static const sd_bus_vtable manager_vtable[] = {
808         SD_BUS_VTABLE_START(0),
809         SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
810         SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
811         SD_BUS_METHOD("PullDkr", "sssssb", "uo", method_pull_dkr, SD_BUS_VTABLE_UNPRIVILEGED),
812         SD_BUS_METHOD("ListTransfers", NULL, "a(ussso)", method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED),
813         SD_BUS_METHOD("CancelTransfer", "u", NULL, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED),
814         SD_BUS_SIGNAL("TransferNew", "uo", 0),
815         SD_BUS_SIGNAL("TransferRemoved", "uos", 0),
816         SD_BUS_VTABLE_END,
817 };
818
819 static int transfer_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
820         Manager *m = userdata;
821         Transfer *t;
822         const char *p;
823         uint32_t id;
824         int r;
825
826         assert(bus);
827         assert(path);
828         assert(interface);
829         assert(found);
830         assert(m);
831
832         p = startswith(path, "/org/freedesktop/import1/transfer/_");
833         if (!p)
834                 return 0;
835
836         r = safe_atou32(p, &id);
837         if (r < 0 || id == 0)
838                 return 0;
839
840         t = hashmap_get(m->transfers, UINT32_TO_PTR(id));
841         if (!t)
842                 return 0;
843
844         *found = t;
845         return 1;
846 }
847
848 static int transfer_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
849         _cleanup_strv_free_ char **l = NULL;
850         Manager *m = userdata;
851         Transfer *t;
852         unsigned k = 0;
853         Iterator i;
854
855         l = new0(char*, hashmap_size(m->transfers) + 1);
856         if (!l)
857                 return -ENOMEM;
858
859         HASHMAP_FOREACH(t, m->transfers, i) {
860
861                 l[k] = strdup(t->object_path);
862                 if (!l[k])
863                         return -ENOMEM;
864
865                 k++;
866         }
867
868         *nodes = l;
869         l = NULL;
870
871         return 1;
872 }
873
874 static int manager_add_bus_objects(Manager *m) {
875         int r;
876
877         assert(m);
878
879         r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", manager_vtable, m);
880         if (r < 0)
881                 return log_error_errno(r, "Failed to register object: %m");
882
883         r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/import1/transfer", "org.freedesktop.import1.Transfer", transfer_vtable, transfer_object_find, m);
884         if (r < 0)
885                 return log_error_errno(r, "Failed to register object: %m");
886
887         r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/import1/transfer", transfer_node_enumerator, m);
888         if (r < 0)
889                 return log_error_errno(r, "Failed to add transfer enumerator: %m");
890
891         r = sd_bus_request_name(m->bus, "org.freedesktop.import1", 0);
892         if (r < 0)
893                 return log_error_errno(r, "Failed to register name: %m");
894
895         r = sd_bus_attach_event(m->bus, m->event, 0);
896         if (r < 0)
897                 return log_error_errno(r, "Failed to attach bus to event loop: %m");
898
899         return 0;
900 }
901
902 static bool manager_check_idle(void *userdata) {
903         Manager *m = userdata;
904
905         return hashmap_isempty(m->transfers);
906 }
907
908 static int manager_run(Manager *m) {
909         assert(m);
910
911         return bus_event_loop_with_idle(
912                         m->event,
913                         m->bus,
914                         "org.freedesktop.import1",
915                         DEFAULT_EXIT_USEC,
916                         manager_check_idle,
917                         m);
918 }
919
920 int main(int argc, char *argv[]) {
921         _cleanup_(manager_unrefp) Manager *m = NULL;
922         int r;
923
924         log_set_target(LOG_TARGET_AUTO);
925         log_parse_environment();
926         log_open();
927
928         umask(0022);
929
930         if (argc != 1) {
931                 log_error("This program takes no arguments.");
932                 r = -EINVAL;
933                 goto finish;
934         }
935
936         assert_se(sigprocmask_many(SIG_BLOCK, SIGCHLD, -1) >= 0);
937
938         r = manager_new(&m);
939         if (r < 0) {
940                 log_error_errno(r, "Failed to allocate manager object: %m");
941                 goto finish;
942         }
943
944         r = manager_add_bus_objects(m);
945         if (r < 0)
946                 goto finish;
947
948         r = manager_run(m);
949         if (r < 0) {
950                 log_error_errno(r, "Failed to run event loop: %m");
951                 goto finish;
952         }
953
954 finish:
955         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
956 }