[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, ®istry);
- 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, ®istry);
+ 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