[systemd-commits] 9 commits - Makefile.am TODO configure.ac man/systemd-run.xml src/core src/import src/machine src/nspawn src/run src/shared

Lennart Poettering lennart at kemper.freedesktop.org
Mon Dec 22 18:27:08 PST 2014


 Makefile.am                 |    4 
 TODO                        |    6 
 configure.ac                |    9 
 man/systemd-run.xml         |   36 +
 src/core/dbus-execute.c     |   89 +++
 src/import/import-dck.c     | 1156 -------------------------------------------
 src/import/import-dck.h     |   39 -
 src/import/import-dkr.c     | 1177 ++++++++++++++++++++++++++++++++++++++++++++
 src/import/import-dkr.h     |   40 +
 src/import/import.c         |   41 +
 src/machine/machine-dbus.c  |  128 ++++
 src/machine/machine.h       |    1 
 src/machine/machinectl.c    |  124 ----
 src/machine/machined-dbus.c |   22 
 src/nspawn/nspawn.c         |   16 
 src/run/run.c               |  510 ++++++++++++-------
 src/shared/pty.c            |    4 
 src/shared/ptyfwd.c         |   33 +
 src/shared/ptyfwd.h         |    4 
 src/shared/util.c           |   64 +-
 src/shared/util.h           |    2 
 21 files changed, 1978 insertions(+), 1527 deletions(-)

New commits:
commit 1d050f9f73b9b5d833687df163ad5a1692cf281c
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Dec 23 02:11:48 2014 +0100

    update TODO

diff --git a/TODO b/TODO
index 587a692..0ea986d 100644
--- a/TODO
+++ b/TODO
@@ -35,8 +35,6 @@ Features:
 
 * Check all invocations of access() and consider turning them into laccess()
 
-* "machinectl run" that works like systemd-run, but allocates a pty in the container and attached the service to it
-
 * "machinectl start/enable/disable foo" as aliases for "systemctl start/enable/disable systemd-nspawn at foo.service"
 
 * "machinectl snapshot" to make a snapshot of a tree or container into /var/lib/containers
@@ -49,7 +47,7 @@ Features:
 
 * "machinectl status" should show 10 most recent log lines of both the host logs of the unit of the machine, plus the logs generated in the machine
 
-* make "machinectl login" use a new machined call AllocateMachinePty() or so to get a pty in a machine. That would open up logins to unprivileged clients
+* make "machinectl login" use a new machined call OpenMachineLogin() or so to get a pty in a machine. That would open up logins to unprivileged clients
 
 * add transparent btrfs pool in a loopback file in /var if btrfs operations (such as systemd-import pull-dkr) are used and /var is not a btrfs file system
 

commit 9d8c4979c0c9b09bd3d0d57ee585f6792c6add26
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Dec 23 02:10:08 2014 +0100

    util: add allocation loop to gettyname_malloc()

diff --git a/src/shared/util.c b/src/shared/util.c
index 6695a85..6bd278e 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -2785,23 +2785,36 @@ char *getusername_malloc(void) {
         return lookup_uid(getuid());
 }
 
-int getttyname_malloc(int fd, char **r) {
-        char path[PATH_MAX], *c;
-        int k;
+int getttyname_malloc(int fd, char **ret) {
+        size_t l = 100;
+        int r;
 
-        assert(r);
+        assert(fd >= 0);
+        assert(ret);
 
-        k = ttyname_r(fd, path, sizeof(path));
-        if (k > 0)
-                return -k;
+        for (;;) {
+                char path[l];
 
-        char_array_0(path);
+                r = ttyname_r(fd, path, sizeof(path));
+                if (r == 0) {
+                        const char *p;
+                        char *c;
 
-        c = strdup(startswith(path, "/dev/") ? path + 5 : path);
-        if (!c)
-                return -ENOMEM;
+                        p = startswith(path, "/dev/");
+                        c = strdup(p ?: path);
+                        if (!c)
+                                return -ENOMEM;
+
+                        *ret = c;
+                        return 0;
+                }
+
+                if (r != ERANGE)
+                        return -r;
+
+                l *= 2;
+        }
 
-        *r = c;
         return 0;
 }
 
@@ -7437,6 +7450,9 @@ int sethostname_idempotent(const char *s) {
 int ptsname_malloc(int fd, char **ret) {
         size_t l = 100;
 
+        assert(fd >= 0);
+        assert(ret);
+
         for (;;) {
                 char *c;
 

commit 611b312b7d0799281347374dc303c73a066cedf8
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Dec 23 02:02:08 2014 +0100

    nspawn,pty: port over to new ptsname_malloc() helper

diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 01b8c32..0dd12ad 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -2931,13 +2931,12 @@ static int determine_names(void) {
 
 int main(int argc, char *argv[]) {
 
-        _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL;
+        _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *console = NULL;
         bool root_device_rw = true, home_device_rw = true, srv_device_rw = true;
         _cleanup_close_ int master = -1, image_fd = -1;
         _cleanup_close_pair_ int kmsg_socket_pair[2] = { -1, -1 };
         _cleanup_fdset_free_ FDSet *fds = NULL;
         int r, n_fd_passed, loop_nr = -1;
-        const char *console = NULL;
         char veth_name[IFNAMSIZ];
         bool secondary = false, remove_subvol = false;
         sigset_t mask, mask_chld;
@@ -3094,9 +3093,9 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        console = ptsname(master);
-        if (!console) {
-                r = log_error_errno(errno, "Failed to determine tty name: %m");
+        r = ptsname_malloc(master, &console);
+        if (r < 0) {
+                r = log_error_errno(r, "Failed to determine tty name: %m");
                 goto finish;
         }
 
diff --git a/src/shared/pty.c b/src/shared/pty.c
index 52a426c..6863be6 100644
--- a/src/shared/pty.c
+++ b/src/shared/pty.c
@@ -194,13 +194,13 @@ int pty_get_fd(Pty *pty) {
 }
 
 int pty_make_child(Pty *pty) {
-        char slave_name[1024];
+        _cleanup_free_ char *slave_name = NULL;
         int r, fd;
 
         assert_return(pty, -EINVAL);
         assert_return(pty_is_unknown(pty), -EALREADY);
 
-        r = ptsname_r(pty->fd, slave_name, sizeof(slave_name));
+        r = ptsname_malloc(pty->fd, &slave_name);
         if (r < 0)
                 return -errno;
 

commit ee451d766a64117a41ec36dd71e61683c9d9b83c
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Dec 23 01:58:49 2014 +0100

    systemd-run: support -t mode when combined with -M
    
    For that, ask machined for a container PTY and use that.

diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c
index 7cabe0f..76c5dcf 100644
--- a/src/machine/machine-dbus.c
+++ b/src/machine/machine-dbus.c
@@ -404,6 +404,7 @@ int bus_machine_method_open_pty(sd_bus *bus, sd_bus_message *message, void *user
                 .msg_controllen = sizeof(control),
         };
         Machine *m = userdata;
+        _cleanup_free_ char *pty_name = NULL;
         struct cmsghdr *cmsg;
         siginfo_t si;
         pid_t child;
@@ -479,11 +480,15 @@ int bus_machine_method_open_pty(sd_bus *bus, sd_bus_message *message, void *user
         if (master < 0)
                 return -EIO;
 
+        r = ptsname_malloc(master, &pty_name);
+        if (r < 0)
+                return r;
+
         r = sd_bus_message_new_method_return(message, &reply);
         if (r < 0)
                 return r;
 
-        r = sd_bus_message_append(reply, "hs", master, ptsname(master));
+        r = sd_bus_message_append(reply, "hs", master, pty_name);
         if (r < 0)
                 return r;
 
diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index ccee16f..b9e8381 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -1053,7 +1053,7 @@ static int login_machine(int argc, char *argv[], void *userdata) {
 
         r = sd_bus_message_read(reply, "hs", &master, &pty);
         if (r < 0)
-                return r;
+                return bus_log_parse_error(r);
 
         p = startswith(pty, "/dev/pts/");
         if (!p) {
diff --git a/src/run/run.c b/src/run/run.c
index 242bed0..05bfc61 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -657,26 +657,63 @@ static int transient_timer_set_properties(sd_bus_message *m) {
 
 static int start_transient_service(
                 sd_bus *bus,
-                char **argv,
-                sd_bus_error *error) {
+                char **argv) {
 
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
-        _cleanup_free_ char *service = NULL;
+        _cleanup_free_ char *service = NULL, *pty_path = NULL;
         _cleanup_close_ int master = -1;
-        const char *pty_path = NULL;
         int r;
 
         assert(bus);
         assert(argv);
 
         if (arg_pty) {
-                master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
-                if (master < 0)
-                        return log_error_errno(errno, "Failed to acquire pseudo tty: %m");
 
-                pty_path = ptsname(master);
-                if (!pty_path)
-                        return log_error_errno(errno, "Failed to determine tty name: %m");
+                if (arg_transport == BUS_TRANSPORT_LOCAL) {
+                        master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
+                        if (master < 0)
+                                return log_error_errno(errno, "Failed to acquire pseudo tty: %m");
+
+                        r = ptsname_malloc(master, &pty_path);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to determine tty name: %m");
+
+                } else if (arg_transport == BUS_TRANSPORT_CONTAINER) {
+                        _cleanup_bus_unref_ sd_bus *system_bus = NULL;
+                        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+                        const char *s;
+
+                        r = sd_bus_open_system(&system_bus);
+                        if (r < 0)
+                                log_error_errno(r, "Failed to connect to system bus: %m");
+
+                        r = sd_bus_call_method(system_bus,
+                                               "org.freedesktop.machine1",
+                                               "/org/freedesktop/machine1",
+                                               "org.freedesktop.machine1.Manager",
+                                               "OpenMachinePTY",
+                                               &error,
+                                               &reply,
+                                               "s", arg_host);
+                        if (r < 0) {
+                                log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
+                                return r;
+                        }
+
+                        r = sd_bus_message_read(reply, "hs", &master, &s);
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+
+                        master = fcntl(master, F_DUPFD_CLOEXEC, 3);
+                        if (master < 0)
+                                return log_error_errno(errno, "Failed to duplicate master fd: %m");
+
+                        pty_path = strdup(s);
+                        if (!pty_path)
+                                return log_oom();
+                } else
+                        assert_not_reached("Can't allocate tty via ssh");
 
                 if (unlockpt(master) < 0)
                         return log_error_errno(errno, "Failed to unlock tty: %m");
@@ -722,9 +759,11 @@ static int start_transient_service(
         if (r < 0)
                 return bus_log_create_error(r);
 
-        r = sd_bus_call(bus, m, 0, error, NULL);
-        if (r < 0)
-                return bus_log_create_error(r);
+        r = sd_bus_call(bus, m, 0, &error, NULL);
+        if (r < 0) {
+                log_error("Failed to start transient service unit: %s", bus_error_message(&error, -r));
+                return r;
+        }
 
         if (master >= 0) {
                 _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
@@ -769,9 +808,9 @@ static int start_transient_service(
 
 static int start_transient_scope(
                 sd_bus *bus,
-                char **argv,
-                sd_bus_error *error) {
+                char **argv) {
 
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_strv_free_ char **env = NULL, **user_env = NULL;
         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
         _cleanup_free_ char *scope = NULL;
@@ -815,15 +854,16 @@ static int start_transient_scope(
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* aux */
+        /* Auxiliary units */
         r = sd_bus_message_append(m, "a(sa(sv))", 0);
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* send dbus */
-        r = sd_bus_call(bus, m, 0, error, NULL);
-        if (r < 0)
-                return bus_log_create_error(r);
+        r = sd_bus_call(bus, m, 0, &error, NULL);
+        if (r < 0) {
+                log_error("Failed to start transient scope unit: %s", bus_error_message(&error, -r));
+                return r;
+        }
 
         if (arg_nice_set) {
                 if (setpriority(PRIO_PROCESS, 0, arg_nice) < 0)
@@ -889,9 +929,9 @@ static int start_transient_scope(
 
 static int start_transient_timer(
                 sd_bus *bus,
-                char **argv,
-                sd_bus_error *error) {
+                char **argv) {
 
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
         _cleanup_free_ char *timer = NULL, *service = NULL;
         int r;
@@ -999,10 +1039,11 @@ static int start_transient_timer(
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* send dbus */
-        r = sd_bus_call(bus, m, 0, error, NULL);
-        if (r < 0)
-                return bus_log_create_error(r);
+        r = sd_bus_call(bus, m, 0, &error, NULL);
+        if (r < 0) {
+                log_error("Failed to start transient timer unit: %s", bus_error_message(&error, -r));
+                return r;
+        }
 
         log_info("Running as unit %s.", timer);
         if (argv[0])
@@ -1012,7 +1053,6 @@ static int start_transient_timer(
 }
 
 int main(int argc, char* argv[]) {
-        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
         _cleanup_free_ char *description = NULL, *command = NULL;
         int r;
@@ -1062,11 +1102,11 @@ int main(int argc, char* argv[]) {
         }
 
         if (arg_scope)
-                r = start_transient_scope(bus, argv + optind, &error);
+                r = start_transient_scope(bus, argv + optind);
         else if (with_timer())
-                r = start_transient_timer(bus, argv + optind, &error);
+                r = start_transient_timer(bus, argv + optind);
         else
-                r = start_transient_service(bus, argv + optind, &error);
+                r = start_transient_service(bus, argv + optind);
 
 finish:
         strv_free(arg_environment);
diff --git a/src/shared/util.c b/src/shared/util.c
index 06b6077..6695a85 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -7433,3 +7433,27 @@ int sethostname_idempotent(const char *s) {
 
         return 1;
 }
+
+int ptsname_malloc(int fd, char **ret) {
+        size_t l = 100;
+
+        for (;;) {
+                char *c;
+
+                c = new(char, l);
+                if (!c)
+                        return -ENOMEM;
+
+                if (ptsname_r(fd, c, l) == 0) {
+                        *ret = c;
+                        return 0;
+                }
+                if (errno != ERANGE) {
+                        free(c);
+                        return -errno;
+                }
+
+                free(c);
+                l *= 2;
+        }
+}
diff --git a/src/shared/util.h b/src/shared/util.h
index 1804b8c..d3e78e4 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -1052,3 +1052,5 @@ int sethostname_idempotent(const char *s);
              (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len))
 
 #define laccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW)
+
+int ptsname_malloc(int fd, char **ret);

commit 40205d706e1210763ff4c98a317556375bd04bcd
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Dec 22 21:17:29 2014 +0100

    machined: add OpenMachinePTY() bus call for allocating a PTY device within a container
    
    Then, port "machinectl" over to make use of it.

diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c
index f6fd9cf..7cabe0f 100644
--- a/src/machine/machine-dbus.c
+++ b/src/machine/machine-dbus.c
@@ -319,14 +319,14 @@ int bus_machine_method_get_os_release(sd_bus *bus, sd_bus_message *message, void
 
         r = namespace_open(m->leader, NULL, &mntns_fd, NULL, &root_fd);
         if (r < 0)
-                return sd_bus_error_set_errno(error, r);
+                return r;
 
         if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0)
-                return sd_bus_error_set_errno(error, -errno);
+                return -errno;
 
         child = fork();
         if (child < 0)
-                return sd_bus_error_set_errno(error, -errno);
+                return -errno;
 
         if (child == 0) {
                 _cleanup_close_ int fd = -1;
@@ -355,37 +355,137 @@ int bus_machine_method_get_os_release(sd_bus *bus, sd_bus_message *message, void
 
         f = fdopen(pair[0], "re");
         if (!f)
-                return sd_bus_error_set_errno(error, -errno);
+                return -errno;
 
         pair[0] = -1;
 
         r = load_env_file_pairs(f, "/etc/os-release", NULL, &l);
         if (r < 0)
-                return sd_bus_error_set_errno(error, r);
+                return r;
 
         r = wait_for_terminate(child, &si);
         if (r < 0)
-                return sd_bus_error_set_errno(error, r);
+                return r;
         if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
-                return sd_bus_error_set_errno(error, EIO);
+                return -EIO;
 
         r = sd_bus_message_new_method_return(message, &reply);
         if (r < 0)
-                return sd_bus_error_set_errno(error, r);
+                return r;
 
         r = sd_bus_message_open_container(reply, 'a', "{ss}");
         if (r < 0)
-                return sd_bus_error_set_errno(error, r);
+                return r;
 
         STRV_FOREACH_PAIR(k, v, l) {
                 r = sd_bus_message_append(reply, "{ss}", *k, *v);
                 if (r < 0)
-                        return sd_bus_error_set_errno(error, r);
+                        return r;
         }
 
         r = sd_bus_message_close_container(reply);
         if (r < 0)
-                return sd_bus_error_set_errno(error, r);
+                return r;
+
+        return sd_bus_send(bus, reply, NULL);
+}
+
+int bus_machine_method_open_pty(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1;
+        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+        _cleanup_close_pair_ int pair[2] = { -1, -1 };
+        _cleanup_close_ int master = -1;
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(int))];
+        } control = {};
+        struct msghdr mh = {
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+        };
+        Machine *m = userdata;
+        struct cmsghdr *cmsg;
+        siginfo_t si;
+        pid_t child;
+        int r;
+
+        assert(bus);
+        assert(message);
+        assert(m);
+
+        r = namespace_open(m->leader, &pidnsfd, &mntnsfd, NULL, &rootfd);
+        if (r < 0)
+                return r;
+
+        if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
+                return -errno;
+
+        child = fork();
+        if (child < 0)
+                return -errno;
+
+        if (child == 0) {
+                pair[0] = safe_close(pair[0]);
+
+                r = namespace_enter(pidnsfd, mntnsfd, -1, rootfd);
+                if (r < 0)
+                        _exit(EXIT_FAILURE);
+
+                master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC);
+                if (master < 0)
+                        _exit(EXIT_FAILURE);
+
+                cmsg = CMSG_FIRSTHDR(&mh);
+                cmsg->cmsg_level = SOL_SOCKET;
+                cmsg->cmsg_type = SCM_RIGHTS;
+                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+                memcpy(CMSG_DATA(cmsg), &master, sizeof(int));
+
+                mh.msg_controllen = cmsg->cmsg_len;
+
+                if (sendmsg(pair[1], &mh, MSG_NOSIGNAL) < 0)
+                        _exit(EXIT_FAILURE);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        pair[1] = safe_close(pair[1]);
+
+        r = wait_for_terminate(child, &si);
+        if (r < 0)
+                return r;
+        if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
+                return -EIO;
+
+        if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0)
+                return -errno;
+
+        for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
+                if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+                        int *fds;
+                        unsigned n_fds;
+
+                        fds = (int*) CMSG_DATA(cmsg);
+                        n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+
+                        if (n_fds != 1) {
+                                close_many(fds, n_fds);
+                                return -EIO;
+                        }
+
+                        master = fds[0];
+                }
+
+        if (master < 0)
+                return -EIO;
+
+        r = sd_bus_message_new_method_return(message, &reply);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_append(reply, "hs", master, ptsname(master));
+        if (r < 0)
+                return r;
 
         return sd_bus_send(bus, reply, NULL);
 }
@@ -407,6 +507,7 @@ const sd_bus_vtable machine_vtable[] = {
         SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_CAPABILITY(CAP_KILL)),
         SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, 0),
         SD_BUS_VTABLE_END
 };
 
diff --git a/src/machine/machine.h b/src/machine/machine.h
index 5c63665..e1094c2 100644
--- a/src/machine/machine.h
+++ b/src/machine/machine.h
@@ -104,6 +104,7 @@ int bus_machine_method_terminate(sd_bus *bus, sd_bus_message *message, void *use
 int bus_machine_method_kill(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_machine_method_get_addresses(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_machine_method_get_os_release(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_open_pty(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error);
 
 int machine_send_signal(Machine *m, bool new_machine);
 int machine_send_create_reply(Machine *m, sd_bus_error *error);
diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index f5b87a2..ccee16f 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -1010,106 +1010,17 @@ finish:
         return r;
 }
 
-static int openpt_in_namespace(pid_t pid, int flags) {
-        _cleanup_close_pair_ int pair[2] = { -1, -1 };
-        _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1;
-        union {
-                struct cmsghdr cmsghdr;
-                uint8_t buf[CMSG_SPACE(sizeof(int))];
-        } control = {};
-        struct msghdr mh = {
-                .msg_control = &control,
-                .msg_controllen = sizeof(control),
-        };
-        struct cmsghdr *cmsg;
-        int master = -1, r;
-        pid_t child;
-        siginfo_t si;
-
-        assert(pid > 0);
-
-        r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &rootfd);
-        if (r < 0)
-                return r;
-
-        if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
-                return -errno;
-
-        child = fork();
-        if (child < 0)
-                return -errno;
-
-        if (child == 0) {
-                pair[0] = safe_close(pair[0]);
-
-                r = namespace_enter(pidnsfd, mntnsfd, -1, rootfd);
-                if (r < 0)
-                        _exit(EXIT_FAILURE);
-
-                master = posix_openpt(flags);
-                if (master < 0)
-                        _exit(EXIT_FAILURE);
-
-                cmsg = CMSG_FIRSTHDR(&mh);
-                cmsg->cmsg_level = SOL_SOCKET;
-                cmsg->cmsg_type = SCM_RIGHTS;
-                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-                memcpy(CMSG_DATA(cmsg), &master, sizeof(int));
-
-                mh.msg_controllen = cmsg->cmsg_len;
-
-                if (sendmsg(pair[1], &mh, MSG_NOSIGNAL) < 0)
-                        _exit(EXIT_FAILURE);
-
-                _exit(EXIT_SUCCESS);
-        }
-
-        pair[1] = safe_close(pair[1]);
-
-        r = wait_for_terminate(child, &si);
-        if (r < 0)
-                return r;
-        if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
-                return -EIO;
-
-        if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0)
-                return -errno;
-
-        for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
-                if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
-                        int *fds;
-                        unsigned n_fds;
-
-                        fds = (int*) CMSG_DATA(cmsg);
-                        n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
-
-                        if (n_fds != 1) {
-                                close_many(fds, n_fds);
-                                return -EIO;
-                        }
-
-                        master = fds[0];
-                }
-
-        if (master < 0)
-                return -EIO;
-
-        return master;
-}
-
 static int login_machine(int argc, char *argv[], void *userdata) {
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
         _cleanup_bus_close_unref_ sd_bus *container_bus = NULL;
         _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
         _cleanup_event_unref_ sd_event *event = NULL;
-        _cleanup_close_ int master = -1;
         _cleanup_free_ char *getty = NULL;
+        int master = -1, r, ret = 0;
         sd_bus *bus = userdata;
         const char *pty, *p;
-        pid_t leader;
         sigset_t mask;
-        int r, ret = 0;
         char last_char = 0;
 
         assert(bus);
@@ -1127,17 +1038,22 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to attach bus to event loop: %m");
 
-        r = machine_get_leader(bus, argv[1], &leader);
-        if (r < 0)
+        r = sd_bus_call_method(bus,
+                               "org.freedesktop.machine1",
+                               "/org/freedesktop/machine1",
+                               "org.freedesktop.machine1.Manager",
+                               "OpenMachinePTY",
+                               &error,
+                               &reply,
+                               "s", argv[1]);
+        if (r < 0) {
+                log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
                 return r;
+        }
 
-        master = openpt_in_namespace(leader, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
-        if (master < 0)
-                return log_error_errno(master, "Failed to acquire pseudo tty: %m");
-
-        pty = ptsname(master);
-        if (!pty)
-                return log_error_errno(errno, "Failed to get pty name: %m");
+        r = sd_bus_message_read(reply, "hs", &master, &pty);
+        if (r < 0)
+                return r;
 
         p = startswith(pty, "/dev/pts/");
         if (!p) {
@@ -1161,7 +1077,7 @@ static int login_machine(int argc, char *argv[], void *userdata) {
                                "/org/freedesktop/systemd1",
                                "org.freedesktop.systemd1.Manager",
                                "StartUnit",
-                               &error, &reply,
+                               &error, NULL,
                                "ss", getty, "replace");
         if (r < 0) {
                 log_error("Failed to start getty service: %s", bus_error_message(&error, r));
diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c
index 0229564..370d04a 100644
--- a/src/machine/machined-dbus.c
+++ b/src/machine/machined-dbus.c
@@ -515,6 +515,27 @@ static int method_list_images(sd_bus *bus, sd_bus_message *message, void *userda
         return sd_bus_send(bus, reply, NULL);
 }
 
+static int method_open_machine_pty(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        Manager *m = userdata;
+        Machine *machine;
+        const char *name;
+        int r;
+
+        assert(bus);
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_read(message, "s", &name);
+        if (r < 0)
+                return sd_bus_error_set_errno(error, r);
+
+        machine = hashmap_get(m->machines, name);
+        if (!machine)
+                return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+        return bus_machine_method_open_pty(bus, message, machine, error);
+}
+
 const sd_bus_vtable manager_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED),
@@ -530,6 +551,7 @@ const sd_bus_vtable manager_vtable[] = {
         SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_CAPABILITY(CAP_KILL)),
         SD_BUS_METHOD("GetMachineAddresses", "s", "a(iay)", method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("GetMachineOSRelease", "s", "a{ss}", method_get_machine_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0),
         SD_BUS_SIGNAL("MachineNew", "so", 0),
         SD_BUS_SIGNAL("MachineRemoved", "so", 0),
         SD_BUS_VTABLE_END

commit 095dc59660c3dde782f32fe5a52b577f7700578b
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Dec 22 20:39:10 2014 +0100

    systemd-run: add --quiet mode to suppress informational message on TTY usage

diff --git a/man/systemd-run.xml b/man/systemd-run.xml
index 5fb4ee2..f57c13b 100644
--- a/man/systemd-run.xml
+++ b/man/systemd-run.xml
@@ -232,11 +232,21 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
         <listitem><para>When invoking a command as service connects
         its standard input and output to the invoking tty via a
         pseudo TTY device. This allows invoking binaries as services
-        that expect interactive user input, such as an interactive
+        that expect interactive user input, such as interactive
         command shells.</para></listitem>
       </varlistentry>
 
       <varlistentry>
+        <term><option>--quiet</option></term>
+        <term><option>-q</option></term>
+
+        <listitem><para>Suppresses additional informational output
+        while running. This is particularly useful in combination with
+        <option>--pty</option> when it will suppress the initial
+        message explaining how to terminate the TTY connection.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><option>--on-active=</option></term>
         <term><option>--on-boot=</option></term>
         <term><option>--on-startup=</option></term>
diff --git a/src/run/run.c b/src/run/run.c
index 828aa5c..242bed0 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -59,6 +59,7 @@ static usec_t arg_on_unit_active = 0;
 static usec_t arg_on_unit_inactive = 0;
 static char *arg_on_calendar = NULL;
 static char **arg_timer_property = NULL;
+static bool arg_quiet = false;
 
 static void help(void) {
         printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n"
@@ -82,7 +83,8 @@ static void help(void) {
                "     --gid=GROUP                  Run as system group\n"
                "     --nice=NICE                  Nice level\n"
                "     --setenv=NAME=VALUE          Set environment\n"
-               "  -t --pty                        Run service on pseudo tty\n\n"
+               "  -t --pty                        Run service on pseudo tty\n"
+               "  -q --quiet                      Suppress information messages during runtime\n\n"
                "Timer options:\n\n"
                "     --on-active=SEC              Run after seconds\n"
                "     --on-boot=SEC                Run after seconds from machine was booted up\n"
@@ -144,6 +146,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "setenv",            required_argument, NULL, ARG_SETENV           },
                 { "property",          required_argument, NULL, 'p'                  },
                 { "tty",               no_argument,       NULL, 't'                  },
+                { "quiet",             no_argument,       NULL, 'q'                  },
                 { "on-active",         required_argument, NULL, ARG_ON_ACTIVE        },
                 { "on-boot",           required_argument, NULL, ARG_ON_BOOT          },
                 { "on-startup",        required_argument, NULL, ARG_ON_STARTUP       },
@@ -160,7 +163,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "+hrH:M:p:t", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "+hrH:M:p:tq", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -255,6 +258,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_pty = true;
                         break;
 
+                case 'q':
+                        arg_quiet = true;
+                        break;
+
                 case ARG_ON_ACTIVE:
 
                         r = parse_sec(optarg, &arg_on_active);
@@ -736,7 +743,8 @@ static int start_transient_service(
                 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
                 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
 
-                log_info("Running as unit %s.\nPress ^] three times within 1s to disconnect TTY.", service);
+                if (!arg_quiet)
+                        log_info("Running as unit %s.\nPress ^] three times within 1s to disconnect TTY.", service);
 
                 r = pty_forward_new(event, master, false, &forward);
                 if (r < 0)
@@ -750,10 +758,10 @@ static int start_transient_service(
 
                 forward = pty_forward_free(forward);
 
-                if (last_char != '\n')
+                if (!arg_quiet && last_char != '\n')
                         fputc('\n', stdout);
 
-        } else
+        } else if (!arg_quiet)
                 log_info("Running as unit %s.", service);
 
         return 0;
@@ -871,7 +879,8 @@ static int start_transient_scope(
         if (!env)
                 return log_oom();
 
-        log_info("Running as unit %s.", scope);
+        if (!arg_quiet)
+                log_info("Running as unit %s.", scope);
 
         execvpe(argv[0], argv, env);
 

commit c7b7d4493aa03e9ef5fb1e670b8969a48aa494dd
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Dec 22 20:33:45 2014 +0100

    machinectl,nspawn: don't print extra final newline if pty terminal output was newline-terinated anyway

diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index 4eebcb7..f5b87a2 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -1110,6 +1110,7 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         pid_t leader;
         sigset_t mask;
         int r, ret = 0;
+        char last_char = 0;
 
         assert(bus);
 
@@ -1186,9 +1187,12 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to run event loop: %m");
 
+        pty_forward_last_char(forward, &last_char);
+
         forward = pty_forward_free(forward);
 
-        fputc('\n', stdout);
+        if (last_char != '\n')
+                fputc('\n', stdout);
 
         log_info("Connection to container %s terminated.", argv[1]);
 
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index f1f9b78..01b8c32 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -3462,6 +3462,7 @@ int main(int argc, char *argv[]) {
                 if (barrier_place_and_sync(&barrier)) {
                         _cleanup_event_unref_ sd_event *event = NULL;
                         _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
+                        char last_char = 0;
                         int ifi = 0;
 
                         r = move_network_interfaces(pid);
@@ -3531,9 +3532,11 @@ int main(int argc, char *argv[]) {
                                 goto finish;
                         }
 
+                        pty_forward_last_char(forward, &last_char);
+
                         forward = pty_forward_free(forward);
 
-                        if (!arg_quiet)
+                        if (!arg_quiet && last_char != '\n')
                                 putc('\n', stdout);
 
                         /* Kill if it is not dead yet anyway */

commit 9b15b7846d4de01bb5d9700a24077787e984e8ab
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Dec 22 19:45:32 2014 +0100

    run: add a new "-t" mode for invoking a binary on an allocated TTY

diff --git a/man/systemd-run.xml b/man/systemd-run.xml
index b9cec91..5fb4ee2 100644
--- a/man/systemd-run.xml
+++ b/man/systemd-run.xml
@@ -225,10 +225,16 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
         </listitem>
       </varlistentry>
 
-      <xi:include href="user-system-options.xml" xpointer="user" />
-      <xi:include href="user-system-options.xml" xpointer="system" />
-      <xi:include href="user-system-options.xml" xpointer="host" />
-      <xi:include href="user-system-options.xml" xpointer="machine" />
+      <varlistentry>
+        <term><option>--pty</option></term>
+        <term><option>-t</option></term>
+
+        <listitem><para>When invoking a command as service connects
+        its standard input and output to the invoking tty via a
+        pseudo TTY device. This allows invoking binaries as services
+        that expect interactive user input, such as an interactive
+        command shells.</para></listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><option>--on-active=</option></term>
@@ -278,6 +284,11 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
         <command>set-property</command> command.</para> </listitem>
       </varlistentry>
 
+      <xi:include href="user-system-options.xml" xpointer="user" />
+      <xi:include href="user-system-options.xml" xpointer="system" />
+      <xi:include href="user-system-options.xml" xpointer="host" />
+      <xi:include href="user-system-options.xml" xpointer="machine" />
+
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
     </variablelist>
@@ -333,6 +344,13 @@ Dec 08 20:44:38 container systemd[1]: Started /bin/touch /tmp/foo.
 -- Logs begin at Fri 2014-12-05 19:09:21 KST, end at Mon 2014-12-08 20:44:54 KST. --
 Dec 08 20:44:48 container systemd[1]: Starting /bin/touch /tmp/foo...
 Dec 08 20:44:48 container systemd[1]: Started /bin/touch /tmp/foo.</programlisting>
+
+    <para>The following command invokes <filename>/bin/bash</filename>
+    as a service passing its standard input, output and error to
+    the calling TTY.</para>
+
+    <programlisting># systemd-run -t /bin/bash</programlisting>
+
   </refsect1>
 
   <refsect1>
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index bbcd610..a9f7971 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -31,11 +31,12 @@
 #include "strv.h"
 #include "fileio.h"
 #include "execute.h"
-#include "dbus-execute.h"
 #include "capability.h"
 #include "env-util.h"
 #include "af-list.h"
 #include "namespace.h"
+#include "path-util.h"
+#include "dbus-execute.h"
 
 #ifdef HAVE_SECCOMP
 #include "seccomp-util.h"
@@ -845,6 +846,92 @@ int bus_exec_context_set_transient_property(
 
                 return 1;
 
+        } else if (streq(name, "TTYPath")) {
+                const char *tty;
+
+                r = sd_bus_message_read(message, "s", &tty);
+                if (r < 0)
+                        return r;
+
+                if (!path_is_absolute(tty))
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "TTY device not absolute path");
+
+                if (mode != UNIT_CHECK) {
+                        char *t;
+
+                        t = strdup(tty);
+                        if (!t)
+                                return -ENOMEM;
+
+                        free(c->tty_path);
+                        c->tty_path = t;
+
+                        unit_write_drop_in_private_format(u, mode, name, "TTYPath=%s\n", tty);
+                }
+
+                return 1;
+
+        } else if (streq(name, "StandardInput")) {
+                const char *s;
+                ExecInput p;
+
+                r = sd_bus_message_read(message, "s", &s);
+                if (r < 0)
+                        return r;
+
+                p = exec_input_from_string(s);
+                if (p < 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard input name");
+
+                if (mode != UNIT_CHECK) {
+                        c->std_input = p;
+
+                        unit_write_drop_in_private_format(u, mode, name, "StandardInput=%s\n", exec_input_to_string(p));
+                }
+
+                return 1;
+
+
+        } else if (streq(name, "StandardOutput")) {
+                const char *s;
+                ExecOutput p;
+
+                r = sd_bus_message_read(message, "s", &s);
+                if (r < 0)
+                        return r;
+
+                p = exec_output_from_string(s);
+                if (p < 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard output name");
+
+                if (mode != UNIT_CHECK) {
+                        c->std_output = p;
+
+                        unit_write_drop_in_private_format(u, mode, name, "StandardOutput=%s\n", exec_output_to_string(p));
+                }
+
+                return 1;
+
+        } else if (streq(name, "StandardError")) {
+                const char *s;
+                ExecOutput p;
+
+                r = sd_bus_message_read(message, "s", &s);
+                if (r < 0)
+                        return r;
+
+                p = exec_output_from_string(s);
+                if (p < 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard error name");
+
+                if (mode != UNIT_CHECK) {
+                        c->std_error = p;
+
+                        unit_write_drop_in_private_format(u, mode, name, "StandardError=%s\n", exec_output_to_string(p));
+                }
+
+                return 1;
+
         } else if (streq(name, "Environment")) {
 
                 _cleanup_strv_free_ char **l = NULL;
diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index cde0c6a..4eebcb7 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -1178,7 +1178,7 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
         sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
 
-        r = pty_forward_new(event, master, &forward);
+        r = pty_forward_new(event, master, true, &forward);
         if (r < 0)
                 return log_error_errno(r, "Failed to create PTY forwarder: %m");
 
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 72f7d66..f1f9b78 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -3519,7 +3519,7 @@ int main(int argc, char *argv[]) {
                         /* simply exit on sigchld */
                         sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL);
 
-                        r = pty_forward_new(event, master, &forward);
+                        r = pty_forward_new(event, master, true, &forward);
                         if (r < 0) {
                                 log_error_errno(r, "Failed to create PTY forwarder: %m");
                                 goto finish;
diff --git a/src/run/run.c b/src/run/run.c
index 7a80223..828aa5c 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -23,7 +23,9 @@
 #include <getopt.h>
 
 #include "sd-bus.h"
+#include "sd-event.h"
 #include "bus-util.h"
+#include "event-util.h"
 #include "strv.h"
 #include "build.h"
 #include "unit-name.h"
@@ -31,6 +33,7 @@
 #include "path-util.h"
 #include "bus-error.h"
 #include "calendarspec.h"
+#include "ptyfwd.h"
 
 static bool arg_scope = false;
 static bool arg_remain_after_exit = false;
@@ -48,6 +51,7 @@ static int arg_nice = 0;
 static bool arg_nice_set = false;
 static char **arg_environment = NULL;
 static char **arg_property = NULL;
+static bool arg_pty = false;
 static usec_t arg_on_active = 0;
 static usec_t arg_on_boot = 0;
 static usec_t arg_on_startup = 0;
@@ -77,7 +81,8 @@ static void help(void) {
                "     --uid=USER                   Run as system user\n"
                "     --gid=GROUP                  Run as system group\n"
                "     --nice=NICE                  Nice level\n"
-               "     --setenv=NAME=VALUE          Set environment\n\n"
+               "     --setenv=NAME=VALUE          Set environment\n"
+               "  -t --pty                        Run service on pseudo tty\n\n"
                "Timer options:\n\n"
                "     --on-active=SEC              Run after seconds\n"
                "     --on-boot=SEC                Run after seconds from machine was booted up\n"
@@ -109,6 +114,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_SERVICE_TYPE,
                 ARG_NICE,
                 ARG_SETENV,
+                ARG_TTY,
                 ARG_ON_ACTIVE,
                 ARG_ON_BOOT,
                 ARG_ON_STARTUP,
@@ -137,6 +143,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "nice",              required_argument, NULL, ARG_NICE             },
                 { "setenv",            required_argument, NULL, ARG_SETENV           },
                 { "property",          required_argument, NULL, 'p'                  },
+                { "tty",               no_argument,       NULL, 't'                  },
                 { "on-active",         required_argument, NULL, ARG_ON_ACTIVE        },
                 { "on-boot",           required_argument, NULL, ARG_ON_BOOT          },
                 { "on-startup",        required_argument, NULL, ARG_ON_STARTUP       },
@@ -153,7 +160,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "+hrH:M:p:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "+hrH:M:p:t", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -244,6 +251,10 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case 't':
+                        arg_pty = true;
+                        break;
+
                 case ARG_ON_ACTIVE:
 
                         r = parse_sec(optarg, &arg_on_active);
@@ -339,6 +350,11 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
+        if (arg_pty && (with_timer() || arg_scope)) {
+                log_error("--pty is not compatible in timer or --scope mode.");
+                return -EINVAL;
+        }
+
         if (arg_scope && with_timer()) {
                 log_error("Timer options are not supported in --scope mode.");
                 return -EINVAL;
@@ -352,11 +368,15 @@ static int parse_argv(int argc, char *argv[]) {
         return 1;
 }
 
-static int transient_unit_set_properties(sd_bus_message *m, UnitType t) {
+static int transient_unit_set_properties(sd_bus_message *m, char **properties) {
         char **i;
         int r;
 
-        STRV_FOREACH(i, t == UNIT_TIMER ? arg_timer_property : arg_property) {
+        r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, properties) {
                 r = sd_bus_message_open_container(m, 'r', "sv");
                 if (r < 0)
                         return r;
@@ -373,9 +393,12 @@ static int transient_unit_set_properties(sd_bus_message *m, UnitType t) {
                         return r;
         }
 
-        r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description);
-        if (r < 0)
-                return r;
+        return 0;
+}
+
+static int transient_cgroup_set_properties(sd_bus_message *m) {
+        int r;
+        assert(m);
 
         if (!isempty(arg_slice)) {
                 _cleanup_free_ char *slice;
@@ -389,21 +412,36 @@ static int transient_unit_set_properties(sd_bus_message *m, UnitType t) {
                         return r;
         }
 
-        if (arg_send_sighup && t != UNIT_TIMER) {
+        return 0;
+}
+
+static int transient_kill_set_properties(sd_bus_message *m) {
+        int r;
+        assert(m);
+
+        if (arg_send_sighup) {
                 r = sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", arg_send_sighup);
                 if (r < 0)
                         return r;
         }
 
-        return 0;
+        return r;
 }
 
-static int transient_service_set_properties(sd_bus_message *m, char **argv) {
+static int transient_service_set_properties(sd_bus_message *m, char **argv, const char *pty_path) {
         int r;
 
         assert(m);
 
-        r = transient_unit_set_properties(m, UNIT_SERVICE);
+        r = transient_unit_set_properties(m, arg_property);
+        if (r < 0)
+                return r;
+
+        r = transient_kill_set_properties(m);
+        if (r < 0)
+                return r;
+
+        r = transient_cgroup_set_properties(m);
         if (r < 0)
                 return r;
 
@@ -437,6 +475,31 @@ static int transient_service_set_properties(sd_bus_message *m, char **argv) {
                         return r;
         }
 
+        if (pty_path) {
+                const char *e;
+
+                r = sd_bus_message_append(m,
+                                          "(sv)(sv)(sv)(sv)",
+                                          "StandardInput", "s", "tty",
+                                          "StandardOutput", "s", "tty",
+                                          "StandardError", "s", "tty",
+                                          "TTYPath", "s", pty_path);
+                if (r < 0)
+                        return r;
+
+                e = getenv("TERM");
+                if (e) {
+                        char *n;
+
+                        n = strappenda("TERM=", e);
+                        r = sd_bus_message_append(m,
+                                                  "(sv)",
+                                                  "Environment", "as", 1, n);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
         if (!strv_isempty(arg_environment)) {
                 r = sd_bus_message_open_container(m, 'r', "sv");
                 if (r < 0)
@@ -517,12 +580,32 @@ static int transient_service_set_properties(sd_bus_message *m, char **argv) {
         return 0;
 }
 
+static int transient_scope_set_properties(sd_bus_message *m) {
+        int r;
+
+        assert(m);
+
+        r = transient_unit_set_properties(m, arg_property);
+        if (r < 0)
+                return r;
+
+        r = transient_kill_set_properties(m);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid());
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 static int transient_timer_set_properties(sd_bus_message *m) {
         int r;
 
         assert(m);
 
-        r = transient_unit_set_properties(m, UNIT_TIMER);
+        r = transient_unit_set_properties(m, arg_timer_property);
         if (r < 0)
                 return r;
 
@@ -565,22 +648,6 @@ static int transient_timer_set_properties(sd_bus_message *m) {
         return 0;
 }
 
-static int transient_scope_set_properties(sd_bus_message *m) {
-        int r;
-
-        assert(m);
-
-        r = transient_unit_set_properties(m, UNIT_SCOPE);
-        if (r < 0)
-                return r;
-
-        r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid());
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
 static int start_transient_service(
                 sd_bus *bus,
                 char **argv,
@@ -588,11 +655,26 @@ static int start_transient_service(
 
         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
         _cleanup_free_ char *service = NULL;
+        _cleanup_close_ int master = -1;
+        const char *pty_path = NULL;
         int r;
 
         assert(bus);
         assert(argv);
 
+        if (arg_pty) {
+                master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
+                if (master < 0)
+                        return log_error_errno(errno, "Failed to acquire pseudo tty: %m");
+
+                pty_path = ptsname(master);
+                if (!pty_path)
+                        return log_error_errno(errno, "Failed to determine tty name: %m");
+
+                if (unlockpt(master) < 0)
+                        return log_error_errno(errno, "Failed to unlock tty: %m");
+        }
+
         if (arg_unit) {
                 service = unit_name_mangle_with_suffix(arg_unit, MANGLE_NOGLOB, ".service");
                 if (!service)
@@ -610,17 +692,17 @@ static int start_transient_service(
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* name and mode */
+        /* Name and mode */
         r = sd_bus_message_append(m, "ss", service, "fail");
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* properties */
+        /* Properties */
         r = sd_bus_message_open_container(m, 'a', "(sv)");
         if (r < 0)
                 return bus_log_create_error(r);
 
-        r = transient_service_set_properties(m, argv);
+        r = transient_service_set_properties(m, argv, pty_path);
         if (r < 0)
                 return bus_log_create_error(r);
 
@@ -628,138 +710,51 @@ static int start_transient_service(
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* aux */
+        /* Auxiliary units */
         r = sd_bus_message_append(m, "a(sa(sv))", 0);
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* send dbus */
         r = sd_bus_call(bus, m, 0, error, NULL);
         if (r < 0)
                 return bus_log_create_error(r);
 
-        log_info("Running as unit %s.", service);
-
-        return 0;
-}
-
-static int start_transient_timer(
-                sd_bus *bus,
-                char **argv,
-                sd_bus_error *error) {
-
-        _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
-        _cleanup_free_ char *timer = NULL, *service = NULL;
-        int r;
-
-        assert(bus);
-        assert(argv);
+        if (master >= 0) {
+                _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
+                _cleanup_event_unref_ sd_event *event = NULL;
+                sigset_t mask;
+                char last_char = 0;
 
-        if (arg_unit) {
-                switch(unit_name_to_type(arg_unit)) {
-                case UNIT_SERVICE:
-                        service = strdup(arg_unit);
-                        timer = unit_name_change_suffix(service, ".timer");
-                        if (!timer)
-                                return log_oom();
-                        break;
-
-                case UNIT_TIMER:
-                        timer = strdup(arg_unit);
-                        service = unit_name_change_suffix(timer, ".service");
-                        if (!service)
-                                return log_oom();
-                        break;
-
-                default:
-                        service = unit_name_mangle_with_suffix(arg_unit, MANGLE_NOGLOB, ".service");
-                        if (!service)
-                                return log_oom();
-
-                        timer = unit_name_mangle_with_suffix(arg_unit, MANGLE_NOGLOB, ".timer");
-                        if (!timer)
-                                return log_oom();
-
-                        break;
-                }
-        } else if ((asprintf(&service, "run-"PID_FMT".service", getpid()) < 0) ||
-                   (asprintf(&timer, "run-"PID_FMT".timer", getpid()) < 0))
-                return log_oom();
-
-        r = sd_bus_message_new_method_call(
-                        bus,
-                        &m,
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        "StartTransientUnit");
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        /* name and mode */
-        r = sd_bus_message_append(m, "ss", timer, "fail");
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        /* properties */
-        r = sd_bus_message_open_container(m, 'a', "(sv)");
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        r = transient_timer_set_properties(m);
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        r = sd_bus_message_close_container(m);
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        if (argv[0]) {
-                r = sd_bus_message_open_container(m, 'a', "(sa(sv))");
+                r = sd_event_default(&event);
                 if (r < 0)
-                        return bus_log_create_error(r);
+                        return log_error_errno(r, "Failed to get event loop: %m");
 
-                r = sd_bus_message_open_container(m, 'r', "sa(sv)");
-                if (r < 0)
-                        return bus_log_create_error(r);
+                assert_se(sigemptyset(&mask) == 0);
+                sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
+                assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
 
-                r = sd_bus_message_append(m, "s", service);
-                if (r < 0)
-                        return bus_log_create_error(r);
+                sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
+                sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
 
-                r = sd_bus_message_open_container(m, 'a', "(sv)");
-                if (r < 0)
-                        return bus_log_create_error(r);
+                log_info("Running as unit %s.\nPress ^] three times within 1s to disconnect TTY.", service);
 
-                r = transient_service_set_properties(m, argv);
+                r = pty_forward_new(event, master, false, &forward);
                 if (r < 0)
-                        return bus_log_create_error(r);
+                        return log_error_errno(r, "Failed to create PTY forwarder: %m");
 
-                r = sd_bus_message_close_container(m);
+                r = sd_event_loop(event);
                 if (r < 0)
-                        return bus_log_create_error(r);
+                        return log_error_errno(r, "Failed to run event loop: %m");
 
-                r = sd_bus_message_close_container(m);
-                if (r < 0)
-                        return bus_log_create_error(r);
+                pty_forward_last_char(forward, &last_char);
 
-                r = sd_bus_message_close_container(m);
-                if (r < 0)
-                        return bus_log_create_error(r);
-        } else {
-                r = sd_bus_message_append(m, "a(sa(sv))", 0);
-                if (r < 0)
-                        return bus_log_create_error(r);
-        }
+                forward = pty_forward_free(forward);
 
-        /* send dbus */
-        r = sd_bus_call(bus, m, 0, error, NULL);
-        if (r < 0)
-                return bus_log_create_error(r);
+                if (last_char != '\n')
+                        fputc('\n', stdout);
 
-        log_info("Running as unit %s.", timer);
-        if (argv[0])
-                log_info("Will run as unit %s.", service);
+        } else
+                log_info("Running as unit %s.", service);
 
         return 0;
 }
@@ -769,9 +764,9 @@ static int start_transient_scope(
                 char **argv,
                 sd_bus_error *error) {
 
+        _cleanup_strv_free_ char **env = NULL, **user_env = NULL;
         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
         _cleanup_free_ char *scope = NULL;
-        _cleanup_strv_free_ char **env = NULL, **user_env = NULL;
         int r;
 
         assert(bus);
@@ -785,21 +780,21 @@ static int start_transient_scope(
                 return log_oom();
 
         r = sd_bus_message_new_method_call(
-                bus,
-                &m,
-                "org.freedesktop.systemd1",
-                "/org/freedesktop/systemd1",
-                "org.freedesktop.systemd1.Manager",
-                "StartTransientUnit");
+                        bus,
+                        &m,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "StartTransientUnit");
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* name and mode */
+        /* Name and Mode */
         r = sd_bus_message_append(m, "ss", scope, "fail");
         if (r < 0)
                 return bus_log_create_error(r);
 
-        /* properties */
+        /* Properties */
         r = sd_bus_message_open_container(m, 'a', "(sv)");
         if (r < 0)
                 return bus_log_create_error(r);
@@ -879,8 +874,132 @@ static int start_transient_scope(
         log_info("Running as unit %s.", scope);
 
         execvpe(argv[0], argv, env);
-        log_error_errno(errno, "Failed to execute: %m");
-        return -errno;
+
+        return log_error_errno(errno, "Failed to execute: %m");
+}
+
+static int start_transient_timer(
+                sd_bus *bus,
+                char **argv,
+                sd_bus_error *error) {
+
+        _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
+        _cleanup_free_ char *timer = NULL, *service = NULL;
+        int r;
+
+        assert(bus);
+        assert(argv);
+
+        if (arg_unit) {
+                switch(unit_name_to_type(arg_unit)) {
+
+                case UNIT_SERVICE:
+                        service = strdup(arg_unit);
+                        if (!service)
+                                return log_oom();
+
+                        timer = unit_name_change_suffix(service, ".timer");
+                        if (!timer)
+                                return log_oom();
+                        break;
+
+                case UNIT_TIMER:
+                        timer = strdup(arg_unit);
+                        if (!timer)
+                                return log_oom();
+
+                        service = unit_name_change_suffix(timer, ".service");
+                        if (!service)
+                                return log_oom();
+                        break;
+
+                default:
+                        service = unit_name_mangle_with_suffix(arg_unit, MANGLE_NOGLOB, ".service");
+                        if (!service)
+                                return log_oom();
+
+                        timer = unit_name_mangle_with_suffix(arg_unit, MANGLE_NOGLOB, ".timer");
+                        if (!timer)
+                                return log_oom();
+
+                        break;
+                }
+        } else if ((asprintf(&service, "run-"PID_FMT".service", getpid()) < 0) ||
+                   (asprintf(&timer, "run-"PID_FMT".timer", getpid()) < 0))
+                return log_oom();
+
+        r = sd_bus_message_new_method_call(
+                        bus,
+                        &m,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "StartTransientUnit");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        /* Name and Mode */
+        r = sd_bus_message_append(m, "ss", timer, "fail");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        /* Properties */
+        r = sd_bus_message_open_container(m, 'a', "(sv)");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = transient_timer_set_properties(m);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_close_container(m);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_open_container(m, 'a', "(sa(sv))");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        if (argv[0]) {
+                r = sd_bus_message_open_container(m, 'r', "sa(sv)");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "s", service);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'a', "(sv)");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = transient_service_set_properties(m, argv, NULL);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+        }
+
+        r = sd_bus_message_close_container(m);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        /* send dbus */
+        r = sd_bus_call(bus, m, 0, error, NULL);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        log_info("Running as unit %s.", timer);
+        if (argv[0])
+                log_info("Will run as unit %s.", service);
+
+        return 0;
 }
 
 int main(int argc, char* argv[]) {
diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c
index 085d374..11356e2 100644
--- a/src/shared/ptyfwd.c
+++ b/src/shared/ptyfwd.c
@@ -53,6 +53,11 @@ struct PTYForward {
         bool master_writable:1;
         bool master_hangup:1;
 
+        bool repeat:1;
+
+        bool last_char_set:1;
+        char last_char;
+
         char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
         size_t in_buffer_full, out_buffer_full;
 
@@ -169,11 +174,12 @@ static int shovel(PTYForward *f) {
                                  * might be cause by vhangup() or
                                  * temporary closing of everything on
                                  * the other side, we treat it like
-                                 * EAGAIN here and try again. */
+                                 * EAGAIN here and try again, unless
+                                 * repeat is off. */
 
-                                if (errno == EAGAIN || errno == EIO)
+                                if (errno == EAGAIN || (errno == EIO && f->repeat))
                                         f->master_readable = false;
-                                else if (errno == EPIPE || errno == ECONNRESET) {
+                                else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
                                         f->master_readable = f->master_writable = false;
                                         f->master_hangup = true;
 
@@ -203,6 +209,12 @@ static int shovel(PTYForward *f) {
                                 }
 
                         } else {
+
+                                if (k > 0) {
+                                        f->last_char = f->out_buffer[k-1];
+                                        f->last_char_set = true;
+                                }
+
                                 assert(f->out_buffer_full >= (size_t) k);
                                 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
                                 f->out_buffer_full -= k;
@@ -285,7 +297,7 @@ static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *
         return 0;
 }
 
-int pty_forward_new(sd_event *event, int master, PTYForward **ret) {
+int pty_forward_new(sd_event *event, int master, bool repeat, PTYForward **ret) {
         _cleanup_(pty_forward_freep) PTYForward *f = NULL;
         struct winsize ws;
         int r;
@@ -294,6 +306,8 @@ int pty_forward_new(sd_event *event, int master, PTYForward **ret) {
         if (!f)
                 return -ENOMEM;
 
+        f->repeat = repeat;
+
         if (event)
                 f->event = sd_event_ref(event);
         else {
@@ -388,3 +402,14 @@ PTYForward *pty_forward_free(PTYForward *f) {
 
         return NULL;
 }
+
+int pty_forward_last_char(PTYForward *f, char *ch) {
+        assert(f);
+        assert(ch);
+
+        if (!f->last_char_set)
+                return -ENXIO;
+
+        *ch = f->last_char;
+        return 0;
+}
diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h
index 5a612fd..d7b658e 100644
--- a/src/shared/ptyfwd.h
+++ b/src/shared/ptyfwd.h
@@ -28,7 +28,9 @@
 
 typedef struct PTYForward PTYForward;
 
-int pty_forward_new(sd_event *event, int master, PTYForward **f);
+int pty_forward_new(sd_event *event, int master, bool repeat, PTYForward **f);
 PTYForward *pty_forward_free(PTYForward *f);
 
+int pty_forward_last_char(PTYForward *f, char *ch);
+
 DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free);

commit 91f4347ef7bde17418b365ed3a97a752fe65bd50
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon Dec 22 19:42:27 2014 +0100

    import: rename 'poll-dck' to 'pull-dkr'
    
    I figure "pull-dck" is not a good name, given that one could certainly
    read the verb in a way that might be funny for 16year-olds. ;-)
    
    Also, don't hardcode the index URL to use, make it runtime and configure
    time configurable instead.

diff --git a/Makefile.am b/Makefile.am
index baa1398..904517f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5131,8 +5131,8 @@ bin_PROGRAMS += \
 
 systemd_import_SOURCES = \
 	src/import/import.c \
-	src/import/import-dck.c \
-	src/import/import-dck.h \
+	src/import/import-dkr.c \
+	src/import/import-dkr.h \
 	src/import/curl-util.c \
 	src/import/curl-util.h \
 	src/import/aufs-util.c \
diff --git a/TODO b/TODO
index fedcf59..587a692 100644
--- a/TODO
+++ b/TODO
@@ -51,7 +51,7 @@ Features:
 
 * make "machinectl login" use a new machined call AllocateMachinePty() or so to get a pty in a machine. That would open up logins to unprivileged clients
 
-* add transparent btrfs pool in a loopback file in /var if btrfs operations (such as systemd-import pull-dck) are used and /var is not a btrfs file system
+* add transparent btrfs pool in a loopback file in /var if btrfs operations (such as systemd-import pull-dkr) are used and /var is not a btrfs file system
 
 * machined: open up certain commands to unprivileged clients via polkit
 
diff --git a/configure.ac b/configure.ac
index 9296c25..0e72166 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1316,6 +1316,14 @@ AC_ARG_ENABLE([split-usr],
                 enable_split_usr=no
         ])])
 
+AC_ARG_WITH([dkr-index-url],
+        [AS_HELP_STRING([--dkr-index-url=URL], [Specify the default index URL to use for image downloads])],
+        [DEFAULT_DKR_INDEX_URL="\"$withval\""],
+        [DEFAULT_DKR_INDEX_URL="NULL"])
+
+AC_DEFINE_UNQUOTED(DEFAULT_DKR_INDEX_URL, [$DEFAULT_DKR_INDEX_URL], [Default index URL to use for image downloads])
+AC_SUBST(DEFAULT_DKR_INDEX_URL)
+
 AS_IF([test "x${enable_split_usr}" = "xyes"], [
         AC_DEFINE(HAVE_SPLIT_USR, 1, [Define if /bin, /sbin aren't symlinks into /usr])
 ])
@@ -1478,6 +1486,7 @@ AC_MSG_RESULT([
         Maximum System UID:      ${SYSTEM_UID_MAX}
         Maximum System GID:      ${SYSTEM_GID_MAX}
         Certificate root:        ${CERTIFICATEROOT}
+        Default dkr Index        ${DEFAULT_DKR_INDEX_URL}
 
         CFLAGS:                  ${OUR_CFLAGS} ${CFLAGS}
         CPPFLAGS:                ${OUR_CPPFLAGS} ${CPPFLAGS}
diff --git a/src/import/import-dck.c b/src/import/import-dck.c
deleted file mode 100644
index adb0c90..0000000
--- a/src/import/import-dck.c
+++ /dev/null
@@ -1,1156 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include <curl/curl.h>
-#include <sys/prctl.h>
-
-#include "hashmap.h"
-#include "set.h"
-#include "json.h"
-#include "strv.h"
-#include "curl-util.h"
-#include "import-dck.h"
-#include "btrfs-util.h"
-#include "aufs-util.h"
-
-/* TODO:
-  - convert json bits
-  - man page
-  - fall back to btrfs loop pool device
-*/
-
-typedef struct DckImportJob DckImportJob;
-typedef struct DckImportName DckImportName;
-
-typedef enum DckImportJobType {
-        DCK_IMPORT_JOB_IMAGES,
-        DCK_IMPORT_JOB_TAGS,
-        DCK_IMPORT_JOB_ANCESTRY,
-        DCK_IMPORT_JOB_JSON,
-        DCK_IMPORT_JOB_LAYER,
-} DckImportJobType;
-
-struct DckImportJob {
-        DckImport *import;
-        DckImportJobType type;
-        bool done;
-
-        char *url;
-
-        Set *needed_by; /* DckImport Name objects */
-
-        CURL *curl;
-        struct curl_slist *request_header;
-        void *payload;
-        size_t payload_size;
-
-        char *response_token;
-        char **response_registries;
-
-        char *temp_path;
-        char *final_path;
-
-        pid_t tar_pid;
-        FILE *tar_stream;
-};
-
-struct DckImportName {
-        DckImport *import;
-
-        char *name;
-        char *tag;
-        char *id;
-        char *local;
-
-        DckImportJob *job_images, *job_tags, *job_ancestry, *job_json, *job_layer;
-
-        char **ancestry;
-        unsigned current_ancestry;
-
-        bool force_local;
-};
-
-struct DckImport {
-        sd_event *event;
-        CurlGlue *glue;
-
-        Hashmap *names;
-        Hashmap *jobs;
-
-        dck_import_on_finished on_finished;
-        void *userdata;
-};
-
-#define PROTOCOL_PREFIX "https://"
-#define INDEX_HOST "index.do" /* the URL we get the data from */ "cker.io"
-
-#define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
-#define HEADER_REGISTRY "X-Do" /*the HTTP header for the registry */ "cker-Endpoints:"
-
-#define PAYLOAD_MAX (16*1024*1024)
-#define LAYERS_MAX 2048
-
-static int dck_import_name_add_job(DckImportName *name, DckImportJobType type, const char *url, DckImportJob **ret);
-
-static DckImportJob *dck_import_job_unref(DckImportJob *job) {
-        if (!job)
-                return NULL;
-
-        if (job->import)
-                curl_glue_remove_and_free(job->import->glue, job->curl);
-        curl_slist_free_all(job->request_header);
-
-        if (job->tar_stream)
-                fclose(job->tar_stream);
-
-        free(job->final_path);
-
-        if (job->temp_path) {
-                btrfs_subvol_remove(job->temp_path);
-                free(job->temp_path);
-        }
-
-        set_free(job->needed_by);
-
-        if (job->tar_pid > 0)
-                kill(job->tar_pid, SIGTERM);
-
-        free(job->url);
-        free(job->payload);
-        free(job->response_token);
-        strv_free(job->response_registries);
-
-        free(job);
-
-        return NULL;
-}
-
-static DckImportName *dck_import_name_unref(DckImportName *name) {
-        if (!name)
-                return NULL;
-
-        if (name->job_images)
-                set_remove(name->job_images->needed_by, name);
-
-        if (name->job_tags)
-                set_remove(name->job_tags->needed_by, name);
-
-        if (name->job_ancestry)
-                set_remove(name->job_ancestry->needed_by, name);
-
-        if (name->job_json)
-                set_remove(name->job_json->needed_by, name);
-
-        if (name->job_layer)
-                set_remove(name->job_layer->needed_by, name);
-
-        free(name->name);
-        free(name->id);
-        free(name->tag);
-        free(name->local);
-
-        strv_free(name->ancestry);
-        free(name);
-
-        return NULL;
-}
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(DckImportJob*, dck_import_job_unref);
-DEFINE_TRIVIAL_CLEANUP_FUNC(DckImportName*, dck_import_name_unref);
-
-static void dck_import_finish(DckImport *import, int error) {
-        assert(import);
-
-        if (import->on_finished)
-                import->on_finished(import, error, import->userdata);
-        else
-                sd_event_exit(import->event, error);
-}
-
-static int parse_id(const void *payload, size_t size, char **ret) {
-        _cleanup_free_ char *buf = NULL, *id = NULL, *other = NULL;
-        union json_value v = {};
-        void *json_state = NULL;
-        const char *p;
-        int t;
-
-        assert(payload);
-        assert(ret);
-
-        if (size <= 0)
-                return -EBADMSG;
-
-        if (memchr(payload, 0, size))
-                return -EBADMSG;
-
-        buf = strndup(payload, size);
-        if (!buf)
-                return -ENOMEM;
-
-        p = buf;
-        t = json_tokenize(&p, &id, &v, &json_state, NULL);
-        if (t < 0)
-                return t;
-        if (t != JSON_STRING)
-                return -EBADMSG;
-
-        t = json_tokenize(&p, &other, &v, &json_state, NULL);
-        if (t < 0)
-                return t;
-        if (t != JSON_END)
-                return -EBADMSG;
-
-        if (!dck_id_is_valid(id))
-                return -EBADMSG;
-
-        *ret = id;
-        id = NULL;
-
-        return 0;
-}
-
-static int parse_ancestry(const void *payload, size_t size, char ***ret) {
-        _cleanup_free_ char *buf = NULL;
-        void *json_state = NULL;
-        const char *p;
-        enum {
-                STATE_BEGIN,
-                STATE_ITEM,
-                STATE_COMMA,
-                STATE_END,
-        } state = STATE_BEGIN;
-        _cleanup_strv_free_ char **l = NULL;
-        size_t n = 0, allocated = 0;
-
-        if (size <= 0)
-                return -EBADMSG;
-
-        if (memchr(payload, 0, size))
-                return -EBADMSG;
-
-        buf = strndup(payload, size);
-        if (!buf)
-                return -ENOMEM;
-
-        p = buf;
-        for (;;) {
-                _cleanup_free_ char *str;
-                union json_value v = {};
-                int t;
-
-                t = json_tokenize(&p, &str, &v, &json_state, NULL);
-                if (t < 0)
-                        return t;
-
-                switch (state) {
-
-                case STATE_BEGIN:
-                        if (t == JSON_ARRAY_OPEN)
-                                state = STATE_ITEM;
-                        else
-                                return -EBADMSG;
-
-                        break;
-
-                case STATE_ITEM:
-                        if (t == JSON_STRING) {
-                                if (!dck_id_is_valid(str))
-                                        return -EBADMSG;
-
-                                if (n+1 > LAYERS_MAX)
-                                        return -EFBIG;
-
-                                if (!GREEDY_REALLOC(l, allocated, n + 2))
-                                        return -ENOMEM;
-
-                                l[n++] = str;
-                                str = NULL;
-                                l[n] = NULL;
-
-                                state = STATE_COMMA;
-
-                        } else if (t == JSON_ARRAY_CLOSE)
-                                state = STATE_END;
-                        else
-                                return -EBADMSG;
-
-                        break;
-
-                case STATE_COMMA:
-                        if (t == JSON_COMMA)
-                                state = STATE_ITEM;
-                        else if (t == JSON_ARRAY_CLOSE)
-                                state = STATE_END;
-                        else
-                                return -EBADMSG;
-                        break;
-
-                case STATE_END:
-                        if (t == JSON_END) {
-
-                                if (strv_isempty(l))
-                                        return -EBADMSG;
-
-                                if (!strv_is_uniq(l))
-                                        return -EBADMSG;
-
-                                l = strv_reverse(l);
-
-                                *ret = l;
-                                l = NULL;
-                                return 0;
-                        } else
-                                return -EBADMSG;
-                }
-
-        }
-}
-
-static const char *dck_import_name_current_layer(DckImportName *name) {
-        assert(name);
-
-        if (strv_isempty(name->ancestry))
-                return NULL;
-
-        return name->ancestry[name->current_ancestry];
-}
-
-static const char *dck_import_name_current_base_layer(DckImportName *name) {
-        assert(name);
-
-        if (strv_isempty(name->ancestry))
-                return NULL;
-
-        if (name->current_ancestry <= 0)
-                return NULL;
-
-        return name->ancestry[name->current_ancestry-1];
-}
-
-static char** dck_import_name_get_registries(DckImportName *name) {
-        assert(name);
-
-        if (!name->job_images)
-                return NULL;
-
-        if (!name->job_images->done)
-                return NULL;
-
-        if (strv_isempty(name->job_images->response_registries))
-                return NULL;
-
-        return name->job_images->response_registries;
-}
-
-static const char*dck_import_name_get_token(DckImportName *name) {
-        assert(name);
-
-        if (!name->job_images)
-                return NULL;
-
-        if (!name->job_images->done)
-                return NULL;
-
-        return name->job_images->response_token;
-}
-
-static void dck_import_name_maybe_finish(DckImportName *name) {
-        int r;
-
-        assert(name);
-
-        if (!name->job_images || !name->job_images->done)
-                return;
-
-        if (!name->job_ancestry || !name->job_ancestry->done)
-                return;
-
-        if (!name->job_json || !name->job_json->done)
-                return;
-
-        if (name->job_layer && !name->job_json->done)
-                return;
-
-        if (dck_import_name_current_layer(name))
-                return;
-
-        if (name->local) {
-                const char *p, *q;
-
-                assert(name->id);
-
-                p = strappenda("/var/lib/container/", name->local);
-                q = strappenda("/var/lib/container/.dck-", name->id);
-
-                if (name->force_local) {
-                        (void) btrfs_subvol_remove(p);
-                        (void) rm_rf(p, false, true, false);
-                }
-
-                r = btrfs_subvol_snapshot(q, p, false, false);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to snapshot final image: %m");
-                        dck_import_finish(name->import, r);
-                        return;
-                }
-
-                log_info("Created new image %s.", p);
-        }
-
-        dck_import_finish(name->import, 0);
-}
-
-static int dck_import_job_run_tar(DckImportJob *job) {
-        _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
-        bool gzip;
-
-        assert(job);
-
-        /* A stream to run tar on? */
-        if (!job->temp_path)
-                return 0;
-
-        if (job->tar_stream)
-                return 0;
-
-        /* Maybe fork off tar, if we have enough to figure out that
-         * something is gzip compressed or not */
-
-        if (job->payload_size < 2)
-                return 0;
-
-        /* Detect gzip signature */
-        gzip = ((uint8_t*) job->payload)[0] == 0x1f &&
-               ((uint8_t*) job->payload)[1] == 0x8b;
-
-        assert(!job->tar_stream);
-        assert(job->tar_pid <= 0);
-
-        if (pipe2(pipefd, O_CLOEXEC) < 0)
-                return log_error_errno(errno, "Failed to create pipe for tar: %m");
-
-        job->tar_pid = fork();
-        if (job->tar_pid < 0)
-                return log_error_errno(errno, "Failed to fork off tar: %m");
-        if (job->tar_pid == 0) {
-                int null_fd;
-
-                reset_all_signal_handlers();
-                reset_signal_mask();
-                assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
-
-                pipefd[1] = safe_close(pipefd[1]);
-
-                if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
-                        log_error_errno(errno, "Failed to dup2() fd: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (pipefd[0] != STDIN_FILENO)
-                        safe_close(pipefd[0]);
-                if (pipefd[1] != STDIN_FILENO)
-                        safe_close(pipefd[1]);
-
-                null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
-                if (null_fd < 0) {
-                        log_error_errno(errno, "Failed to open /dev/null: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
-                        log_error_errno(errno, "Failed to dup2() fd: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (null_fd != STDOUT_FILENO)
-                        safe_close(null_fd);
-
-                execlp("tar", "tar", "-C", job->temp_path, gzip ? "-xz" : "-x", NULL);
-                _exit(EXIT_FAILURE);
-        }
-
-        pipefd[0] = safe_close(pipefd[0]);
-
-        job->tar_stream = fdopen(pipefd[1], "w");
-        if (!job->tar_stream)
-                return log_error_errno(errno, "Failed to allocate tar stream: %m");
-
-        pipefd[1] = -1;
-
-        if (fwrite(job->payload, 1, job->payload_size, job->tar_stream) != job->payload_size)
-                return log_error_errno(errno, "Couldn't write payload: %m");
-
-        free(job->payload);
-        job->payload = NULL;
-        job->payload_size = 0;
-
-        return 0;
-}
-
-static int dck_import_name_pull_layer(DckImportName *name) {
-        _cleanup_free_ char *path = NULL, *temp = NULL;
-        const char *url, *layer = NULL, *base = NULL;
-        char **rg;
-        int r;
-
-        assert(name);
-
-        if (name->job_layer) {
-                set_remove(name->job_layer->needed_by, name);
-                name->job_layer = NULL;
-        }
-
-        for (;;) {
-                layer = dck_import_name_current_layer(name);
-                if (!layer) {
-                        dck_import_name_maybe_finish(name);
-                        return 0;
-                }
-
-                path = strjoin("/var/lib/container/.dck-", layer, NULL);
-                if (!path)
-                        return log_oom();
-
-                if (laccess(path, F_OK) < 0) {
-                        if (errno == ENOENT)
-                                break;
-
-                        return log_error_errno(errno, "Failed to check for container: %m");
-                }
-
-                log_info("Layer %s already exists, skipping.", layer);
-
-                name->current_ancestry++;
-
-                free(path);
-                path = NULL;
-        }
-
-        rg = dck_import_name_get_registries(name);
-        assert(rg && rg[0]);
-
-        url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", layer, "/layer");
-        r = dck_import_name_add_job(name, DCK_IMPORT_JOB_LAYER, url, &name->job_layer);
-        if (r < 0) {
-                log_error_errno(r, "Failed to issue HTTP request: %m");
-                return r;
-        }
-        if (r == 0) /* Already downloading this one? */
-                return 0;
-
-        log_info("Pulling layer %s...", layer);
-
-        r = tempfn_random(path, &temp);
-        if (r < 0)
-                return log_oom();
-
-        base = dck_import_name_current_base_layer(name);
-        if (base) {
-                const char *base_path;
-
-                base_path = strappend("/var/lib/container/.dck-", base);
-                r = btrfs_subvol_snapshot(base_path, temp, false, true);
-        } else
-                r = btrfs_subvol_make(temp);
-
-        if (r < 0)
-                return log_error_errno(r, "Failed to make btrfs subvolume %s", temp);
-
-        name->job_layer->final_path = path;
-        name->job_layer->temp_path = temp;
-        path = temp = NULL;
-
-        return 0;
-}
-
-static void dck_import_name_job_finished(DckImportName *name, DckImportJob *job) {
-        int r;
-
-        assert(name);
-        assert(job);
-
-        if (name->job_images == job) {
-                const char *url;
-                char **rg;
-
-                assert(!name->job_tags);
-                assert(!name->job_ancestry);
-                assert(!name->job_json);
-                assert(!name->job_layer);
-
-                rg = dck_import_name_get_registries(name);
-                if (strv_isempty(rg)) {
-                        log_error("Didn't get registry information.");
-                        r = -EBADMSG;
-                        goto fail;
-                }
-
-                log_info("Index lookup succeeded, directed to registry %s.", rg[0]);
-
-                url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/repositories/", name->name, "/tags/", name->tag);
-
-                r = dck_import_name_add_job(name, DCK_IMPORT_JOB_TAGS, url, &name->job_tags);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to issue HTTP request: %m");
-                        goto fail;
-                }
-
-        } else if (name->job_tags == job) {
-                const char *url;
-                char *id = NULL, **rg;
-
-                assert(!name->job_ancestry);
-                assert(!name->job_json);
-                assert(!name->job_layer);
-
-                r = parse_id(job->payload, job->payload_size, &id);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to parse JSON id.");
-                        goto fail;
-                }
-
-                free(name->id);
-                name->id = id;
-
-                rg = dck_import_name_get_registries(name);
-                assert(rg && rg[0]);
-
-                log_info("Tag lookup succeeded, resolved to layer %s.", name->id);
-
-                url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", name->id, "/ancestry");
-                r = dck_import_name_add_job(name, DCK_IMPORT_JOB_ANCESTRY, url, &name->job_ancestry);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to issue HTTP request: %m");
-                        goto fail;
-                }
-
-                url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", name->id, "/json");
-                r = dck_import_name_add_job(name, DCK_IMPORT_JOB_JSON, url, &name->job_json);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to issue HTTP request: %m");
-                        goto fail;
-                }
-
-        } else if (name->job_ancestry == job) {
-                char **ancestry = NULL, **i;
-                unsigned n;
-
-                r = parse_ancestry(job->payload, job->payload_size, &ancestry);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to parse JSON id.");
-                        goto fail;
-                }
-
-                n = strv_length(ancestry);
-                if (n <= 0 || !streq(ancestry[n-1], name->id)) {
-                        log_error("Ancestry doesn't end in main layer.");
-                        r = -EBADMSG;
-                        goto fail;
-                }
-
-                log_info("Ancestor lookup succeeded, requires layers:\n");
-                STRV_FOREACH(i, ancestry)
-                        log_info("\t%s", *i);
-
-                strv_free(name->ancestry);
-                name->ancestry = ancestry;
-
-                name->current_ancestry = 0;
-                r = dck_import_name_pull_layer(name);
-                if (r < 0)
-                        goto fail;
-
-        } else if (name->job_json == job) {
-
-                dck_import_name_maybe_finish(name);
-
-        } else if (name->job_layer == job) {
-
-                name->current_ancestry ++;
-                r = dck_import_name_pull_layer(name);
-                if (r < 0)
-                        goto fail;
-
-        } else
-                assert_not_reached("Got finished event for unknown curl object");
-
-        return;
-
-fail:
-        dck_import_finish(name->import, r);
-}
-
-static void dck_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
-        DckImportJob *job = NULL;
-        CURLcode code;
-        DckImportName *n;
-        long status;
-        Iterator i;
-        int r;
-
-        if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &job) != CURLE_OK)
-                return;
-
-        if (!job)
-                return;
-
-        job->done = true;
-
-        if (result != CURLE_OK) {
-                log_error("Transfer failed: %s", curl_easy_strerror(result));
-                r = -EIO;
-                goto fail;
-        }
-
-        code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
-        if (code != CURLE_OK) {
-                log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
-                r = -EIO;
-                goto fail;
-        } else if (status >= 300) {
-                log_error("HTTP request to %s failed with code %li.", job->url, status);
-                r = -EIO;
-                goto fail;
-        } else if (status < 200) {
-                log_error("HTTP request to %s finished with unexpected code %li.", job->url, status);
-                r = -EIO;
-                goto fail;
-        }
-
-        switch (job->type) {
-
-        case DCK_IMPORT_JOB_LAYER: {
-                siginfo_t si;
-
-                if (!job->tar_stream) {
-                        log_error("Downloaded layer too short.");
-                        r = -EIO;
-                        goto fail;
-                }
-
-                fclose(job->tar_stream);
-                job->tar_stream = NULL;
-
-                assert(job->tar_pid > 0);
-
-                r = wait_for_terminate(job->tar_pid, &si);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to wait for tar process: %m");
-                        goto fail;
-                }
-
-                job->tar_pid = 0;
-
-                if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) {
-                        log_error_errno(r, "tar failed abnormally.");
-                        r = -EIO;
-                        goto fail;
-                }
-
-                r = aufs_resolve(job->temp_path);
-                if (r < 0) {
-                        log_error_errno(r, "Couldn't resolve aufs whiteouts: %m");
-                        goto fail;
-                }
-
-                r = btrfs_subvol_read_only(job->temp_path, true);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to mark snapshot read-only: %m");
-                        goto fail;
-                }
-
-                if (rename(job->temp_path, job->final_path) < 0) {
-                        log_error_errno(r, "Failed to rename snapshot: %m");
-                        goto fail;
-                }
-
-                log_info("Completed writing to layer %s", job->final_path);
-                break;
-        }
-
-        default:
-                ;
-        }
-
-        SET_FOREACH(n, job->needed_by, i)
-                dck_import_name_job_finished(n, job);
-
-        return;
-
-fail:
-        dck_import_finish(job->import, r);
-}
-
-static size_t dck_import_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
-        DckImportJob *j = userdata;
-        size_t sz = size * nmemb;
-        char *p;
-        int r;
-
-        assert(contents);
-        assert(j);
-
-        if (j->tar_stream) {
-                size_t l;
-
-                l = fwrite(contents, size, nmemb, j->tar_stream);
-                if (l != nmemb) {
-                        r = -errno;
-                        goto fail;
-                }
-
-                return l;
-        }
-
-        if (j->payload_size + sz > PAYLOAD_MAX) {
-                r = -EFBIG;
-                goto fail;
-        }
-
-        p = realloc(j->payload, j->payload_size + sz);
-        if (!p) {
-                r = -ENOMEM;
-                goto fail;
-        }
-
-        memcpy(p + j->payload_size, contents, sz);
-        j->payload_size += sz;
-        j->payload = p;
-
-        r = dck_import_job_run_tar(j);
-        if (r < 0)
-                goto fail;
-
-        return sz;
-
-fail:
-        dck_import_finish(j->import, r);
-        return 0;
-}
-
-static size_t dck_import_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
-        _cleanup_free_ char *registry = NULL;
-        size_t sz = size * nmemb;
-        DckImportJob *j = userdata;
-        char *token;
-        int r;
-
-        assert(contents);
-        assert(j);
-
-        r = curl_header_strdup(contents, sz, HEADER_TOKEN, &token);
-        if (r < 0) {
-                log_oom();
-                goto fail;
-        }
-        if (r > 0) {
-                free(j->response_token);
-                j->response_token = token;
-        }
-
-        r = curl_header_strdup(contents, sz, HEADER_REGISTRY, &registry);
-        if (r < 0) {
-                log_oom();
-                goto fail;
-        }
-        if (r > 0) {
-                char **l, **i;
-
-                l = strv_split(registry, ",");
-                if (!l) {
-                        r = log_oom();
-                        goto fail;
-                }
-
-                STRV_FOREACH(i, l) {
-                        if (!hostname_is_valid(*i)) {
-                                log_error("Registry hostname is not valid.");
-                                strv_free(l);
-                                r = -EBADMSG;
-                                goto fail;
-                        }
-                }
-
-                strv_free(j->response_registries);
-                j->response_registries = l;
-        }
-
-        return sz;
-
-fail:
-        dck_import_finish(j->import, r);
-        return 0;
-}
-
-static int dck_import_name_add_job(DckImportName *name, DckImportJobType type, const char *url, DckImportJob **ret) {
-        _cleanup_(dck_import_job_unrefp) DckImportJob *j = NULL;
-        DckImportJob *f = NULL;
-        const char *t, *token;
-        int r;
-
-        assert(name);
-        assert(url);
-        assert(ret);
-
-        log_info("Getting %s.", url);
-        f = hashmap_get(name->import->jobs, url);
-        if (f) {
-                if (f->type != type)
-                        return -EINVAL;
-
-                r = set_put(f->needed_by, name);
-                if (r < 0)
-                        return r;
-
-                return 0;
-        }
-
-        r = hashmap_ensure_allocated(&name->import->jobs, &string_hash_ops);
-        if (r < 0)
-                return r;
-
-        j = new0(DckImportJob, 1);
-        if (!j)
-                return -ENOMEM;
-
-        j->import = name->import;
-        j->type = type;
-        j->url = strdup(url);
-        if (!j->url)
-                return -ENOMEM;
-
-        r = set_ensure_allocated(&j->needed_by, &trivial_hash_ops);
-        if (r < 0)
-                return r;
-
-        r = curl_glue_make(&j->curl, j->url, j);
-        if (r < 0)
-                return r;
-
-        token = dck_import_name_get_token(name);
-        if (token)
-                t = strappenda("Authorization: Token ", token);
-        else
-                t = HEADER_TOKEN " true";
-
-        j->request_header = curl_slist_new("Accept: application/json", t, NULL);
-        if (!j->request_header)
-                return -ENOMEM;
-
-        if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK)
-                return -EIO;
-
-        if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, dck_import_job_write_callback) != CURLE_OK)
-                return -EIO;
-
-        if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK)
-                return -EIO;
-
-        if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, dck_import_job_header_callback) != CURLE_OK)
-                return -EIO;
-
-        if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK)
-                return -EIO;
-
-        r = curl_glue_add(name->import->glue, j->curl);
-        if (r < 0)
-                return r;
-
-        r = hashmap_put(name->import->jobs, j->url, j);
-        if (r < 0)
-                return r;
-
-        r = set_put(j->needed_by, name);
-        if (r < 0) {
-                hashmap_remove(name->import->jobs, url);
-                return r;
-        }
-
-        *ret = j;
-        j = NULL;
-
-        return 1;
-}
-
-static int dck_import_name_begin(DckImportName *name) {
-        const char *url;
-
-        assert(name);
-        assert(!name->job_images);
-
-        url = strappenda(PROTOCOL_PREFIX, INDEX_HOST, "/v1/repositories/", name->name, "/images");
-
-        return dck_import_name_add_job(name, DCK_IMPORT_JOB_IMAGES, url, &name->job_images);
-}
-
-int dck_import_new(DckImport **import, sd_event *event, dck_import_on_finished on_finished, void *userdata) {
-        _cleanup_(dck_import_unrefp) DckImport *i = NULL;
-        int r;
-
-        assert(import);
-
-        i = new0(DckImport, 1);
-        if (!i)
-                return -ENOMEM;
-
-        i->on_finished = on_finished;
-        i->userdata = userdata;
-
-        if (event)
-                i->event = sd_event_ref(event);
-        else {
-                r = sd_event_default(&i->event);
-                if (r < 0)
-                        return r;
-        }
-
-        r = curl_glue_new(&i->glue, i->event);
-        if (r < 0)
-                return r;
-
-        i->glue->on_finished = dck_import_curl_on_finished;
-        i->glue->userdata = i;
-
-        *import = i;
-        i = NULL;
-
-        return 0;
-}
-
-DckImport* dck_import_unref(DckImport *import) {
-        DckImportName *n;
-        DckImportJob *j;
-
-        if (!import)
-                return NULL;
-
-        while ((n = hashmap_steal_first(import->names)))
-               dck_import_name_unref(n);
-        hashmap_free(import->names);
-
-        while ((j = hashmap_steal_first(import->jobs)))
-                dck_import_job_unref(j);
-        hashmap_free(import->jobs);
-
-        curl_glue_unref(import->glue);
-        sd_event_unref(import->event);
-
-        free(import);
-        return NULL;
-}
-
-int dck_import_cancel(DckImport *import, const char *name) {
-        DckImportName *n;
-
-        assert(import);
-        assert(name);
-
-        n = hashmap_remove(import->names, name);
-        if (!n)
-                return 0;
-
-        dck_import_name_unref(n);
-        return 1;
-}
-
-int dck_import_pull(DckImport *import, const char *name, const char *tag, const char *local, bool force_local) {
-        _cleanup_(dck_import_name_unrefp) DckImportName *n = NULL;
-        int r;
-
-        assert(import);
-        assert(dck_name_is_valid(name));
-        assert(dck_tag_is_valid(tag));
-        assert(!local || machine_name_is_valid(local));
-
-        if (hashmap_get(import->names, name))
-                return -EEXIST;
-
-        r = hashmap_ensure_allocated(&import->names, &string_hash_ops);
-        if (r < 0)
-                return r;
-
-        n = new0(DckImportName, 1);
-        if (!n)
-                return -ENOMEM;
-
-        n->import = import;
-
-        n->name = strdup(name);
-        if (!n->name)
-                return -ENOMEM;
-
-        n->tag = strdup(tag);
-        if (!n->tag)
-                return -ENOMEM;
-
-        if (local) {
-                n->local = strdup(local);
-                if (!n->local)
-                        return -ENOMEM;
-                n->force_local = force_local;
-        }
-
-        r = hashmap_put(import->names, name, n);
-        if (r < 0)
-                return r;
-
-        r = dck_import_name_begin(n);
-        if (r < 0) {
-                dck_import_cancel(import, n->name);
-                n = NULL;
-                return r;
-        }
-
-        n = NULL;
-
-        return 0;
-}
-
-bool dck_name_is_valid(const char *name) {
-        const char *slash, *p;
-
-        if (isempty(name))
-                return false;
-
-        slash = strchr(name, '/');
-        if (!slash)
-                return false;
-
-        if (!filename_is_valid(slash + 1))
-                return false;
-
-        p = strndupa(name, slash - name);
-        if (!filename_is_valid(p))
-                return false;
-
-        return true;
-}
-
-bool dck_id_is_valid(const char *id) {
-
-        if (!filename_is_valid(id))
-                return false;
-
-        if (!in_charset(id, "0123456789abcdef"))
-                return false;
-
-        return true;
-}
diff --git a/src/import/import-dck.h b/src/import/import-dck.h
deleted file mode 100644
index cd2d27c..0000000
--- a/src/import/import-dck.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include "sd-event.h"
-#include "util.h"
-
-typedef struct DckImport DckImport;
-
-typedef void (*dck_import_on_finished)(DckImport *import, int error, void *userdata);
-
-int dck_import_new(DckImport **import, sd_event *event, dck_import_on_finished on_finished, void *userdata);
-DckImport* dck_import_unref(DckImport *import);
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(DckImport*, dck_import_unref);
-
-int dck_import_pull(DckImport *import, const char *name, const char *tag, const char *local, bool force_local);
-int dck_import_cancel(DckImport *import, const char *name);
-
-bool dck_name_is_valid(const char *name);
-bool dck_id_is_valid(const char *id);
-#define dck_tag_is_valid(tag) filename_is_valid(tag)
diff --git a/src/import/import-dkr.c b/src/import/import-dkr.c
new file mode 100644
index 0000000..e2910f9
--- /dev/null
+++ b/src/import/import-dkr.c
@@ -0,0 +1,1177 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <curl/curl.h>
+#include <sys/prctl.h>
+
+#include "hashmap.h"
+#include "set.h"
+#include "json.h"
+#include "strv.h"
+#include "curl-util.h"
+#include "import-dkr.h"
+#include "btrfs-util.h"
+#include "aufs-util.h"
+#include "utf8.h"
+
+/* TODO:
+  - convert json bits
+  - man page
+  - fall back to btrfs loop pool device
+*/
+
+typedef struct DkrImportJob DkrImportJob;
+typedef struct DkrImportName DkrImportName;
+
+typedef enum DkrImportJobType {
+        DKR_IMPORT_JOB_IMAGES,
+        DKR_IMPORT_JOB_TAGS,
+        DKR_IMPORT_JOB_ANCESTRY,
+        DKR_IMPORT_JOB_JSON,
+        DKR_IMPORT_JOB_LAYER,
+} DkrImportJobType;
+
+struct DkrImportJob {
+        DkrImport *import;
+        DkrImportJobType type;
+        bool done;
+
+        char *url;
+
+        Set *needed_by; /* DkrImport Name objects */
+
+        CURL *curl;
+        struct curl_slist *request_header;
+        void *payload;
+        size_t payload_size;
+
+        char *response_token;
+        char **response_registries;
+
+        char *temp_path;
+        char *final_path;
+
+        pid_t tar_pid;
+        FILE *tar_stream;
+};
+
+struct DkrImportName {
+        DkrImport *import;
+
+        char *index_url;
+        char *name;
+        char *tag;
+        char *id;
+        char *local;
+
+        DkrImportJob *job_images, *job_tags, *job_ancestry, *job_json, *job_layer;
+
+        char **ancestry;
+        unsigned current_ancestry;
+
+        bool force_local;
+};
+
+struct DkrImport {
+        sd_event *event;
+        CurlGlue *glue;
+
+        Hashmap *names;
+        Hashmap *jobs;
+
+        dkr_import_on_finished on_finished;
+        void *userdata;
+};
+
+#define PROTOCOL_PREFIX "https://"
+
+#define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
+#define HEADER_REGISTRY "X-Do" /*the HTTP header for the registry */ "cker-Endpoints:"
+
+#define PAYLOAD_MAX (16*1024*1024)
+#define LAYERS_MAX 2048
+
+static int dkr_import_name_add_job(DkrImportName *name, DkrImportJobType type, const char *url, DkrImportJob **ret);
+
+static DkrImportJob *dkr_import_job_unref(DkrImportJob *job) {
+        if (!job)
+                return NULL;
+
+        if (job->import)
+                curl_glue_remove_and_free(job->import->glue, job->curl);
+        curl_slist_free_all(job->request_header);
+
+        if (job->tar_stream)
+                fclose(job->tar_stream);
+
+        free(job->final_path);
+
+        if (job->temp_path) {
+                btrfs_subvol_remove(job->temp_path);
+                free(job->temp_path);
+        }
+
+        set_free(job->needed_by);
+
+        if (job->tar_pid > 0)
+                kill(job->tar_pid, SIGTERM);
+
+        free(job->url);
+        free(job->payload);
+        free(job->response_token);
+        strv_free(job->response_registries);
+
+        free(job);
+
+        return NULL;
+}
+
+static DkrImportName *dkr_import_name_unref(DkrImportName *name) {
+        if (!name)
+                return NULL;
+
+        if (name->job_images)
+                set_remove(name->job_images->needed_by, name);
+
+        if (name->job_tags)
+                set_remove(name->job_tags->needed_by, name);
+
+        if (name->job_ancestry)
+                set_remove(name->job_ancestry->needed_by, name);
+
+        if (name->job_json)
+                set_remove(name->job_json->needed_by, name);
+
+        if (name->job_layer)
+                set_remove(name->job_layer->needed_by, name);
+
+        free(name->index_url);
+        free(name->name);
+        free(name->id);
+        free(name->tag);
+        free(name->local);
+
+        strv_free(name->ancestry);
+        free(name);
+
+        return NULL;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImportJob*, dkr_import_job_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImportName*, dkr_import_name_unref);
+
+static void dkr_import_finish(DkrImport *import, int error) {
+        assert(import);
+
+        if (import->on_finished)
+                import->on_finished(import, error, import->userdata);
+        else
+                sd_event_exit(import->event, error);
+}
+
+static int parse_id(const void *payload, size_t size, char **ret) {
+        _cleanup_free_ char *buf = NULL, *id = NULL, *other = NULL;
+        union json_value v = {};
+        void *json_state = NULL;
+        const char *p;
+        int t;
+
+        assert(payload);
+        assert(ret);
+
+        if (size <= 0)
+                return -EBADMSG;
+
+        if (memchr(payload, 0, size))
+                return -EBADMSG;
+
+        buf = strndup(payload, size);
+        if (!buf)
+                return -ENOMEM;
+
+        p = buf;
+        t = json_tokenize(&p, &id, &v, &json_state, NULL);
+        if (t < 0)
+                return t;
+        if (t != JSON_STRING)
+                return -EBADMSG;
+
+        t = json_tokenize(&p, &other, &v, &json_state, NULL);
+        if (t < 0)
+                return t;
+        if (t != JSON_END)
+                return -EBADMSG;
+
+        if (!dkr_id_is_valid(id))
+                return -EBADMSG;
+
+        *ret = id;
+        id = NULL;
+
+        return 0;
+}
+
+static int parse_ancestry(const void *payload, size_t size, char ***ret) {
+        _cleanup_free_ char *buf = NULL;
+        void *json_state = NULL;
+        const char *p;
+        enum {
+                STATE_BEGIN,
+                STATE_ITEM,
+                STATE_COMMA,
+                STATE_END,
+        } state = STATE_BEGIN;
+        _cleanup_strv_free_ char **l = NULL;
+        size_t n = 0, allocated = 0;
+
+        if (size <= 0)
+                return -EBADMSG;
+
+        if (memchr(payload, 0, size))
+                return -EBADMSG;
+
+        buf = strndup(payload, size);
+        if (!buf)
+                return -ENOMEM;
+
+        p = buf;
+        for (;;) {
+                _cleanup_free_ char *str;
+                union json_value v = {};
+                int t;
+
+                t = json_tokenize(&p, &str, &v, &json_state, NULL);
+                if (t < 0)
+                        return t;
+
+                switch (state) {
+
+                case STATE_BEGIN:
+                        if (t == JSON_ARRAY_OPEN)
+                                state = STATE_ITEM;
+                        else
+                                return -EBADMSG;
+
+                        break;
+
+                case STATE_ITEM:
+                        if (t == JSON_STRING) {
+                                if (!dkr_id_is_valid(str))
+                                        return -EBADMSG;
+
+                                if (n+1 > LAYERS_MAX)
+                                        return -EFBIG;
+
+                                if (!GREEDY_REALLOC(l, allocated, n + 2))
+                                        return -ENOMEM;
+
+                                l[n++] = str;
+                                str = NULL;
+                                l[n] = NULL;
+
+                                state = STATE_COMMA;
+
+                        } else if (t == JSON_ARRAY_CLOSE)
+                                state = STATE_END;
+                        else
+                                return -EBADMSG;
+
+                        break;
+
+                case STATE_COMMA:
+                        if (t == JSON_COMMA)
+                                state = STATE_ITEM;
+                        else if (t == JSON_ARRAY_CLOSE)
+                                state = STATE_END;
+                        else
+                                return -EBADMSG;
+                        break;
+
+                case STATE_END:
+                        if (t == JSON_END) {
+
+                                if (strv_isempty(l))
+                                        return -EBADMSG;
+
+                                if (!strv_is_uniq(l))
+                                        return -EBADMSG;
+
+                                l = strv_reverse(l);
+
+                                *ret = l;
+                                l = NULL;
+                                return 0;
+                        } else
+                                return -EBADMSG;
+                }
+
+        }
+}
+
+static const char *dkr_import_name_current_layer(DkrImportName *name) {
+        assert(name);
+
+        if (strv_isempty(name->ancestry))
+                return NULL;
+
+        return name->ancestry[name->current_ancestry];
+}
+
+static const char *dkr_import_name_current_base_layer(DkrImportName *name) {
+        assert(name);
+
+        if (strv_isempty(name->ancestry))
+                return NULL;
+
+        if (name->current_ancestry <= 0)
+                return NULL;
+
+        return name->ancestry[name->current_ancestry-1];
+}
+
+static char** dkr_import_name_get_registries(DkrImportName *name) {
+        assert(name);
+
+        if (!name->job_images)
+                return NULL;
+
+        if (!name->job_images->done)
+                return NULL;
+
+        if (strv_isempty(name->job_images->response_registries))
+                return NULL;
+
+        return name->job_images->response_registries;
+}
+
+static const char*dkr_import_name_get_token(DkrImportName *name) {
+        assert(name);
+
+        if (!name->job_images)
+                return NULL;
+
+        if (!name->job_images->done)
+                return NULL;
+
+        return name->job_images->response_token;
+}
+
+static void dkr_import_name_maybe_finish(DkrImportName *name) {
+        int r;
+
+        assert(name);
+
+        if (!name->job_images || !name->job_images->done)
+                return;
+
+        if (!name->job_ancestry || !name->job_ancestry->done)
+                return;
+
+        if (!name->job_json || !name->job_json->done)
+                return;
+
+        if (name->job_layer && !name->job_json->done)
+                return;
+
+        if (dkr_import_name_current_layer(name))
+                return;
+
+        if (name->local) {
+                const char *p, *q;
+
+                assert(name->id);
+
+                p = strappenda("/var/lib/container/", name->local);
+                q = strappenda("/var/lib/container/.dkr-", name->id);
+
+                if (name->force_local) {
+                        (void) btrfs_subvol_remove(p);
+                        (void) rm_rf(p, false, true, false);
+                }
+
+                r = btrfs_subvol_snapshot(q, p, false, false);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to snapshot final image: %m");
+                        dkr_import_finish(name->import, r);
+                        return;
+                }
+
+                log_info("Created new image %s.", p);
+        }
+
+        dkr_import_finish(name->import, 0);
+}
+
+static int dkr_import_job_run_tar(DkrImportJob *job) {
+        _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
+        bool gzip;
+
+        assert(job);
+
+        /* A stream to run tar on? */
+        if (!job->temp_path)
+                return 0;
+
+        if (job->tar_stream)
+                return 0;
+
+        /* Maybe fork off tar, if we have enough to figure out that
+         * something is gzip compressed or not */
+
+        if (job->payload_size < 2)
+                return 0;
+
+        /* Detect gzip signature */
+        gzip = ((uint8_t*) job->payload)[0] == 0x1f &&
+               ((uint8_t*) job->payload)[1] == 0x8b;
+
+        assert(!job->tar_stream);
+        assert(job->tar_pid <= 0);
+
+        if (pipe2(pipefd, O_CLOEXEC) < 0)
+                return log_error_errno(errno, "Failed to create pipe for tar: %m");
+
+        job->tar_pid = fork();
+        if (job->tar_pid < 0)
+                return log_error_errno(errno, "Failed to fork off tar: %m");
+        if (job->tar_pid == 0) {
+                int null_fd;
+
+                reset_all_signal_handlers();
+                reset_signal_mask();
+                assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+
+                pipefd[1] = safe_close(pipefd[1]);
+
+                if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
+                        log_error_errno(errno, "Failed to dup2() fd: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                if (pipefd[0] != STDIN_FILENO)
+                        safe_close(pipefd[0]);
+                if (pipefd[1] != STDIN_FILENO)
+                        safe_close(pipefd[1]);
+
+                null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
+                if (null_fd < 0) {
+                        log_error_errno(errno, "Failed to open /dev/null: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
+                        log_error_errno(errno, "Failed to dup2() fd: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                if (null_fd != STDOUT_FILENO)
+                        safe_close(null_fd);
+
+                execlp("tar", "tar", "-C", job->temp_path, gzip ? "-xz" : "-x", NULL);
+                _exit(EXIT_FAILURE);
+        }
+
+        pipefd[0] = safe_close(pipefd[0]);
+
+        job->tar_stream = fdopen(pipefd[1], "w");
+        if (!job->tar_stream)
+                return log_error_errno(errno, "Failed to allocate tar stream: %m");
+
+        pipefd[1] = -1;
+
+        if (fwrite(job->payload, 1, job->payload_size, job->tar_stream) != job->payload_size)
+                return log_error_errno(errno, "Couldn't write payload: %m");
+
+        free(job->payload);
+        job->payload = NULL;
+        job->payload_size = 0;
+
+        return 0;
+}
+
+static int dkr_import_name_pull_layer(DkrImportName *name) {
+        _cleanup_free_ char *path = NULL, *temp = NULL;
+        const char *url, *layer = NULL, *base = NULL;
+        char **rg;
+        int r;
+
+        assert(name);
+
+        if (name->job_layer) {
+                set_remove(name->job_layer->needed_by, name);
+                name->job_layer = NULL;
+        }
+
+        for (;;) {
+                layer = dkr_import_name_current_layer(name);
+                if (!layer) {
+                        dkr_import_name_maybe_finish(name);
+                        return 0;
+                }
+
+                path = strjoin("/var/lib/container/.dkr-", layer, NULL);
+                if (!path)
+                        return log_oom();
+
+                if (laccess(path, F_OK) < 0) {
+                        if (errno == ENOENT)
+                                break;
+
+                        return log_error_errno(errno, "Failed to check for container: %m");
+                }
+
+                log_info("Layer %s already exists, skipping.", layer);
+
+                name->current_ancestry++;
+
+                free(path);
+                path = NULL;
+        }
+
+        rg = dkr_import_name_get_registries(name);
+        assert(rg && rg[0]);
+
+        url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", layer, "/layer");
+        r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_LAYER, url, &name->job_layer);
+        if (r < 0) {
+                log_error_errno(r, "Failed to issue HTTP request: %m");
+                return r;
+        }
+        if (r == 0) /* Already downloading this one? */
+                return 0;
+
+        log_info("Pulling layer %s...", layer);
+
+        r = tempfn_random(path, &temp);
+        if (r < 0)
+                return log_oom();
+
+        base = dkr_import_name_current_base_layer(name);
+        if (base) {
+                const char *base_path;
+
+                base_path = strappend("/var/lib/container/.dkr-", base);
+                r = btrfs_subvol_snapshot(base_path, temp, false, true);
+        } else
+                r = btrfs_subvol_make(temp);
+
+        if (r < 0)
+                return log_error_errno(r, "Failed to make btrfs subvolume %s", temp);
+
+        name->job_layer->final_path = path;
+        name->job_layer->temp_path = temp;
+        path = temp = NULL;
+
+        return 0;
+}
+
+static void dkr_import_name_job_finished(DkrImportName *name, DkrImportJob *job) {
+        int r;
+
+        assert(name);
+        assert(job);
+
+        if (name->job_images == job) {
+                const char *url;
+                char **rg;
+
+                assert(!name->job_tags);
+                assert(!name->job_ancestry);
+                assert(!name->job_json);
+                assert(!name->job_layer);
+
+                rg = dkr_import_name_get_registries(name);
+                if (strv_isempty(rg)) {
+                        log_error("Didn't get registry information.");
+                        r = -EBADMSG;
+                        goto fail;
+                }
+
+                log_info("Index lookup succeeded, directed to registry %s.", rg[0]);
+
+                url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/repositories/", name->name, "/tags/", name->tag);
+
+                r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_TAGS, url, &name->job_tags);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to issue HTTP request: %m");
+                        goto fail;
+                }
+
+        } else if (name->job_tags == job) {
+                const char *url;
+                char *id = NULL, **rg;
+
+                assert(!name->job_ancestry);
+                assert(!name->job_json);
+                assert(!name->job_layer);
+
+                r = parse_id(job->payload, job->payload_size, &id);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to parse JSON id.");
+                        goto fail;
+                }
+
+                free(name->id);
+                name->id = id;
+
+                rg = dkr_import_name_get_registries(name);
+                assert(rg && rg[0]);
+
+                log_info("Tag lookup succeeded, resolved to layer %s.", name->id);
+
+                url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", name->id, "/ancestry");
+                r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_ANCESTRY, url, &name->job_ancestry);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to issue HTTP request: %m");
+                        goto fail;
+                }
+
+                url = strappenda(PROTOCOL_PREFIX, rg[0], "/v1/images/", name->id, "/json");
+                r = dkr_import_name_add_job(name, DKR_IMPORT_JOB_JSON, url, &name->job_json);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to issue HTTP request: %m");
+                        goto fail;
+                }
+
+        } else if (name->job_ancestry == job) {
+                char **ancestry = NULL, **i;
+                unsigned n;
+
+                r = parse_ancestry(job->payload, job->payload_size, &ancestry);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to parse JSON id.");
+                        goto fail;
+                }
+
+                n = strv_length(ancestry);
+                if (n <= 0 || !streq(ancestry[n-1], name->id)) {
+                        log_error("Ancestry doesn't end in main layer.");
+                        r = -EBADMSG;
+                        goto fail;
+                }
+
+                log_info("Ancestor lookup succeeded, requires layers:\n");
+                STRV_FOREACH(i, ancestry)
+                        log_info("\t%s", *i);
+
+                strv_free(name->ancestry);
+                name->ancestry = ancestry;
+
+                name->current_ancestry = 0;
+                r = dkr_import_name_pull_layer(name);
+                if (r < 0)
+                        goto fail;
+
+        } else if (name->job_json == job) {
+
+                dkr_import_name_maybe_finish(name);
+
+        } else if (name->job_layer == job) {
+
+                name->current_ancestry ++;
+                r = dkr_import_name_pull_layer(name);
+                if (r < 0)
+                        goto fail;
+
+        } else
+                assert_not_reached("Got finished event for unknown curl object");
+
+        return;
+
+fail:
+        dkr_import_finish(name->import, r);
+}
+
+static void dkr_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
+        DkrImportJob *job = NULL;
+        CURLcode code;
+        DkrImportName *n;
+        long status;
+        Iterator i;
+        int r;
+
+        if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &job) != CURLE_OK)
+                return;
+
+        if (!job)
+                return;
+
+        job->done = true;
+
+        if (result != CURLE_OK) {
+                log_error("Transfer failed: %s", curl_easy_strerror(result));
+                r = -EIO;
+                goto fail;
+        }
+
+        code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
+        if (code != CURLE_OK) {
+                log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
+                r = -EIO;
+                goto fail;
+        } else if (status >= 300) {
+                log_error("HTTP request to %s failed with code %li.", job->url, status);
+                r = -EIO;
+                goto fail;
+        } else if (status < 200) {
+                log_error("HTTP request to %s finished with unexpected code %li.", job->url, status);
+                r = -EIO;
+                goto fail;
+        }
+
+        switch (job->type) {
+
+        case DKR_IMPORT_JOB_LAYER: {
+                siginfo_t si;
+
+                if (!job->tar_stream) {
+                        log_error("Downloaded layer too short.");
+                        r = -EIO;
+                        goto fail;
+                }
+
+                fclose(job->tar_stream);
+                job->tar_stream = NULL;
+
+                assert(job->tar_pid > 0);
+
+                r = wait_for_terminate(job->tar_pid, &si);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to wait for tar process: %m");
+                        goto fail;
+                }
+
+                job->tar_pid = 0;
+
+                if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) {
+                        log_error_errno(r, "tar failed abnormally.");
+                        r = -EIO;
+                        goto fail;
+                }
+
+                r = aufs_resolve(job->temp_path);
+                if (r < 0) {
+                        log_error_errno(r, "Couldn't resolve aufs whiteouts: %m");
+                        goto fail;
+                }
+
+                r = btrfs_subvol_read_only(job->temp_path, true);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to mark snapshot read-only: %m");
+                        goto fail;
+                }
+
+                if (rename(job->temp_path, job->final_path) < 0) {
+                        log_error_errno(r, "Failed to rename snapshot: %m");
+                        goto fail;
+                }
+
+                log_info("Completed writing to layer %s", job->final_path);
+                break;
+        }
+
+        default:
+                ;
+        }
+
+        SET_FOREACH(n, job->needed_by, i)
+                dkr_import_name_job_finished(n, job);
+
+        return;
+
+fail:
+        dkr_import_finish(job->import, r);
+}
+
+static size_t dkr_import_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
+        DkrImportJob *j = userdata;
+        size_t sz = size * nmemb;
+        char *p;
+        int r;
+
+        assert(contents);
+        assert(j);
+
+        if (j->tar_stream) {
+                size_t l;
+
+                l = fwrite(contents, size, nmemb, j->tar_stream);
+                if (l != nmemb) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                return l;
+        }
+
+        if (j->payload_size + sz > PAYLOAD_MAX) {
+                r = -EFBIG;
+                goto fail;
+        }
+
+        p = realloc(j->payload, j->payload_size + sz);
+        if (!p) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        memcpy(p + j->payload_size, contents, sz);
+        j->payload_size += sz;
+        j->payload = p;
+
+        r = dkr_import_job_run_tar(j);
+        if (r < 0)
+                goto fail;
+
+        return sz;
+
+fail:
+        dkr_import_finish(j->import, r);
+        return 0;
+}
+
+static size_t dkr_import_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
+        _cleanup_free_ char *registry = NULL;
+        size_t sz = size * nmemb;
+        DkrImportJob *j = userdata;
+        char *token;
+        int r;
+
+        assert(contents);
+        assert(j);
+
+        r = curl_header_strdup(contents, sz, HEADER_TOKEN, &token);
+        if (r < 0) {
+                log_oom();
+                goto fail;
+        }
+        if (r > 0) {
+                free(j->response_token);
+                j->response_token = token;
+        }
+
+        r = curl_header_strdup(contents, sz, HEADER_REGISTRY, &registry);
+        if (r < 0) {
+                log_oom();
+                goto fail;
+        }
+        if (r > 0) {
+                char **l, **i;
+
+                l = strv_split(registry, ",");
+                if (!l) {
+                        r = log_oom();
+                        goto fail;
+                }
+
+                STRV_FOREACH(i, l) {
+                        if (!hostname_is_valid(*i)) {
+                                log_error("Registry hostname is not valid.");
+                                strv_free(l);
+                                r = -EBADMSG;
+                                goto fail;
+                        }
+                }
+
+                strv_free(j->response_registries);
+                j->response_registries = l;
+        }
+
+        return sz;
+
+fail:
+        dkr_import_finish(j->import, r);
+        return 0;
+}
+
+static int dkr_import_name_add_job(DkrImportName *name, DkrImportJobType type, const char *url, DkrImportJob **ret) {
+        _cleanup_(dkr_import_job_unrefp) DkrImportJob *j = NULL;
+        DkrImportJob *f = NULL;
+        const char *t, *token;
+        int r;
+
+        assert(name);
+        assert(url);
+        assert(ret);
+
+        log_info("Getting %s.", url);
+        f = hashmap_get(name->import->jobs, url);
+        if (f) {
+                if (f->type != type)
+                        return -EINVAL;
+
+                r = set_put(f->needed_by, name);
+                if (r < 0)
+                        return r;
+
+                return 0;
+        }
+
+        r = hashmap_ensure_allocated(&name->import->jobs, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        j = new0(DkrImportJob, 1);
+        if (!j)
+                return -ENOMEM;
+
+        j->import = name->import;
+        j->type = type;
+        j->url = strdup(url);
+        if (!j->url)
+                return -ENOMEM;
+
+        r = set_ensure_allocated(&j->needed_by, &trivial_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = curl_glue_make(&j->curl, j->url, j);
+        if (r < 0)
+                return r;
+
+        token = dkr_import_name_get_token(name);
+        if (token)
+                t = strappenda("Authorization: Token ", token);
+        else
+                t = HEADER_TOKEN " true";
+
+        j->request_header = curl_slist_new("Accept: application/json", t, NULL);
+        if (!j->request_header)
+                return -ENOMEM;
+
+        if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK)
+                return -EIO;
+
+        if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, dkr_import_job_write_callback) != CURLE_OK)
+                return -EIO;
+
+        if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK)
+                return -EIO;
+
+        if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, dkr_import_job_header_callback) != CURLE_OK)
+                return -EIO;
+
+        if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK)
+                return -EIO;
+
+        r = curl_glue_add(name->import->glue, j->curl);
+        if (r < 0)
+                return r;
+
+        r = hashmap_put(name->import->jobs, j->url, j);
+        if (r < 0)
+                return r;
+
+        r = set_put(j->needed_by, name);
+        if (r < 0) {
+                hashmap_remove(name->import->jobs, url);
+                return r;
+        }
+
+        *ret = j;
+        j = NULL;
+
+        return 1;
+}
+
+static int dkr_import_name_begin(DkrImportName *name) {
+        const char *url;
+
+        assert(name);
+        assert(!name->job_images);
+
+        url = strappenda(name->index_url, "/v1/repositories/", name->name, "/images");
+
+        return dkr_import_name_add_job(name, DKR_IMPORT_JOB_IMAGES, url, &name->job_images);
+}
+
+int dkr_import_new(DkrImport **import, sd_event *event, dkr_import_on_finished on_finished, void *userdata) {
+        _cleanup_(dkr_import_unrefp) DkrImport *i = NULL;
+        int r;
+
+        assert(import);
+
+        i = new0(DkrImport, 1);
+        if (!i)
+                return -ENOMEM;
+
+        i->on_finished = on_finished;
+        i->userdata = userdata;
+
+        if (event)
+                i->event = sd_event_ref(event);
+        else {
+                r = sd_event_default(&i->event);
+                if (r < 0)
+                        return r;
+        }
+
+        r = curl_glue_new(&i->glue, i->event);
+        if (r < 0)
+                return r;
+
+        i->glue->on_finished = dkr_import_curl_on_finished;
+        i->glue->userdata = i;
+
+        *import = i;
+        i = NULL;
+
+        return 0;
+}
+
+DkrImport* dkr_import_unref(DkrImport *import) {
+        DkrImportName *n;
+        DkrImportJob *j;
+
+        if (!import)
+                return NULL;
+
+        while ((n = hashmap_steal_first(import->names)))
+               dkr_import_name_unref(n);
+        hashmap_free(import->names);
+
+        while ((j = hashmap_steal_first(import->jobs)))
+                dkr_import_job_unref(j);
+        hashmap_free(import->jobs);
+
+        curl_glue_unref(import->glue);
+        sd_event_unref(import->event);
+
+        free(import);
+
+        return NULL;
+}
+
+int dkr_import_cancel(DkrImport *import, const char *name) {
+        DkrImportName *n;
+
+        assert(import);
+        assert(name);
+
+        n = hashmap_remove(import->names, name);
+        if (!n)
+                return 0;
+
+        dkr_import_name_unref(n);
+        return 1;
+}
+
+int dkr_import_pull(DkrImport *import, const char *index_url, const char *name, const char *tag, const char *local, bool force_local) {
+        _cleanup_(dkr_import_name_unrefp) DkrImportName *n = NULL;
+        char *e;
+        int r;
+
+        assert(import);
+        assert(dkr_url_is_valid(index_url));
+        assert(dkr_name_is_valid(name));
+        assert(dkr_tag_is_valid(tag));
+        assert(!local || machine_name_is_valid(local));
+
+        if (hashmap_get(import->names, name))
+                return -EEXIST;
+
+        r = hashmap_ensure_allocated(&import->names, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        n = new0(DkrImportName, 1);
+        if (!n)
+                return -ENOMEM;
+
+        n->import = import;
+
+        n->index_url = strdup(index_url);
+        if (!n->index_url)
+                return -ENOMEM;
+        e = endswith(n->index_url, "/");
+        if (e)
+                *e = NULL;
+
+        n->name = strdup(name);
+        if (!n->name)
+                return -ENOMEM;
+
+        n->tag = strdup(tag);
+        if (!n->tag)
+                return -ENOMEM;
+
+        if (local) {
+                n->local = strdup(local);
+                if (!n->local)
+                        return -ENOMEM;
+                n->force_local = force_local;
+        }
+
+        r = hashmap_put(import->names, name, n);
+        if (r < 0)
+                return r;
+
+        r = dkr_import_name_begin(n);
+        if (r < 0) {
+                dkr_import_cancel(import, n->name);
+                n = NULL;
+                return r;
+        }
+
+        n = NULL;
+
+        return 0;
+}
+
+bool dkr_name_is_valid(const char *name) {
+        const char *slash, *p;
+
+        if (isempty(name))
+                return false;
+
+        slash = strchr(name, '/');
+        if (!slash)
+                return false;
+
+        if (!filename_is_valid(slash + 1))
+                return false;
+
+        p = strndupa(name, slash - name);
+        if (!filename_is_valid(p))
+                return false;
+
+        return true;
+}
+
+bool dkr_id_is_valid(const char *id) {
+
+        if (!filename_is_valid(id))
+                return false;
+
+        if (!in_charset(id, "0123456789abcdef"))
+                return false;
+
+        return true;
+}
+
+bool dkr_url_is_valid(const char *url) {
+
+        if (!startswith(url, "http://") &&
+            !startswith(url, "https://"))
+                return false;
+
+        return ascii_is_valid(url);
+}
diff --git a/src/import/import-dkr.h b/src/import/import-dkr.h
new file mode 100644
index 0000000..a951695
--- /dev/null
+++ b/src/import/import-dkr.h
@@ -0,0 +1,40 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "sd-event.h"
+#include "util.h"
+
+typedef struct DkrImport DkrImport;
+
+typedef void (*dkr_import_on_finished)(DkrImport *import, int error, void *userdata);
+
+int dkr_import_new(DkrImport **import, sd_event *event, dkr_import_on_finished on_finished, void *userdata);
+DkrImport* dkr_import_unref(DkrImport *import);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImport*, dkr_import_unref);
+
+int dkr_import_pull(DkrImport *import, const char *index_url, const char *name, const char *tag, const char *local, bool force_local);
+int dkr_import_cancel(DkrImport *import, const char *name);
+
+bool dkr_name_is_valid(const char *name);
+bool dkr_id_is_valid(const char *id);
+#define dkr_tag_is_valid(tag) filename_is_valid(tag)
+bool dkr_url_is_valid(const char *url);
diff --git a/src/import/import.c b/src/import/import.c
index 6fd3eeb..11d2e05 100644
--- a/src/import/import.c
+++ b/src/import/import.c
@@ -25,11 +25,13 @@
 #include "event-util.h"
 #include "verbs.h"
 #include "build.h"
-#include "import-dck.h"
+#include "import-dkr.h"
 
 static bool arg_force = false;
 
-static void on_finished(DckImport *import, int error, void *userdata) {
+static const char* arg_dkr_index_url = DEFAULT_DKR_INDEX_URL;
+
+static void on_finished(DkrImport *import, int error, void *userdata) {
         sd_event *event = userdata;
         assert(import);
 
@@ -41,12 +43,17 @@ static void on_finished(DckImport *import, int error, void *userdata) {
         sd_event_exit(event, error);
 }
 
-static int pull_dck(int argc, char *argv[], void *userdata) {
-        _cleanup_(dck_import_unrefp) DckImport *import = NULL;
+static int pull_dkr(int argc, char *argv[], void *userdata) {
+        _cleanup_(dkr_import_unrefp) DkrImport *import = NULL;
         _cleanup_event_unref_ sd_event *event = NULL;
         const char *name, *tag, *local;
         int r;
 
+        if (!arg_dkr_index_url) {
+                log_error("Please specify an index URL with --dkr-index-url=");
+                return -EINVAL;
+        }
+
         tag = strchr(argv[1], ':');
         if (tag) {
                 name = strndupa(argv[1], tag - argv[1]);
@@ -69,12 +76,12 @@ static int pull_dck(int argc, char *argv[], void *userdata) {
         if (streq(local, "-") || isempty(local))
                 local = NULL;
 
-        if (!dck_name_is_valid(name)) {
+        if (!dkr_name_is_valid(name)) {
                 log_error("Remote name '%s' is not valid.", name);
                 return -EINVAL;
         }
 
-        if (!dck_tag_is_valid(tag)) {
+        if (!dkr_tag_is_valid(tag)) {
                 log_error("Tag name '%s' is not valid.", tag);
                 return -EINVAL;
         }
@@ -108,11 +115,11 @@ static int pull_dck(int argc, char *argv[], void *userdata) {
         sd_event_add_signal(event, NULL, SIGTERM, NULL,  NULL);
         sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
 
-        r = dck_import_new(&import, event, on_finished, event);
+        r = dkr_import_new(&import, event, on_finished, event);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate importer: %m");
 
-        r = dck_import_pull(import, name, tag, local, arg_force);
+        r = dkr_import_pull(import, arg_dkr_index_url, name, tag, local, arg_force);
         if (r < 0)
                 return log_error_errno(r, "Failed to pull image: %m");
 
@@ -131,9 +138,10 @@ static int help(int argc, char *argv[], void *userdata) {
                "Import container or virtual machine image.\n\n"
                "  -h --help                   Show this help\n"
                "     --version                Show package version\n"
-               "     --force                  Force creation of image\n\n"
+               "     --force                  Force creation of image\n"
+               "     --dkr-index-url=URL      Specify index URL to use for downloads\n\n"
                "Commands:\n"
-               "  pull-dck REMOTE [NAME]      Download an image\n",
+               "  pull-dkr REMOTE [NAME]      Download an image\n",
                program_invocation_short_name);
 
         return 0;
@@ -144,12 +152,14 @@ static int parse_argv(int argc, char *argv[]) {
         enum {
                 ARG_VERSION = 0x100,
                 ARG_FORCE,
+                ARG_DKR_INDEX_URL,
         };
 
         static const struct option options[] = {
                 { "help",            no_argument,       NULL, 'h'                 },
                 { "version",         no_argument,       NULL, ARG_VERSION         },
                 { "force",           no_argument,       NULL, ARG_FORCE           },
+                { "dkr-index-url",   required_argument, NULL, ARG_DKR_INDEX_URL   },
                 {}
         };
 
@@ -174,6 +184,15 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_force = true;
                         break;
 
+                case ARG_DKR_INDEX_URL:
+                        if (!dkr_url_is_valid(optarg)) {
+                                log_error("Index URL is not valid: %s", optarg);
+                                return -EINVAL;
+                        }
+
+                        arg_dkr_index_url = optarg;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -188,7 +207,7 @@ static int import_main(int argc, char *argv[]) {
 
         static const Verb verbs[] = {
                 { "help",     VERB_ANY, VERB_ANY, 0, help     },
-                { "pull-dck", 2,        3,        0, pull_dck },
+                { "pull-dkr", 2,        3,        0, pull_dkr },
                 {}
         };
 



More information about the systemd-commits mailing list