diff --git a/projects/miragesdk/examples/mirage-dhcp.yml b/projects/miragesdk/examples/mirage-dhcp.yml index 796509ada..d3d414f0f 100644 --- a/projects/miragesdk/examples/mirage-dhcp.yml +++ b/projects/miragesdk/examples/mirage-dhcp.yml @@ -1,19 +1,15 @@ kernel: - image: "mobylinux/kernel:4.9.x" + image: "linuxkit/kernel:4.9.x" cmdline: "console=ttyS0 page_poison=1" init: - - mobylinux/init:61a72fa20b9b9be269fe6b2b6360031f2cb897a7 # base init + strace + git - - mobylinux/runc:b0fb122e10dbb7e4e45115177a61a3f8d68c19a9 - - mobylinux/containerd:18eaf72f3f4f9a9f29ca1951f66df701f873060b - - mobylinux/ca-certificates:eabc5a6e59f05aa91529d80e9a595b85b046f935 + - linuxkit/init:2599bcd5013ce5962aa155ee8929c26160de13bd + - linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f + - linuxkit/containerd:b50181bc6e0084e5fcd6b6ad3cf433c4f66cae5a onboot: - name: sysctl - image: "linuxkit/sysctl:3aa6bc663c2849ef239be7d941d3eaf3e6fcc018" -services: - - name: rngd - image: mobylinux/rngd:3dad6dd43270fa632ac031e99d1947f20b22eec9 + image: linuxkit/sysctl:3aa6bc663c2849ef239be7d941d3eaf3e6fcc018 - name: dhcp-client - image: linuxkitprojects/dhcp-client:6c231135a88d42e7d18d2ba952f0798910550cbd + image: miragesdk/dhcp-client:22aa9d527820534295a8cd59901c0c5197af6585 net: host capabilities: - CAP_NET_ADMIN # to bring eth0 up @@ -30,6 +26,15 @@ services: - /sbin:/sbin # for ifconfig - /bin:/bin # for ifconfig - /lib:/lib # for ifconfig +services: + - name: sshd + image: "linuxkit/sshd:abc1f5e096982ebc3fb61c506aed3ac9c2ae4d55" + - name: getty + image: "linuxkit/getty:ef9d667af71089326419fb08e9cc9d567cf15748" + env: + - INSECURE=true files: - - path: /var/run/dhcp-client/README + - path: var/run/dhcp-client/README contents: 'data for dhcp-client' + - path: root/.ssh/authorized_keys + contents: '#your SSH key here' diff --git a/projects/miragesdk/pkg/init/.gitignore b/projects/miragesdk/pkg/init/.gitignore deleted file mode 100644 index cf40cde3b..000000000 --- a/projects/miragesdk/pkg/init/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -sbin/ -usr/ diff --git a/projects/miragesdk/pkg/init/Dockerfile b/projects/miragesdk/pkg/init/Dockerfile deleted file mode 100644 index 5d0cc2a72..000000000 --- a/projects/miragesdk/pkg/init/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM alpine:3.5 - -RUN \ - apk --no-cache update && \ - apk --no-cache upgrade -a && \ - apk --no-cache add \ - strace \ - git \ - && rm -rf /var/cache/apk/* - -COPY . ./ diff --git a/projects/miragesdk/pkg/init/Makefile b/projects/miragesdk/pkg/init/Makefile deleted file mode 100644 index 5f2c4e793..000000000 --- a/projects/miragesdk/pkg/init/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -C_COMPILE=linuxkit/c-compile:63b085bbaec1aa7c42a7bd22a4b1c350d900617d@sha256:286e3a729c7a0b1a605ae150235416190f9f430c29b00e65fa50ff73158998e5 -START_STOP_DAEMON=sbin/start-stop-daemon - -default: push - -$(START_STOP_DAEMON): start-stop-daemon.c - mkdir -p $(dir $@) - tar cf - $^ | docker run --rm --net=none --log-driver=none -i $(C_COMPILE) -o $@ | tar xf - - -.PHONY: tag push - -BASE=alpine:3.5 -IMAGE=init - -ETC=$(shell find etc -type f) - -hash: Dockerfile $(ETC) init $(START_STOP_DAEMON) - DOCKER_CONTENT_TRUST=1 docker pull $(BASE) - tar cf - $^ | docker build --no-cache -t $(IMAGE):build - - docker run --rm $(IMAGE):build sh -c 'cat $^ /lib/apk/db/installed | sha1sum' | sed 's/ .*//' > $@ - -push: hash - docker pull mobylinux/$(IMAGE):$(shell cat hash) || \ - (docker tag $(IMAGE):build mobylinux/$(IMAGE):$(shell cat hash) && \ - docker push mobylinux/$(IMAGE):$(shell cat hash)) - docker rmi $(IMAGE):build - rm -f hash - -tag: hash - docker pull mobylinux/$(IMAGE):$(shell cat hash) || \ - docker tag $(IMAGE):build mobylinux/$(IMAGE):$(shell cat hash) - docker rmi $(IMAGE):build - rm -f hash - -clean: - rm -rf hash sbin usr - -.DELETE_ON_ERROR: diff --git a/projects/miragesdk/pkg/init/etc/init.d/containerd b/projects/miragesdk/pkg/init/etc/init.d/containerd deleted file mode 100755 index f62710d7e..000000000 --- a/projects/miragesdk/pkg/init/etc/init.d/containerd +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -# bring up containerd -ulimit -n 1048576 -ulimit -p unlimited - -printf "\nStarting containerd\n" -mkdir -p /var/log -exec /usr/bin/containerd diff --git a/projects/miragesdk/pkg/init/etc/init.d/containers b/projects/miragesdk/pkg/init/etc/init.d/containers deleted file mode 100755 index 41535a3ba..000000000 --- a/projects/miragesdk/pkg/init/etc/init.d/containers +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh - -# start onboot containers, run to completion - -if [ -d /containers/onboot ] -then - for f in $(find /containers/onboot -mindepth 1 -maxdepth 1 | sort) - do - base="$(basename $f)" - /bin/mount --bind "$f/rootfs" "$f/rootfs" - mount -o remount,rw "$f/rootfs" - /usr/bin/runc run --bundle "$f" "$(basename $f)" - printf " - $base\n" - done -fi - -# start service containers -# temporarily using runc not containerd - -if [ -d /containers/services ] -then - for f in $(find /containers/services -mindepth 1 -maxdepth 1 | sort) - do - base="$(basename $f)" - /bin/mount --bind "$f/rootfs" "$f/rootfs" - mount -o remount,rw "$f/rootfs" - log="/var/log/$base.log" - /sbin/start-stop-daemon --start --pidfile /run/$base.pid --exec /usr/bin/runc -- run --bundle "$f" --pid-file /run/$base.pid "$(basename $f)" $log >$log & - printf " - $base\n" - done -fi - -wait diff --git a/projects/miragesdk/pkg/init/etc/init.d/rcS b/projects/miragesdk/pkg/init/etc/init.d/rcS deleted file mode 100755 index 5f6773f7c..000000000 --- a/projects/miragesdk/pkg/init/etc/init.d/rcS +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/sh - -# mount filesystems -mkdir -p -m 0755 /proc /run /tmp /sys /dev - -mount -n -t proc proc /proc -o nodev,nosuid,noexec,relatime - -mount -n -t tmpfs tmpfs /run -o nodev,nosuid,noexec,relatime,size=10%,mode=755 -mount -n -t tmpfs tmpfs /tmp -o nodev,nosuid,noexec,relatime,size=10%,mode=1777 - -# mount devfs -mount -n -t devtmpfs dev /dev -o nosuid,noexec,relatime,size=10m,nr_inodes=248418,mode=755 -# devices -[ -c /dev/console ] || mknod -m 600 /dev/console c 5 1 -[ -c /dev/tty1 ] || mknod -m 620 /dev/tty1 c 4 1 -[ -c /dev/tty ] || mknod -m 666 /dev/tty c 5 0 - -[ -c /dev/null ] || mknod -m 666 /dev/null c 1 3 -[ -c /dev/kmsg ] || mknod -m 660 /dev/kmsg c 1 11 - -# extra symbolic links not provided by default -[ -e /dev/fd ] || ln -snf /proc/self/fd /dev/fd -[ -e /dev/stdin ] || ln -snf /proc/self/fd/0 /dev/stdin -[ -e /dev/stdout ] || ln -snf /proc/self/fd/1 /dev/stdout -[ -e /dev/stderr ] || ln -snf /proc/self/fd/2 /dev/stderr -[ -e /proc/kcore ] && ln -snf /proc/kcore /dev/core - -# devfs filesystems -mkdir -p -m 1777 /dev/mqueue -mkdir -p -m 1777 /dev/shm -mkdir -p -m 0755 /dev/pts -mount -n -t mqueue -o noexec,nosuid,nodev mqueue /dev/mqueue -mount -n -t tmpfs -o noexec,nosuid,nodev,mode=1777 shm /dev/shm -mount -n -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts - -# mount sysfs -sysfs_opts=nodev,noexec,nosuid -mount -n -t sysfs -o ${sysfs_opts} sysfs /sys -[ -d /sys/kernel/security ] && mount -n -t securityfs -o ${sysfs_opts} securityfs /sys/kernel/security -[ -d /sys/kernel/debug ] && mount -n -t debugfs -o ${sysfs_opts} debugfs /sys/kernel/debug -[ -d /sys/kernel/config ] && mount -n -t configfs -o ${sysfs_opts} configfs /sys/kernel/config -[ -d /sys/fs/fuse/connections ] && mount -n -t fusectl -o ${sysfs_opts} fusectl /sys/fs/fuse/connections -[ -d /sys/fs/selinux ] && mount -n -t selinuxfs -o nosuid,noexec selinuxfs /sys/fs/selinux -[ -d /sys/fs/pstore ] && mount -n -t pstore pstore -o ${sysfs_opts} /sys/fs/pstore -[ -d /sys/firmware/efi/efivars ] && mount -n -t efivarfs -o ro,${sysfs_opts} efivarfs /sys/firmware/efi/efivars - -# misc /proc mounted fs -[ -d /proc/sys/fs/binfmt_misc ] && mount -t binfmt_misc -o nodev,noexec,nosuid binfmt_misc /proc/sys/fs/binfmt_misc - -# mount cgroups -mount -n -t tmpfs -o nodev,noexec,nosuid,mode=755,size=10m cgroup_root /sys/fs/cgroup - -while read name hier groups enabled rest -do - case "${enabled}" in - 1) mkdir -p /sys/fs/cgroup/${name} - mount -n -t cgroup -o ${sysfs_opts},${name} ${name} /sys/fs/cgroup/${name} - ;; - esac -done < /proc/cgroups - -# use hierarchy for memory -echo 1 > /sys/fs/cgroup/memory/memory.use_hierarchy - -# for compatibility -mkdir -p /sys/fs/cgroup/systemd -mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd - -# start mdev for hotplug -echo "/sbin/mdev" > /proc/sys/kernel/hotplug - -# mdev -s will not create /dev/usb[1-9] devices with recent kernels -# so we trigger hotplug events for usb for now -for i in $(find /sys/devices -name 'usb[0-9]*'); do - [ -e $i/uevent ] && echo add > $i/uevent -done - -mdev -s - -# set hostname -if [ -s /etc/hostname ] -then - hostname -F /etc/hostname -fi - -if [ $(hostname) = "moby" -a -f /sys/class/net/eth0/address ] -then - mac=$(cat /sys/class/net/eth0/address) - hostname moby-$(echo $mac | sed 's/://g') -fi - -# set system clock from hwclock -hwclock --hctosys --utc - -# bring up loopback interface -ip addr add 127.0.0.1/8 dev lo brd + scope host -ip route add 127.0.0.0/8 dev lo scope host -ip link set lo up - -# for containerising dhcpcd and other containers that need writable etc -mkdir /tmp/etc -mv /etc/resolv.conf /tmp/etc/resolv.conf -ln -snf /tmp/etc/resolv.conf /etc/resolv.conf - -# remount rootfs as readonly -mount -o remount,ro / - -# make /var writeable and shared -mount -o bind /var /var -mount -o remount,rw,nodev,nosuid,noexec,relatime /var /var -mount --make-rshared /var - -# make / rshared -mount --make-rshared / diff --git a/projects/miragesdk/pkg/init/etc/inittab b/projects/miragesdk/pkg/init/etc/inittab deleted file mode 100644 index 8ef3e8565..000000000 --- a/projects/miragesdk/pkg/init/etc/inittab +++ /dev/null @@ -1,15 +0,0 @@ -# /etc/inittab - -::sysinit:/etc/init.d/rcS -::once:/etc/init.d/containerd -::once:/etc/init.d/containers - -# Stuff to do for the 3-finger salute -::ctrlaltdel:/sbin/reboot - -# Stuff to do before rebooting -::shutdown:/usr/sbin/killall5 -15 -::shutdown:/bin/sleep 5 -::shutdown:/usr/sbin/killall5 -9 -::shutdown:/bin/echo "Unmounting filesystems" -::shutdown:/bin/umount -a -r diff --git a/projects/miragesdk/pkg/init/etc/issue b/projects/miragesdk/pkg/init/etc/issue deleted file mode 100644 index f5a95ea49..000000000 --- a/projects/miragesdk/pkg/init/etc/issue +++ /dev/null @@ -1,12 +0,0 @@ - -Welcome to Moby - - ## . - ## ## ## == - ## ## ## ## ## === - /"""""""""""""""""\___/ === - ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ / ===- ~~~ - \______ o __/ - \ \ __/ - \____\_______/ - diff --git a/projects/miragesdk/pkg/init/init b/projects/miragesdk/pkg/init/init deleted file mode 100755 index f27b647b0..000000000 --- a/projects/miragesdk/pkg/init/init +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh - -setup_console() { - tty=${1%,*} - speed=${1#*,} - inittab="$2" - securetty="$3" - line= - term="linux" - [ "$speed" = "$1" ] && speed=115200 - - case "$tty" in - ttyS*|ttyAMA*|ttyUSB*|ttyMFD*) - line="-L" - term="vt100" - ;; - tty?) - line="" - speed="38400" - term="" - ;; - esac - # skip consoles already in inittab - grep -q "^$tty:" "$inittab" && return - - echo "$tty::once:cat /etc/issue" >> "$inittab" - echo "$tty::respawn:/sbin/getty -n -l /bin/sh $line $speed $tty $term" >> "$inittab" - if ! grep -q -w "$tty" "$securetty"; then - echo "$tty" >> "$securetty" - fi -} - -/bin/mount -t tmpfs tmpfs /mnt - -/bin/cp -a / /mnt 2>/dev/null - -/bin/mount -t proc -o noexec,nosuid,nodev proc /proc -for opt in $(cat /proc/cmdline); do - case "$opt" in - console=*) - setup_console ${opt#console=} /mnt/etc/inittab /mnt/etc/securetty;; - esac -done - -exec /bin/busybox switch_root /mnt /sbin/init diff --git a/projects/miragesdk/pkg/init/start-stop-daemon.c b/projects/miragesdk/pkg/init/start-stop-daemon.c deleted file mode 100644 index f27406746..000000000 --- a/projects/miragesdk/pkg/init/start-stop-daemon.c +++ /dev/null @@ -1,1054 +0,0 @@ -/* - * A rewrite of the original Debian's start-stop-daemon Perl script - * in C (faster - it is executed many times during system startup). - * - * Written by Marek Michalkiewicz , - * public domain. Based conceptually on start-stop-daemon.pl, by Ian - * Jackson . May be used and distributed - * freely for any purpose. Changes by Christian Schwarz - * , to make output conform to the Debian - * Console Message Standard, also placed in public domain. Minor - * changes by Klee Dienes , also placed in the Public - * Domain. - * - * Changes by Ben Collins , added --chuid, --background - * and --make-pidfile options, placed in public domain aswell. - * - * Port to OpenBSD by Sontri Tomo Huynh - * and Andreas Schuldei - * - * Changes by Ian Jackson: added --retry (and associated rearrangements). - * - * Modified for Gentoo rc-scripts by Donny Davies : - * I removed the BSD/Hurd/OtherOS stuff, added #include - * and stuck in a #define VERSION "1.9.18". Now it compiles without - * the whole automake/config.h dance. - * - * Modified to compile on Alpine by Justin Cormack - */ - -#include -#define VERSION "1.9.18" - -#define MIN_POLL_INTERVAL 20000 /*us*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int testmode = 0; -static int quietmode = 0; -static int exitnodo = 1; -static int start = 0; -static int stop = 0; -static int background = 0; -static int mpidfile = 0; -static int signal_nr = 15; -static const char *signal_str = NULL; -static int user_id = -1; -static int runas_uid = -1; -static int runas_gid = -1; -static const char *userspec = NULL; -static char *changeuser = NULL; -static const char *changegroup = NULL; -static char *changeroot = NULL; -static const char *cmdname = NULL; -static char *execname = NULL; -static char *startas = NULL; -static const char *pidfile = NULL; -static char what_stop[1024]; -static const char *schedule_str = NULL; -static const char *progname = ""; -static int nicelevel = 0; - -static struct stat exec_stat; - -struct pid_list { - struct pid_list *next; - pid_t pid; -}; - -static struct pid_list *found = NULL; -static struct pid_list *killed = NULL; - -struct schedule_item { - enum { sched_timeout, sched_signal, sched_goto, sched_forever } type; - int value; /* seconds, signal no., or index into array */ - /* sched_forever is only seen within parse_schedule and callees */ -}; - -static int schedule_length; -static struct schedule_item *schedule = NULL; - -LIST_HEAD(namespace_head, namespace); - -struct namespace { - LIST_ENTRY(namespace) list; - char *path; - int nstype; -}; - -static struct namespace_head namespace_head; - -static void *xmalloc(int size); -static void push(struct pid_list **list, pid_t pid); -static void do_help(void); -static void parse_options(int argc, char * const *argv); -static int pid_is_user(pid_t pid, uid_t uid); -static int pid_is_cmd(pid_t pid, const char *name); -static void check(pid_t pid); -static void do_pidfile(const char *name); -static void do_stop(int signal_nr, int quietmode, - int *n_killed, int *n_notkilled, int retry_nr); -static int pid_is_exec(pid_t pid, const struct stat *esb); - -#ifdef __GNUC__ -static void fatal(const char *format, ...) - __attribute__((noreturn, format(printf, 1, 2))); -static void badusage(const char *msg) - __attribute__((noreturn)); -#else -static void fatal(const char *format, ...); -static void badusage(const char *msg); -#endif - -/* This next part serves only to construct the TVCALC macro, which - * is used for doing arithmetic on struct timeval's. It works like this: - * TVCALC(result, expression); - * where result is a struct timeval (and must be an lvalue) and - * expression is the single expression for both components. In this - * expression you can use the special values TVELEM, which when fed a - * const struct timeval* gives you the relevant component, and - * TVADJUST. TVADJUST is necessary when subtracting timevals, to make - * it easier to renormalise. Whenver you subtract timeval elements, - * you must make sure that TVADJUST is added to the result of the - * subtraction (before any resulting multiplication or what have you). - * TVELEM must be linear in TVADJUST. - */ -typedef long tvselector(const struct timeval*); -static long tvselector_sec(const struct timeval *tv) { return tv->tv_sec; } -static long tvselector_usec(const struct timeval *tv) { return tv->tv_usec; } -#define TVCALC_ELEM(result, expr, sec, adj) \ -{ \ - const long TVADJUST = adj; \ - long (*const TVELEM)(const struct timeval*) = tvselector_##sec; \ - (result).tv_##sec = (expr); \ -} -#define TVCALC(result,expr) \ -do { \ - TVCALC_ELEM(result, expr, sec, (-1)); \ - TVCALC_ELEM(result, expr, usec, (+1000000)); \ - (result).tv_sec += (result).tv_usec / 1000000; \ - (result).tv_usec %= 1000000; \ -} while(0) - - -static void -fatal(const char *format, ...) -{ - va_list arglist; - - fprintf(stderr, "%s: ", progname); - va_start(arglist, format); - vfprintf(stderr, format, arglist); - va_end(arglist); - putc('\n', stderr); - exit(2); -} - - -static void * -xmalloc(int size) -{ - void *ptr; - - ptr = malloc(size); - if (ptr) - return ptr; - fatal("malloc(%d) failed", size); -} - -static void -xgettimeofday(struct timeval *tv) -{ - if (gettimeofday(tv,0) != 0) - fatal("gettimeofday failed: %s", strerror(errno)); -} - -static void -push(struct pid_list **list, pid_t pid) -{ - struct pid_list *p; - - p = xmalloc(sizeof(*p)); - p->next = *list; - p->pid = pid; - *list = p; -} - -static void -clear(struct pid_list **list) -{ - struct pid_list *here, *next; - - for (here = *list; here != NULL; here = next) { - next = here->next; - free(here); - } - - *list = NULL; -} - -static char * -next_dirname(const char *s) -{ - char *cur; - - cur = (char *)s; - - if (*cur != '\0') { - for (; *cur != '/'; ++cur) - if (*cur == '\0') - return cur; - - for (; *cur == '/'; ++cur) - ; - } - - return cur; -} - -static void -add_namespace(const char *path) -{ - int nstype; - char *nsdirname, *nsname, *cur; - struct namespace *namespace; - - cur = (char *)path; - nsdirname = nsname = ""; - - while ((cur = next_dirname(cur))[0] != '\0') { - nsdirname = nsname; - nsname = cur; - } - - if (!memcmp(nsdirname, "ipcns/", strlen("ipcns/"))) - nstype = CLONE_NEWIPC; - else if (!memcmp(nsdirname, "netns/", strlen("netns/"))) - nstype = CLONE_NEWNET; - else if (!memcmp(nsdirname, "utcns/", strlen("utcns/"))) - nstype = CLONE_NEWUTS; - else - badusage("invalid namepspace path"); - - namespace = xmalloc(sizeof(*namespace)); - namespace->path = (char *)path; - namespace->nstype = nstype; - LIST_INSERT_HEAD(&namespace_head, namespace, list); -} - -#ifdef HAVE_LXC -static void -set_namespaces() -{ - struct namespace *namespace; - int fd; - - LIST_FOREACH(namespace, &namespace_head, list) { - if ((fd = open(namespace->path, O_RDONLY)) == -1) - fatal("open namespace %s: %s", namespace->path, strerror(errno)); - if (setns(fd, namespace->nstype) == -1) - fatal("setns %s: %s", namespace->path, strerror(errno)); - } -} -#else -static void -set_namespaces() -{ - if (!LIST_EMPTY(&namespace_head)) - fatal("LCX namespaces not supported"); -} -#endif - -static void -do_help(void) -{ - printf( -"start-stop-daemon " VERSION " for Debian - small and fast C version written by\n" -"Marek Michalkiewicz , public domain.\n" -"\n" -"Usage:\n" -" start-stop-daemon -S|--start options ... -- arguments ...\n" -" start-stop-daemon -K|--stop options ...\n" -" start-stop-daemon -H|--help\n" -" start-stop-daemon -V|--version\n" -"\n" -"Options (at least one of --exec|--pidfile|--user is required):\n" -" -x|--exec program to start/check if it is running\n" -" -p|--pidfile pid file to check\n" -" -c|--chuid \n" -" change to this user/group before starting process\n" -" -u|--user | stop processes owned by this user\n" -" -n|--name stop processes with this name\n" -" -s|--signal signal to send (default TERM)\n" -" -a|--startas program to start (default is )\n" -" -N|--nicelevel add incr to the process's nice level\n" -" -b|--background force the process to detach\n" -" -m|--make-pidfile create the pidfile before starting\n" -" -R|--retry check whether processes die, and retry\n" -" -t|--test test mode, don't do anything\n" -" -o|--oknodo exit status 0 (not 1) if nothing done\n" -" -q|--quiet be more quiet\n" -" -v|--verbose be more verbose\n" -"Retry is |//... where is one of\n" -" -|[-] send that signal\n" -" wait that many seconds\n" -" forever repeat remainder forever\n" -"or may be just , meaning //KILL/\n" -"\n" -"Exit status: 0 = done 1 = nothing done (=> 0 if --oknodo)\n" -" 3 = trouble 2 = with --retry, processes wouldn't die\n"); -} - - -static void -badusage(const char *msg) -{ - if (msg) - fprintf(stderr, "%s: %s\n", progname, msg); - fprintf(stderr, "Try `%s --help' for more information.\n", progname); - exit(3); -} - -struct sigpair { - const char *name; - int signal; -}; - -const struct sigpair siglist[] = { - { "ABRT", SIGABRT }, - { "ALRM", SIGALRM }, - { "FPE", SIGFPE }, - { "HUP", SIGHUP }, - { "ILL", SIGILL }, - { "INT", SIGINT }, - { "KILL", SIGKILL }, - { "PIPE", SIGPIPE }, - { "QUIT", SIGQUIT }, - { "SEGV", SIGSEGV }, - { "TERM", SIGTERM }, - { "USR1", SIGUSR1 }, - { "USR2", SIGUSR2 }, - { "CHLD", SIGCHLD }, - { "CONT", SIGCONT }, - { "STOP", SIGSTOP }, - { "TSTP", SIGTSTP }, - { "TTIN", SIGTTIN }, - { "TTOU", SIGTTOU } -}; - -static int parse_integer (const char *string, int *value_r) { - unsigned long ul; - char *ep; - - if (!string[0]) - return -1; - - ul= strtoul(string,&ep,10); - if (ul > INT_MAX || *ep != '\0') - return -1; - - *value_r= ul; - return 0; -} - -static int parse_signal (const char *signal_str, int *signal_nr) -{ - unsigned int i; - - if (parse_integer(signal_str, signal_nr) == 0) - return 0; - - for (i = 0; i < sizeof (siglist) / sizeof (siglist[0]); i++) { - if (strcmp (signal_str, siglist[i].name) == 0) { - *signal_nr = siglist[i].signal; - return 0; - } - } - return -1; -} - -static void -parse_schedule_item(const char *string, struct schedule_item *item) { - const char *after_hyph; - - if (!strcmp(string,"forever")) { - item->type = sched_forever; - } else if (isdigit(string[0])) { - item->type = sched_timeout; - if (parse_integer(string, &item->value) != 0) - badusage("invalid timeout value in schedule"); - } else if ((after_hyph = string + (string[0] == '-')) && - parse_signal(after_hyph, &item->value) == 0) { - item->type = sched_signal; - } else { - badusage("invalid schedule item (must be [-], " - "-, or `forever'"); - } -} - -static void -parse_schedule(const char *schedule_str) { - char item_buf[20]; - const char *slash; - int count, repeatat; - ptrdiff_t str_len; - - count = 0; - for (slash = schedule_str; *slash; slash++) - if (*slash == '/') - count++; - - schedule_length = (count == 0) ? 4 : count+1; - schedule = xmalloc(sizeof(*schedule) * schedule_length); - - if (count == 0) { - schedule[0].type = sched_signal; - schedule[0].value = signal_nr; - parse_schedule_item(schedule_str, &schedule[1]); - if (schedule[1].type != sched_timeout) { - badusage ("--retry takes timeout, or schedule list" - " of at least two items"); - } - schedule[2].type = sched_signal; - schedule[2].value = SIGKILL; - schedule[3]= schedule[1]; - } else { - count = 0; - repeatat = -1; - while (schedule_str != NULL) { - slash = strchr(schedule_str,'/'); - str_len = slash ? slash - schedule_str : strlen(schedule_str); - if (str_len >= (ptrdiff_t)sizeof(item_buf)) - badusage("invalid schedule item: far too long" - " (you must delimit items with slashes)"); - memcpy(item_buf, schedule_str, str_len); - item_buf[str_len] = 0; - schedule_str = slash ? slash+1 : NULL; - - parse_schedule_item(item_buf, &schedule[count]); - if (schedule[count].type == sched_forever) { - if (repeatat >= 0) - badusage("invalid schedule: `forever'" - " appears more than once"); - repeatat = count; - continue; - } - count++; - } - if (repeatat >= 0) { - schedule[count].type = sched_goto; - schedule[count].value = repeatat; - count++; - } - assert(count == schedule_length); - } -} - -static void -parse_options(int argc, char * const *argv) -{ - static struct option longopts[] = { - { "help", 0, NULL, 'H'}, - { "stop", 0, NULL, 'K'}, - { "start", 0, NULL, 'S'}, - { "version", 0, NULL, 'V'}, - { "startas", 1, NULL, 'a'}, - { "name", 1, NULL, 'n'}, - { "oknodo", 0, NULL, 'o'}, - { "pidfile", 1, NULL, 'p'}, - { "quiet", 0, NULL, 'q'}, - { "signal", 1, NULL, 's'}, - { "test", 0, NULL, 't'}, - { "user", 1, NULL, 'u'}, - { "chroot", 1, NULL, 'r'}, - { "namespace", 1, NULL, 'd'}, - { "verbose", 0, NULL, 'v'}, - { "exec", 1, NULL, 'x'}, - { "chuid", 1, NULL, 'c'}, - { "nicelevel", 1, NULL, 'N'}, - { "background", 0, NULL, 'b'}, - { "make-pidfile", 0, NULL, 'm'}, - { "retry", 1, NULL, 'R'}, - { NULL, 0, NULL, 0} - }; - int c; - - for (;;) { - c = getopt_long(argc, argv, "HKSVa:n:op:qr:d:s:tu:vx:c:N:bmR:", - longopts, (int *) 0); - if (c == -1) - break; - switch (c) { - case 'H': /* --help */ - do_help(); - exit(0); - case 'K': /* --stop */ - stop = 1; - break; - case 'S': /* --start */ - start = 1; - break; - case 'V': /* --version */ - printf("start-stop-daemon " VERSION "\n"); - exit(0); - case 'a': /* --startas */ - startas = optarg; - break; - case 'n': /* --name */ - cmdname = optarg; - break; - case 'o': /* --oknodo */ - exitnodo = 0; - break; - case 'p': /* --pidfile */ - pidfile = optarg; - break; - case 'q': /* --quiet */ - quietmode = 1; - break; - case 's': /* --signal */ - signal_str = optarg; - break; - case 't': /* --test */ - testmode = 1; - break; - case 'u': /* --user | */ - userspec = optarg; - break; - case 'v': /* --verbose */ - quietmode = -1; - break; - case 'x': /* --exec */ - execname = optarg; - break; - case 'c': /* --chuid | */ - /* we copy the string just in case we need the - * argument later. */ - changeuser = strdup(optarg); - changeuser = strtok(changeuser, ":"); - changegroup = strtok(NULL, ":"); - break; - case 'r': /* --chroot /new/root */ - changeroot = optarg; - break; - case 'd': /* --namespace /.../||/name */ - add_namespace(optarg); - break; - case 'N': /* --nice */ - nicelevel = atoi(optarg); - break; - case 'b': /* --background */ - background = 1; - break; - case 'm': /* --make-pidfile */ - mpidfile = 1; - break; - case 'R': /* --retry | */ - schedule_str = optarg; - break; - default: - badusage(NULL); /* message printed by getopt */ - } - } - - if (signal_str != NULL) { - if (parse_signal (signal_str, &signal_nr) != 0) - badusage("signal value must be numeric or name" - " of signal (KILL, INTR, ...)"); - } - - if (schedule_str != NULL) { - parse_schedule(schedule_str); - } - - if (start == stop) - badusage("need one of --start or --stop"); - - if (!execname && !pidfile && !userspec && !cmdname) - badusage("need at least one of --exec, --pidfile, --user or --name"); - - if (!startas) - startas = execname; - - if (start && !startas) - badusage("--start needs --exec or --startas"); - - if (mpidfile && pidfile == NULL) - badusage("--make-pidfile is only relevant with --pidfile"); - - if (background && !start) - badusage("--background is only relevant with --start"); - -} - -static int -pid_is_exec(pid_t pid, const struct stat *esb) -{ - struct stat sb; - char buf[32]; - - sprintf(buf, "/proc/%d/exe", pid); - if (stat(buf, &sb) != 0) - return 0; - return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino); -} - - -static int -pid_is_user(pid_t pid, uid_t uid) -{ - struct stat sb; - char buf[32]; - - sprintf(buf, "/proc/%d", pid); - if (stat(buf, &sb) != 0) - return 0; - return (sb.st_uid == uid); -} - - -static int -pid_is_cmd(pid_t pid, const char *name) -{ - char buf[32]; - FILE *f; - int c; - - sprintf(buf, "/proc/%d/stat", pid); - f = fopen(buf, "r"); - if (!f) - return 0; - while ((c = getc(f)) != EOF && c != '(') - ; - if (c != '(') { - fclose(f); - return 0; - } - /* this hopefully handles command names containing ')' */ - while ((c = getc(f)) != EOF && c == *name) - name++; - fclose(f); - return (c == ')' && *name == '\0'); -} - - -static void -check(pid_t pid) -{ - if (execname && !pid_is_exec(pid, &exec_stat)) - return; - if (userspec && !pid_is_user(pid, user_id)) - return; - if (cmdname && !pid_is_cmd(pid, cmdname)) - return; - push(&found, pid); -} - -static void -do_pidfile(const char *name) -{ - FILE *f; - pid_t pid; - - f = fopen(name, "r"); - if (f) { - if (fscanf(f, "%d", &pid) == 1) - check(pid); - fclose(f); - } else if (errno != ENOENT) - fatal("open pidfile %s: %s", name, strerror(errno)); - -} - -/* WTA: this needs to be an autoconf check for /proc/pid existance. - */ -static void -do_procinit(void) -{ - DIR *procdir; - struct dirent *entry; - int foundany; - pid_t pid; - - procdir = opendir("/proc"); - if (!procdir) - fatal("opendir /proc: %s", strerror(errno)); - - foundany = 0; - while ((entry = readdir(procdir)) != NULL) { - if (sscanf(entry->d_name, "%d", &pid) != 1) - continue; - foundany++; - check(pid); - } - closedir(procdir); - if (!foundany) - fatal("nothing in /proc - not mounted?"); -} - -static void -do_findprocs(void) -{ - clear(&found); - - if (pidfile) - do_pidfile(pidfile); - else - do_procinit(); -} - -/* return 1 on failure */ -static void -do_stop(int signal_nr, int quietmode, int *n_killed, int *n_notkilled, int retry_nr) -{ - struct pid_list *p; - - do_findprocs(); - - *n_killed = 0; - *n_notkilled = 0; - - if (!found) - return; - - clear(&killed); - - for (p = found; p; p = p->next) { - if (testmode) - printf("Would send signal %d to %d.\n", - signal_nr, p->pid); - else if (kill(p->pid, signal_nr) == 0) { - push(&killed, p->pid); - (*n_killed)++; - } else { - printf("%s: warning: failed to kill %d: %s\n", - progname, p->pid, strerror(errno)); - (*n_notkilled)++; - } - } - if (quietmode < 0 && killed) { - printf("Stopped %s (pid", what_stop); - for (p = killed; p; p = p->next) - printf(" %d", p->pid); - putchar(')'); - if (retry_nr > 0) - printf(", retry #%d", retry_nr); - printf(".\n"); - } -} - - -static void -set_what_stop(const char *str) -{ - strncpy(what_stop, str, sizeof(what_stop)); - what_stop[sizeof(what_stop)-1] = '\0'; -} - -static int -run_stop_schedule(void) -{ - int r, position, n_killed, n_notkilled, value, ratio, anykilled, retry_nr; - struct timeval stopat, before, after, interval, maxinterval; - - if (testmode) { - if (schedule != NULL) { - printf("Ignoring --retry in test mode\n"); - schedule = NULL; - } - } - - if (cmdname) - set_what_stop(cmdname); - else if (execname) - set_what_stop(execname); - else if (pidfile) - sprintf(what_stop, "process in pidfile `%.200s'", pidfile); - else if (userspec) - sprintf(what_stop, "process(es) owned by `%.200s'", userspec); - else - fatal("internal error, please report"); - - anykilled = 0; - retry_nr = 0; - - if (schedule == NULL) { - do_stop(signal_nr, quietmode, &n_killed, &n_notkilled, 0); - if (n_notkilled > 0 && quietmode <= 0) - printf("%d pids were not killed\n", n_notkilled); - if (n_killed) - anykilled = 1; - goto x_finished; - } - - for (position = 0; position < schedule_length; ) { - value= schedule[position].value; - n_notkilled = 0; - - switch (schedule[position].type) { - - case sched_goto: - position = value; - continue; - - case sched_signal: - do_stop(value, quietmode, &n_killed, &n_notkilled, retry_nr++); - if (!n_killed) - goto x_finished; - else - anykilled = 1; - goto next_item; - - case sched_timeout: - /* We want to keep polling for the processes, to see if they've exited, - * or until the timeout expires. - * - * This is a somewhat complicated algorithm to try to ensure that we - * notice reasonably quickly when all the processes have exited, but - * don't spend too much CPU time polling. In particular, on a fast - * machine with quick-exiting daemons we don't want to delay system - * shutdown too much, whereas on a slow one, or where processes are - * taking some time to exit, we want to increase the polling - * interval. - * - * The algorithm is as follows: we measure the elapsed time it takes - * to do one poll(), and wait a multiple of this time for the next - * poll. However, if that would put us past the end of the timeout - * period we wait only as long as the timeout period, but in any case - * we always wait at least MIN_POLL_INTERVAL (20ms). The multiple - * (`ratio') starts out as 2, and increases by 1 for each poll to a - * maximum of 10; so we use up to between 30% and 10% of the - * machine's resources (assuming a few reasonable things about system - * performance). - */ - xgettimeofday(&stopat); - stopat.tv_sec += value; - ratio = 1; - for (;;) { - xgettimeofday(&before); - if (timercmp(&before,&stopat,>)) - goto next_item; - - do_stop(0, 1, &n_killed, &n_notkilled, 0); - if (!n_killed) - goto x_finished; - - xgettimeofday(&after); - - if (!timercmp(&after,&stopat,<)) - goto next_item; - - if (ratio < 10) - ratio++; - - TVCALC(interval, ratio * (TVELEM(&after) - TVELEM(&before) + TVADJUST)); - TVCALC(maxinterval, TVELEM(&stopat) - TVELEM(&after) + TVADJUST); - - if (timercmp(&interval,&maxinterval,>)) - interval = maxinterval; - - if (interval.tv_sec == 0 && - interval.tv_usec <= MIN_POLL_INTERVAL) - interval.tv_usec = MIN_POLL_INTERVAL; - - r = select(0,0,0,0,&interval); - if (r < 0 && errno != EINTR) - fatal("select() failed for pause: %s", - strerror(errno)); - } - - default: - assert(!"schedule[].type value must be valid"); - - } - - next_item: - position++; - } - - if (quietmode <= 0) - printf("Program %s, %d process(es), refused to die.\n", - what_stop, n_killed); - - return 2; - -x_finished: - if (!anykilled) { - if (quietmode <= 0) - printf("No %s found running; none killed.\n", what_stop); - return exitnodo; - } else { - return 0; - } -} - -/* -int main(int argc, char **argv) NONRETURNING; -*/ - -int -main(int argc, char **argv) -{ - progname = argv[0]; - - LIST_INIT(&namespace_head); - - parse_options(argc, argv); - argc -= optind; - argv += optind; - - if (execname && stat(execname, &exec_stat)) - fatal("stat %s: %s", execname, strerror(errno)); - - if (userspec && sscanf(userspec, "%d", &user_id) != 1) { - struct passwd *pw; - - pw = getpwnam(userspec); - if (!pw) - fatal("user `%s' not found\n", userspec); - - user_id = pw->pw_uid; - } - - if (changegroup && sscanf(changegroup, "%d", &runas_gid) != 1) { - struct group *gr = getgrnam(changegroup); - if (!gr) - fatal("group `%s' not found\n", changegroup); - runas_gid = gr->gr_gid; - } - if (changeuser && sscanf(changeuser, "%d", &runas_uid) != 1) { - struct passwd *pw = getpwnam(changeuser); - if (!pw) - fatal("user `%s' not found\n", changeuser); - runas_uid = pw->pw_uid; - if (changegroup == NULL) { /* pass the default group of this user */ - changegroup = ""; /* just empty */ - runas_gid = pw->pw_gid; - } - } - - if (stop) { - int i = run_stop_schedule(); - exit(i); - } - - do_findprocs(); - - if (found) { - if (quietmode <= 0) - printf("%s already running.\n", execname); - exit(exitnodo); - } - if (testmode) { - printf("Would start %s ", startas); - while (argc-- > 0) - printf("%s ", *argv++); - if (changeuser != NULL) { - printf(" (as user %s[%d]", changeuser, runas_uid); - if (changegroup != NULL) - printf(", and group %s[%d])", changegroup, runas_gid); - else - printf(")"); - } - if (changeroot != NULL) - printf(" in directory %s", changeroot); - if (nicelevel) - printf(", and add %i to the priority", nicelevel); - printf(".\n"); - exit(0); - } - if (quietmode < 0) - printf("Starting %s...\n", startas); - *--argv = startas; - if (changeroot != NULL) { - if (chdir(changeroot) < 0) - fatal("Unable to chdir() to %s", changeroot); - if (chroot(changeroot) < 0) - fatal("Unable to chroot() to %s", changeroot); - } - if (changeuser != NULL) { - if (setgid(runas_gid)) - fatal("Unable to set gid to %d", runas_gid); - if (initgroups(changeuser, runas_gid)) - fatal("Unable to set initgroups() with gid %d", runas_gid); - if (setuid(runas_uid)) - fatal("Unable to set uid to %s", changeuser); - } - - if (background) { /* ok, we need to detach this process */ - int i, fd; - if (quietmode < 0) - printf("Detatching to start %s...", startas); - i = fork(); - if (i<0) { - fatal("Unable to fork.\n"); - } - if (i) { /* parent */ - if (quietmode < 0) - printf("done.\n"); - exit(0); - } - /* child continues here */ - /* now close all extra fds */ - for (i=getdtablesize()-1; i>=0; --i) close(i); - /* change tty */ - fd = open("/dev/tty", O_RDWR); - ioctl(fd, TIOCNOTTY, 0); - close(fd); - chdir("/"); - umask(022); /* set a default for dumb programs */ - setpgid(0,0); /* set the process group */ - fd=open("/dev/null", O_RDWR); /* stdin */ - dup(fd); /* stdout */ - dup(fd); /* stderr */ - } - if (nicelevel) { - errno = 0; - if (nice(nicelevel) < 0 && errno) - fatal("Unable to alter nice level by %i: %s", nicelevel, - strerror(errno)); - } - if (mpidfile && pidfile != NULL) { /* user wants _us_ to make the pidfile :) */ - FILE *pidf = fopen(pidfile, "w"); - pid_t pidt = getpid(); - if (pidf == NULL) - fatal("Unable to open pidfile `%s' for writing: %s", pidfile, - strerror(errno)); - fprintf(pidf, "%d\n", pidt); - fclose(pidf); - } - set_namespaces(); - execv(startas, argv); - fatal("Unable to start %s: %s", startas, strerror(errno)); -} diff --git a/projects/miragesdk/pkg/mirage-compile/.gitignore b/projects/miragesdk/pkg/mirage-compile/.gitignore deleted file mode 100644 index 052e350ae..000000000 --- a/projects/miragesdk/pkg/mirage-compile/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.hash -functoria -mirage diff --git a/projects/miragesdk/pkg/mirage-compile/Dockerfile b/projects/miragesdk/pkg/mirage-compile/Dockerfile deleted file mode 100644 index 6ebfa314b..000000000 --- a/projects/miragesdk/pkg/mirage-compile/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM ocaml/opam:alpine-3.5_ocaml-4.04.0 -RUN git -C /home/opam/opam-repository pull origin master && opam update -u - -RUN opam pin add -n functoria https://github.com/samoht/functoria.git#output -RUN opam pin add -n mirage https://github.com/samoht/mirage.git#static -RUN opam pin add -n mirage-net-fd https://github.com/mirage/mirage-net-fd.git -RUN opam pin add -n charrua-client https://github.com/yomimono/charrua-client.git#state-halfway -RUN opam depext -uiy ocamlfind topkg-care ocamlbuild lwt mirage-types-lwt mirage -RUN opam depext -uiy charrua-client cohttp conduit mirage-unix -RUN opam depext -uiy mirage-net-fd ptime mirage-logs - -RUN sudo mkdir -p /src -RUN sudo chown -R opam /src -WORKDIR /src - -COPY compile.sh /usr/bin/ - -ENTRYPOINT ["/usr/bin/compile.sh"] diff --git a/projects/miragesdk/pkg/mirage-compile/Makefile b/projects/miragesdk/pkg/mirage-compile/Makefile deleted file mode 100644 index 2e463671a..000000000 --- a/projects/miragesdk/pkg/mirage-compile/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -.PHONY: tag push - -BASE=ocaml/opam:alpine-3.5_ocaml-4.04.0 -IMAGE=mirage-compile - -default: push - -hash: Dockerfile compile.sh - docker pull $(BASE) - tar cf - $^ | docker build -t $(IMAGE):build - - docker run --rm --entrypoint=/bin/sh $(IMAGE):build -c \ - '{ dpkg-query -W; \ - opam list; \ - cat /usr/bin/compile.sh; \ - } | sha1sum' | sed 's/ .*//' > hash - -push: hash - docker pull mobylinux/$(IMAGE):$(shell cat hash) || \ - (docker tag $(IMAGE):build mobylinux/$(IMAGE):$(shell cat hash) && \ - docker push mobylinux/$(IMAGE):$(shell cat hash)) - docker rmi $(IMAGE):build - rm -f hash - -tag: hash - docker pull mobylinux/$(IMAGE):$(shell cat hash) || \ - docker tag $(IMAGE):build mobylinux/$(IMAGE):$(shell cat hash) - docker rmi $(IMAGE):build - rm -f hash - -clean: - rm -f hash $(HASHES) - -.DELETE_ON_ERROR: diff --git a/projects/miragesdk/pkg/mirage-compile/compile.sh b/projects/miragesdk/pkg/mirage-compile/compile.sh deleted file mode 100755 index fdfcadc03..000000000 --- a/projects/miragesdk/pkg/mirage-compile/compile.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh - -set -e - -usage() { - echo "Usage: -o file" - exit 1 -} - -[ $# = 0 ] && usage - -while [ $# -gt 0 ] -do - flag="$1" - case "$flag" in - -o) - [ $# -eq 1 ] && usage - out="$2" - mkdir -p "$(dirname $2)" - shift - ;; - *) - echo "Unknown option $1" - exit 1 - esac - shift -done - -[ -z "$out" ] && usage - -package=$(basename "$out") - -dir="/src" - -# untar input -tar xf - -C $dir - -( - cd $dir - opam config exec -- mirage configure -o $out -t unix - opam config exec -- make depend - opam config exec -- make - mv $(readlink $out) $out -) > /src/logs 2>&1 - -cd $dir && tar -cf - $out - -exit 0 diff --git a/projects/miragesdk/src/Dockerfile b/projects/miragesdk/src/Dockerfile index e71e03176..1cdc8c28d 100644 --- a/projects/miragesdk/src/Dockerfile +++ b/projects/miragesdk/src/Dockerfile @@ -5,7 +5,7 @@ FROM alpine:3.5 as capnp RUN mkdir -p /src RUN apk update && apk add autoconf automake libtool linux-headers git g++ make -RUN cd /src && git clone -b v0.6.0 https://github.com/sandstorm-io/capnproto.git +RUN cd /src && git clone https://github.com/sandstorm-io/capnproto.git WORKDIR /src/capnproto/c++ RUN ./setup-autotools.sh RUN autoreconf -i @@ -17,37 +17,45 @@ RUN which capnp ### SDK -FROM ocaml/opam:alpine-3.5_ocaml-4.04.0 as sdk +FROM ocaml/opam@sha256:1e1d7fafbfd461bf684b5e11213c85a71fec93577455285e5d82d902ffad91d2 as sdk +#FROM ocaml/opam:alpine-3.5_ocaml-4.04.0 as sdk COPY --from=capnp /usr/local/bin/capnp /usr/local/bin/ -COPY --from=capnp /usr/local/lib/libcapnpc-0.6.0.so /usr/local/lib/ -COPY --from=capnp /usr/local/lib/libcapnp-0.6.0.so /usr/local/lib/ -COPY --from=capnp /usr/local/lib/libkj-0.6.0.so /usr/local/lib/ +COPY --from=capnp /usr/local/bin/capnpc /usr/local/bin/ +COPY --from=capnp /usr/local/lib/libcapnpc-0.7-dev.so /usr/local/lib/ +COPY --from=capnp /usr/local/lib/libcapnp-0.7-dev.so /usr/local/lib/ +COPY --from=capnp /usr/local/lib/libkj-0.7-dev.so /usr/local/lib/ RUN sudo mkdir -p /src USER opam WORKDIR /src -RUN git -C /home/opam/opam-repository pull && opam update -u -RUN opam pin add jbuilder 1.0+beta10 -n +RUN git -C /home/opam/opam-repository fetch origin && \ + git -C /home/opam/opam-repository reset ad921dfa87c2e201ef54806d0367aaacce75bc62 --hard && \ + opam update -u -RUN opam depext -uiy cstruct cstruct-lwt lwt lwt logs irmin-git rawlink tuntap astring rresult \ - mirage-flow-lwt mirage-channel-lwt io-page decompress capnp +RUN opam pin add -n capnp.dev 'https://github.com/talex5/capnp-ocaml.git#interfaces' +RUN opam pin add -n capnp-rpc.dev 'https://github.com/mirage/capnp-rpc.git' +RUN opam pin add -n capnp-rpc-lwt.dev 'https://github.com/mirage/capnp-rpc.git' + +COPY sdk.opam /src +RUN sudo chown opam -R /src +RUN opam pin add sdk.local /src -n + +RUN opam depext -y alcotest sdk +RUN opam install alcotest && opam install --deps sdk RUN opam list COPY ./sdk /src/ -COPY ./sdk.opam /src/ RUN sudo chown opam -R /src -RUN opam config exec -- jbuilder build -p sdk @install -RUN opam config exec -- jbuilder install sdk +RUN opam update sdk && opam install sdk -t ### Privileged Container FROM sdk as priv -RUN opam pin add tuntap 1.0.0 -n RUN opam depext -iy bos cmdliner RUN opam list @@ -56,16 +64,17 @@ COPY ./dhcp-client /src/dhcp-client RUN sudo chown opam -R /src RUN opam config exec -- jbuilder build dhcp-client/main.exe -RUN sudo mkdir -p /bin -RUN sudo cp /src/_build/default/dhcp-client/main.exe /bin/dhcp-client +RUN sudo mkdir -p /out +RUN sudo cp /src/_build/default/dhcp-client/main.exe /out/dhcp-client ### Calf Container FROM sdk as calf -RUN opam pin add charrua-client https://github.com/yomimono/charrua-client.git#with-cdhcpc -n +RUN opam pin add charrua-client.dev https://github.com/samoht/charrua-client.git#with-cdhcpc -n RUN opam pin add mirage-net-fd 0.2.0 -n +RUN opam list RUN opam depext -iy mirage-net-fd charrua-client lwt mirage-types-lwt cmdliner RUN opam list @@ -75,15 +84,15 @@ COPY ./dhcp-client-calf/jbuild /src/dhcp-client-calf/ RUN sudo chown opam -R /src RUN opam config exec -- jbuilder build dhcp-client-calf/unikernel.exe -RUN sudo mkdir -p /bin/calf -RUN sudo cp /src/_build/default/dhcp-client-calf/unikernel.exe /bin/calf/dhcp-client-calf -USER 0 +RUN sudo mkdir -p /out/ +RUN sudo cp /src/_build/default/dhcp-client-calf/unikernel.exe /out/dhcp-client-calf ### Final build FROM scratch -COPY --from=priv /bin / -COPY --from=calf /bin / -COPY dhcp-client-calf/config.json /calf/ +USER 0 +COPY --from=priv /out / +COPY --from=calf /out /calf/rootfs/ +COPY dhcp-client-calf/config.json /calf CMD ["/dhcp-client", "-vv"] diff --git a/projects/miragesdk/src/Makefile b/projects/miragesdk/src/Makefile index cf9182d92..44b4f321a 100644 --- a/projects/miragesdk/src/Makefile +++ b/projects/miragesdk/src/Makefile @@ -1,39 +1,25 @@ -BASE=ocaml/opam:alpine-3.5_ocaml-4.04.0 - -FILES=$(shell find . -name jbuild) \ - $(shell find sdk/ -name '*.ml') \ - $(shell find sdk/ -name '*.mli') \ - dhcp-client/bpf/dhcp.c dhcp-client/main.ml \ - dhcp-client-calf/unikernel.ml dhcp-client-calf/config.json - -IMAGE=dhcp-client - +.PHONY: tag push default: push - @ -.build: Dockerfile $(FILES) - docker build $(NO_CACHE) -t $(IMAGE) -f Dockerfile . - docker build $(NO_CACHE) -t $(IMAGE) -f Dockerfile -q . > .build || \ - (rm -f $@ && exit 1) +ORG?=miragesdk +IMAGE=dhcp-client +NOCACHE?= -hash: Makefile Dockerfile $(FILES) .build - { cat $^; \ - docker run --rm --entrypoint sh $(IMAGE) -c 'cat /lib/apk/db/installed'; \ - docker run --rm --entrypoint sh $(IMAGE) -c 'opam list'; } \ - | sha1sum | sed 's/ .*//' > $@ +DEPS=Dockerfile \ + $(shell find . -name jbuild) \ + $(shell find . -name '*.ml') \ + $(shell find . -name '*.mli') \ + $(shell find . -name '*.c') \ + $(shell find . -name '*.json') -tag: .build - docker tag $(IMAGE) linuxkitprojects/$(IMAGE):$(shell cat hash) +HASH?=$(shell git ls-tree HEAD -- ../$(notdir $(CURDIR)) | awk '{print $$3}') -push: hash .build - docker pull $(BASE) - docker pull linuxkitprojects/$(IMAGE):$(shell cat hash) || \ - (docker tag $(IMAGE) linuxkitprojects/$(IMAGE):$(shell cat hash) && \ - docker push linuxkitprojects/$(IMAGE):$(shell cat hash)) +tag: $(DEPS) + docker build --squash $(NOCACHE) -t $(ORG)/$(IMAGE):$(HASH) . -clean:: - rm -rf hash .build - (docker rmi -f $(IMAGE) || echo ok) +push: tag + DOCKER_CONTENT_TRUST=1 docker pull $(ORG)/$(IMAGE):$(HASH) || \ + DOCKER_CONTENT_TRUST=1 docker push $(ORG)/$(IMAGE):$(HASH) #### DEV @@ -42,11 +28,9 @@ clean:: test: jbuilder runtest --dev -dev-clean: - rm -rf _build +clean: + jbuilder clean dev: jbuilder build dhcp-client/main.exe --dev jbuilder build dhcp-client-calf/unikernel.exe --dev - -.DELETE_ON_ERROR: diff --git a/projects/miragesdk/src/dhcp-client-calf/config.json b/projects/miragesdk/src/dhcp-client-calf/config.json index 212e28393..7a6beace2 100644 --- a/projects/miragesdk/src/dhcp-client-calf/config.json +++ b/projects/miragesdk/src/dhcp-client-calf/config.json @@ -17,7 +17,7 @@ } }, "root": { - "path": "calf", + "path": "rootfs", "readonly": true }, "mounts": [ diff --git a/projects/miragesdk/src/dhcp-client-calf/unikernel.ml b/projects/miragesdk/src/dhcp-client-calf/unikernel.ml index 640c8c3b1..dca4fe10b 100644 --- a/projects/miragesdk/src/dhcp-client-calf/unikernel.ml +++ b/projects/miragesdk/src/dhcp-client-calf/unikernel.ml @@ -208,8 +208,10 @@ let flow (x: int) = Sdk.Init.file_descr (Lwt_unix.of_unix_file_descr @@ fd x) let start () dhcp_codes net ctl = Lwt_main.run ( + Lwt_switch.with_switch @@ fun switch -> let net = fd net in - let ctl = Sdk.Ctl.Client.v (flow ctl) in + let client = Capnp_rpc_lwt.CapTP.of_endpoint ~switch (Capnp_rpc_lwt.Endpoint.of_flow ~switch (module Sdk.IO) (flow ctl)) in + let ctl = Capnp_rpc_lwt.CapTP.bootstrap client in start () dhcp_codes net ctl ) diff --git a/projects/miragesdk/src/dhcp-client/bpf/dhcp.c b/projects/miragesdk/src/dhcp-client/bpf/dhcp.c index a81d51cbb..9382e4918 100644 --- a/projects/miragesdk/src/dhcp-client/bpf/dhcp.c +++ b/projects/miragesdk/src/dhcp-client/bpf/dhcp.c @@ -28,6 +28,8 @@ #include +#ifdef __linux__ + #include #include @@ -87,3 +89,5 @@ CAMLprim value bpf_filter(value vunit) memcpy(String_val(vfilter), bootp_bpf_filter, sizeof(bootp_bpf_filter)); CAMLreturn (vfilter); } + +#endif diff --git a/projects/miragesdk/src/dhcp-client/main.ml b/projects/miragesdk/src/dhcp-client/main.ml index 6ae838926..d51addabe 100644 --- a/projects/miragesdk/src/dhcp-client/main.ml +++ b/projects/miragesdk/src/dhcp-client/main.ml @@ -60,7 +60,7 @@ let default_cmd = [ *) let default_cmd = [ - "/usr/bin/runc"; "run"; "--preserve-fds"; "2"; "--bundle"; "."; "calf" + "/usr/bin/runc"; "run"; "--preserve-fds"; "2"; "--bundle"; "calf"; "calf" ] let read_cmd file = @@ -83,13 +83,18 @@ let run () cmd ethif path = | Some f -> read_cmd f in Lwt_main.run ( + Lwt_switch.with_switch @@ fun switch -> let routes = [ ["ip"] , [`Write]; ["mac"] , [`Read ]; ["gateway"], [`Write]; ] in Ctl.v path >>= fun db -> - let ctl fd = Ctl.Server.listen ~routes db fd in + let ctl fd = + let service = Ctl.Server.service ~routes db in + let endpoint = Capnp_rpc_lwt.Endpoint.of_flow ~switch (module Sdk.IO) fd in + ignore (Capnp_rpc_lwt.CapTP.of_endpoint ~switch ~offer:service endpoint) + in let handlers () = Handlers.watch ~ethif db in let net = Init.rawlink ~filter:(dhcp_filter ()) ethif in Net.mac ethif >>= fun mac -> diff --git a/projects/miragesdk/src/sdk.opam b/projects/miragesdk/src/sdk.opam index 4f6e18743..ecaccaf18 100644 --- a/projects/miragesdk/src/sdk.opam +++ b/projects/miragesdk/src/sdk.opam @@ -13,6 +13,7 @@ depends: [ "jbuilder" {build & >= "1.0+beta7"} "ocamlfind" {build} "cstruct" + "cstruct-lwt" "lwt" "logs" "astring" "rresult" "mirage-flow-lwt" @@ -20,5 +21,9 @@ depends: [ "io-page" "irmin-git" "decompress" - "capnp" + "capnp-rpc-lwt" + "rawlink" + "tuntap" {= "1.0.0"} + "ipaddr" + "alcotest" {test} ] diff --git a/projects/miragesdk/src/sdk/ctl.ml b/projects/miragesdk/src/sdk/ctl.ml index 11a339137..cfbd49f7f 100644 --- a/projects/miragesdk/src/sdk/ctl.ml +++ b/projects/miragesdk/src/sdk/ctl.ml @@ -1,4 +1,5 @@ open Lwt.Infix +open Capnp_rpc_lwt let src = Logs.Src.create "init" ~doc:"Init steps" module Log = (val Logs.src_log src : Logs.LOG) @@ -20,6 +21,8 @@ end module Store = Irmin_git.FS.KV(No_IO)(Inflator)(Io_fs) module KV = Store(Irmin.Contents.String) +let pp_path = Fmt.(brackets (list ~sep:(const string "/") string)) + let v path = let config = Irmin_git.config path in KV.Repo.v config >>= fun repo -> @@ -33,282 +36,50 @@ let () = module C = Mirage_channel_lwt.Make(IO) -module P = Proto.Make(Capnp.BytesMessage) - exception Undefined_field of int -module Endpoint = struct - - let compression = `None - - type t = { - output : IO.t; - input : C.t; (* reads are buffered *) - decoder: Capnp.Codecs.FramedStream.t; - } - - type error = [ - | `IO of IO.write_error - | `Channel of C.error - | `Msg of string - | `Undefined_field of int - ] - - let pp_error ppf (e:error) = match e with - | `IO e -> Fmt.pf ppf "IO: %a" IO.pp_write_error e - | `Channel e -> Fmt.pf ppf "channel: %a" C.pp_error e - | `Msg e -> Fmt.string ppf e - | `Undefined_field i -> Fmt.pf ppf "undefined field %d" i - - let err_io e = Error (`IO e) - let err_channel e = Error (`Channel e) - let err_msg fmt = Fmt.kstrf (fun s -> Error (`Msg s)) fmt - let err_frame = err_msg "Unsupported Cap'n'Proto frame received" - let err_undefined_field i = Error (`Undefined_field i) - - let v fd = - let output = fd in - let input = C.create fd in - let decoder = Capnp.Codecs.FramedStream.empty compression in - { output; input; decoder } - - let send t msg = - let buf = Capnp.Codecs.serialize ~compression msg in - (* FIXME: avoid copying *) - IO.write t.output (Cstruct.of_string buf) >|= function - | Error e -> err_io e - | Ok () -> Ok () - - let rec recv t = - match Capnp.Codecs.FramedStream.get_next_frame t.decoder with - | Ok msg -> Lwt.return (Ok (`Data msg)) - | Error Capnp.Codecs.FramingError.Unsupported -> Lwt.return err_frame - | Error Capnp.Codecs.FramingError.Incomplete -> - Log.info (fun f -> f "Endpoint.recv: incomplete; waiting for more data"); - C.read_some ~len:4096 t.input >>= function - | Ok `Eof -> Lwt.return (Ok `Eof) - | Error e -> Lwt.return (err_channel e) - | Ok (`Data data) -> - (* FIXME: avoid copying *) - let data = Cstruct.to_string data in - Log.info (fun f -> f "Got %S" data); - Capnp.Codecs.FramedStream.add_fragment t.decoder data; - recv t - -end - -module Request = struct - - type action = - | Write of string - | Read - | Delete - - let pp_action ppf = function - | Write s -> Fmt.pf ppf "write[%S]" s - | Read -> Fmt.pf ppf "read" - | Delete -> Fmt.pf ppf "delete" - - type t = { - id : int32 Lazy.t; - path : string list Lazy.t; - action: action Lazy.t; - } - - let id t = Lazy.force t.id - let path t = Lazy.force t.path - let action t = Lazy.force t.action - - let pp_path = Fmt.(list ~sep:(unit "/") string) - - let pp ppf t = - let id = id t and path = path t and action = action t in - match action with - | exception Undefined_field i -> Fmt.pf ppf "" i - | action -> Fmt.pf ppf "%ld:%a:%a" id pp_path path pp_action action - - let equal x y = - id x = id y && path x = path y && match action x = action y with - | exception Undefined_field _ -> false - | b -> b - - let v ~id ~path action = - { id = lazy id; action = lazy action; path = lazy path } - - let read e: (t, Endpoint.error) result Lwt.t = - Endpoint.recv e >|= function - | Error e -> Error e - | Ok `Eof -> Error (`IO `Closed) - | Ok (`Data x) -> - let open P.Reader in - let msg = Request.of_message x in - let id = lazy (Request.id_get msg) in - let path = lazy (Request.path_get_list msg) in - let action = lazy (match Request.get msg with - | Request.Write x -> Write x - | Request.Read -> Read - | Request.Delete -> Delete - | Request.Undefined i -> raise (Undefined_field i) - ) in - Ok { id; path; action } - - let write e t = - let open P.Builder in - match action t with - | exception Undefined_field i -> Lwt.return (Endpoint.err_undefined_field i) - | action -> - let msg = - let b = Request.init_root () in - Request.id_set b (id t); - ignore (Request.path_set_list b (path t)); - (match action with - | Write x -> Request.write_set b x - | Read -> Request.read_set b - | Delete -> Request.delete_set b); - b - in - Endpoint.send e (Request.to_message msg) - -end - -module Response = struct - - type status = (string, string) result - - let pp_status ppf = function - | Ok ok -> Fmt.pf ppf "ok:%S" ok - | Error e -> Fmt.pf ppf "error:%S" e - - type t = { - id : int32 Lazy.t; - status: status Lazy.t; - } - - let v ~id status = { id = lazy id; status = lazy status } - let id t = Lazy.force t.id - let status t = Lazy.force t.status - - let pp ppf t = match status t with - | exception Undefined_field i -> Fmt.pf ppf "" i - | s -> Fmt.pf ppf "%ld:%a" (id t) pp_status s - - let equal x y = - id x = id y && match status x = status y with - | exception Undefined_field _ -> false - | b -> b - - let read e: (t, Endpoint.error) result Lwt.t = - Endpoint.recv e >|= function - | Error e -> Error e - | Ok `Eof -> Error (`IO `Closed) - | Ok (`Data x) -> - let open P.Reader in - let msg = Response.of_message x in - let id = lazy (Response.id_get msg) in - let status = lazy (match Response.get msg with - | Response.Ok x -> Ok x - | Response.Error x -> Error x - | Response.Undefined i -> raise (Undefined_field i) - ) in - Ok { id; status } - - let write e t = - let open P.Builder in - match status t with - | exception Undefined_field i -> Lwt.return (Endpoint.err_undefined_field i) - | s -> - let msg = - let b = Response.init_root () in - Response.id_set b (id t); - (match s with - | Error s -> Response.error_set b s - | Ok s -> Response.ok_set b s); - b - in - Endpoint.send e (Response.to_message msg) - -end - -let err_not_found = "err-not-found" +let errorf fmt = + Fmt.kstrf (fun x -> Error (`Msg x)) fmt module Client = struct - let new_id = - let n = ref 0l in - fun () -> n := Int32.succ !n; !n + module C = Ctl_api.Reader.Ctl type error = [`Msg of string] let pp_error ppf (`Msg s) = Fmt.string ppf s - module K = struct - type t = int32 - let equal = Int32.equal - let hash = Hashtbl.hash - end - module Cache = Hashtbl.Make(K) - - type t = { - e : Endpoint.t; - replies: Response.t Cache.t; - } - - let v fd = { e = Endpoint.v fd; replies = Cache.create 12 } - let err e = Fmt.kstrf (fun e -> Error (`Msg e)) "%a" Endpoint.pp_error e - - let call t r = - let id = Request.id r in - Request.write t.e r >>= function - | Error e -> Lwt.return (err e) - | Ok () -> - let rec loop () = - try - let r = Cache.find t.replies id in - Cache.remove t.replies id; - Lwt.return r - with Not_found -> - Response.read t.e >>= function - | Error e -> - Log.err (fun l -> l "Got %a while waiting for a reply to %ld" - Endpoint.pp_error e id); - loop () - | Ok r -> - let rid = Response.id r in - if rid = id then Lwt.return r - else ( - (* FIXME: maybe we want to check if id is not already - allocated *) - Cache.add t.replies rid r; - loop () - ) - in - loop () >|= fun r -> - assert (Response.id r = id); - match Response.status r with - | Ok s -> Ok s - | Error s -> Error (`Msg s) - - let request path action = - let id = new_id () in - Request.v ~id ~path action + type t = C.t Capability.t let read t path = - call t (request path Read) >|= function - | Ok x -> Ok (Some x) - | Error e -> - if e = `Msg err_not_found then Ok None - else Error e + let module P = Ctl_api.Builder.Ctl.Read_params in + let module R = Ctl_api.Reader.Response in + let req, p = Capability.Request.create P.init_pointer in + P.path_set_list p path |> ignore; + Capability.call_for_value t C.read_method req >|= function + | Error e -> errorf "error: read(%a) -> %a" pp_path path Capnp_rpc.Error.pp e + | Ok r -> + let r = R.of_payload r in + match R.get r with + | R.Ok data -> Ok (Some data) + | R.NotFound -> Ok None + | R.Undefined _ -> Error (`Msg "invalid return") - let write t path v = - call t (request path @@ Write v) >|= function - | Ok "" -> Ok () - | Ok _ -> Error (`Msg "invalid return") - | Error _ as e -> e + let write t path data = + let module P = Ctl_api.Builder.Ctl.Write_params in + let req, p = Capability.Request.create P.init_pointer in + P.path_set_list p path |> ignore; + P.data_set p data; + Capability.call_for_value t C.write_method req >|= function + | Ok _ -> Ok () + | Error e -> errorf "error: write(%a) -> %a" pp_path path Capnp_rpc.Error.pp e let delete t path = - call t (request path Delete) >|= function - | Ok "" -> Ok () - | Ok _ -> Error (`Msg "invalid return") - | Error _ as e -> e + let module P = Ctl_api.Builder.Ctl.Delete_params in + let req, p = Capability.Request.create P.init_pointer in + P.path_set_list p path |> ignore; + Capability.call_for_value t C.delete_method req >|= function + | Ok _ -> Ok () + | Error e -> errorf "error: delete(%a) -> %a" pp_path path Capnp_rpc.Error.pp e end @@ -316,80 +87,68 @@ module Server = struct type op = [ `Read | `Write | `Delete ] - let ok q s = Response.v ~id:(Request.id q) (Ok s) - let error q s = Response.v ~id:(Request.id q) (Error s) - let with_key q f = f (Request.path q) - let infof fmt = Fmt.kstrf (fun msg () -> let date = Int64.of_float (Unix.gettimeofday ()) in Irmin.Info.v ~date ~author:"calf" msg ) fmt - let not_allowed q = - let path = Request.path q in - let err = Fmt.strf "%a is not an allowed path" Request.pp_path path in - Log.err (fun l -> l "%ld: %a" (Request.id q) Request.pp_path path); - error q err + let not_allowed path = + let err = Fmt.strf "%a is not an allowed path" pp_path path in + Log.err (fun l -> l "%s" err); + err - let dispatch db op q = - with_key q (fun key -> - let can x = List.mem x op in - match Request.action q with - | exception Undefined_field i -> - Fmt.kstrf (fun e -> Lwt.return (error q e)) "undefined field %i" i - | Write s when can `Write -> - let info = infof "Updating %a" KV.Key.pp key in - KV.set db ~info key s >|= fun () -> - ok q "" - | Delete when can `Delete -> - let info = infof "Removing %a" KV.Key.pp key in - KV.remove db ~info key >|= fun () -> - ok q "" - | Read when can `Read -> - (KV.find db key >|= function - | None -> error q err_not_found - | Some v -> ok q v) - | _ -> Lwt.return (not_allowed q) - ) + let write db key value = + let info = infof "Updating %a" KV.Key.pp key in + KV.set db ~info key value - let listen ~routes db fd = - Log.debug (fun l -> l "Serving the control state over %a" IO.pp fd); - let queries = Queue.create () in - let cond = Lwt_condition.create () in - let e = Endpoint.v fd in - let rec listen () = - Request.read e >>= function - | Error (`Channel _ | `IO _ as e) -> - Log.err (fun l -> l "fatal error: %a" Endpoint.pp_error e); - Lwt.return_unit - | Error (`Msg _ | `Undefined_field _ as e) -> - Log.err (fun l -> l "transient error: %a" Endpoint.pp_error e); - listen () - | Ok q -> - Queue.add q queries; - Lwt_condition.signal cond (); - listen () - in - let rec process () = - Lwt_condition.wait cond >>= fun () -> - let q = Queue.pop queries in - let path = Request.path q in - (if List.mem_assoc path routes then ( - let op = List.assoc path routes in - dispatch db op q >>= fun r -> - Response.write e r - ) else ( - Response.write e (not_allowed q) - )) >>= function - | Ok () -> process () - | Error e -> - Log.err (fun l -> l "%a" Endpoint.pp_error e); - process () - in - Lwt.pick [ - listen (); - process (); - ] + let delete db key = + let info = infof "Removing %a" KV.Key.pp key in + KV.remove db ~info key + let with_permission_check ~routes op key fn = + match List.assoc key routes with + | perms when List.mem op perms -> fn () + | _ -> Service.fail "%s" (not_allowed key) + | exception Not_found -> Service.fail "%s" (not_allowed key) + + let service ~routes db = + Ctl_api.Builder.Ctl.local @@ + object (_ : Ctl_api.Builder.Ctl.service) + method read req = + let module P = Ctl_api.Reader.Ctl.Read_params in + let module R = Ctl_api.Builder.Response in + let params = P.of_payload req in + let key = P.path_get_list params in + with_permission_check ~routes `Read key @@ fun () -> + Service.return_lwt (fun () -> + let resp, r = Service.Response.create R.init_pointer in + (KV.find db key >|= function + | None -> R.not_found_set r + | Some x -> R.ok_set r x + ) >|= fun () -> + Ok resp + ) + + method write req = + let module P = Ctl_api.Reader.Ctl.Write_params in + let params = P.of_payload req in + let key = P.path_get_list params in + let value = P.data_get params in + with_permission_check ~routes `Write key @@ fun () -> + Service.return_lwt (fun () -> + write db key value >|= fun () -> + Ok (Service.Response.create_empty ()) + ) + + method delete req = + let module P = Ctl_api.Reader.Ctl.Delete_params in + let params = P.of_payload req in + let key = P.path_get_list params in + with_permission_check ~routes `Delete key @@ fun () -> + Service.return_lwt (fun () -> + delete db key >|= fun () -> + Ok (Service.Response.create_empty ()) + ) + end end diff --git a/projects/miragesdk/src/sdk/ctl.mli b/projects/miragesdk/src/sdk/ctl.mli index 896dfefdd..ecd95c833 100644 --- a/projects/miragesdk/src/sdk/ctl.mli +++ b/projects/miragesdk/src/sdk/ctl.mli @@ -4,103 +4,6 @@ exception Undefined_field of int -module Endpoint: sig - - type t - (** The type for SDK endpoints. *) - - val v: IO.t ->t - (** [v f] is a fresh endpoint state built on top of the flow [f]. *) - - (** The type for endpoint errors. *) - type error = private [> - | `IO of IO.write_error - | `Msg of string - | `Undefined_field of int - ] - - val pp_error: error Fmt.t - (** [pp_error] is the pretty-printer for errors. *) - -end - -module Request: sig - - type t - (** The type for SDK requests. *) - - (** The type for request actions. *) - type action = - | Write of string - | Read - | Delete - - val pp: t Fmt.t - (** [pp] is the pretty-printer for requests. *) - - val equal: t -> t -> bool - (** [equal] is the equality function for requests. *) - - val pp_action: action Fmt.t - (** [pp_action] is the pretty-printer for request actions. *) - - val action: t -> action - (** [action t] is [t]'s requested operation. Can raise - [Endpoint.Undefined_field]. *) - - val path: t -> string list - (** [path t] is the [t]'s request path. *) - - val id: t -> int32 - (** [id t] it [t]'s request id. *) - - val v: id:int32 -> path:string list -> action -> t - (** [v ~id ~path action] is a new request. *) - - val write: Endpoint.t -> t -> (unit, Endpoint.error) result Lwt.t - (** [write e t] writes a request message for the - action [action] and the path [path] using the unique ID [id]. *) - - val read: Endpoint.t -> (t, Endpoint.error) result Lwt.t - (** [read e] reads a query message. *) - -end - -module Response: sig - - type t - (** The type for responses. *) - - (** The type for response status. *) - type status = (string, string) result - - val pp: t Fmt.t - (** [pp] is the pretty-printer for responses. *) - - val equal: t -> t -> bool - (** [equal] is the equality function for responses. *) - - val pp_status: status Fmt.t - (** [pp_status] is the pretty-printer for response statuses. *) - - val status: t -> status - (** [status t] is [t]'s response status. Can raise - [Endpoint.Undefined_field]. *) - - val id: t -> int32 - (** [id t] is [t]'s response ID. *) - - val v: id:int32 -> status -> t - (** [v ~id status] is a new response. *) - - val write: Endpoint.t -> t -> (unit, Endpoint.error) result Lwt.t - (** [write fd t] writes a reply message. *) - - val read: Endpoint.t -> (t, Endpoint.error) result Lwt.t - (** [read fd] reads a reply message. *) - -end - module Client: sig (** Client-side of the control plane. The control plane state is a @@ -110,7 +13,7 @@ module Client: sig TODO: decide if we want to support test_and_set (instead of write) and some kind of watches. *) - type t + type t = Ctl_api.Reader.Ctl.t Capnp_rpc_lwt.Capability.t (** The type for client state. *) type error @@ -119,11 +22,6 @@ module Client: sig val pp_error: error Fmt.t (** [pp_error] is the pretty-printer for client errors. *) - val v: IO.t -> t - (** [v fd] is the client state using [fd] to send requests to the - server. A client state also stores some state for all the - incomplete client queries. *) - val read: t -> string list -> (string option, error) result Lwt.t (** [read t k] is the value associated with the key [k] in the control plane state. Return [None] if no value is associated to @@ -150,8 +48,8 @@ module Server: sig type op = [ `Read | `Write | `Delete ] (** The type for operations to perform on routes. *) - val listen: routes:(string list * op list) list -> KV.t -> IO.t -> unit Lwt.t - (** [listen ~routes kv fd] is the thread exposing the KV store [kv], + val service: routes:(string list * op list) list -> KV.t -> Ctl_api.Reader.Ctl.t Capnp_rpc_lwt.Capability.t + (** [service ~routes kv] is the thread exposing the KV store [kv], holding control plane state, running inside the privileged container. [routes] are the routes exposed by the server to the calf and [kv] is the control plane state. *) diff --git a/projects/miragesdk/src/sdk/init.ml b/projects/miragesdk/src/sdk/init.ml index d0590cd28..618796c12 100644 --- a/projects/miragesdk/src/sdk/init.ml +++ b/projects/miragesdk/src/sdk/init.ml @@ -288,6 +288,8 @@ let exec_and_forward ?(handlers=block_for_ever) ~pid ~cmd ~net ~ctl t = let priv_stdout = Fd.flow Pipe.(priv t.stdout) in let priv_stderr = Fd.flow Pipe.(priv t.stderr) in + ctl priv_ctl; + Lwt.pick ([ wait (); (* data *) @@ -298,7 +300,6 @@ let exec_and_forward ?(handlers=block_for_ever) ~pid ~cmd ~net ~ctl t = IO.forward ~verbose:false ~src:priv_stderr ~dst:Fd.(flow stderr); (* TODO: Init.Fd.forward ~src:Init.Pipe.(priv metrics) ~dst:Init.Fd.metric; *) - ctl priv_ctl; handlers (); ]) diff --git a/projects/miragesdk/src/sdk/init.mli b/projects/miragesdk/src/sdk/init.mli index f3e225496..590bf1d13 100644 --- a/projects/miragesdk/src/sdk/init.mli +++ b/projects/miragesdk/src/sdk/init.mli @@ -110,11 +110,11 @@ val exec: Pipe.monitor -> string list -> (int -> unit Lwt.t) -> unit Lwt.t (* FIXME(samoht): not very happy with that signatue *) val run: Pipe.monitor -> - net:IO.t -> ctl:(IO.t -> unit Lwt.t) -> + net:IO.t -> ctl:(IO.t -> unit) -> ?handlers:(unit -> unit Lwt.t) -> string list -> unit Lwt.t (** [run m ~net ~ctl ?handlers cmd] runs [cmd] in a unprivileged calf - process. [net] is the network interface flow. [ctl] is the control + process. [net] is the network interface flow. [ctl] runs the control thread connected to the {Pipe.ctl} pipe. [handlers] are the system handler thread which will react to control data to perform privileged system actions. *) diff --git a/projects/miragesdk/src/sdk/jbuild b/projects/miragesdk/src/sdk/jbuild index 6c57feede..2bf453ff0 100644 --- a/projects/miragesdk/src/sdk/jbuild +++ b/projects/miragesdk/src/sdk/jbuild @@ -3,16 +3,12 @@ (library ((name sdk) (public_name sdk) + (flags (:standard -w -53-55)) (libraries (cstruct.lwt decompress irmin irmin-git lwt.unix rawlink - tuntap astring rresult mirage-flow-lwt capnp - mirage-channel-lwt io-page.unix ipaddr)) - )) + tuntap astring rresult mirage-flow-lwt capnp capnp-rpc-lwt + mirage-channel-lwt io-page.unix ipaddr)))) (rule ((targets (proto.ml proto.mli)) (deps (proto.capnp)) - (action (progn - (run capnp compile -o ocaml ${<}) - (system "mv proto.ml proto.ml.in") - (system "echo '[@@@ocaml.warning \"-A\"]\n' > proto.ml") - (system "cat proto.ml.in >> proto.ml"))))) + (action (run capnp compile -o ocaml ${<})))) diff --git a/projects/miragesdk/src/sdk/net.ml b/projects/miragesdk/src/sdk/net.ml index d093888f5..2a99e60ce 100644 --- a/projects/miragesdk/src/sdk/net.ml +++ b/projects/miragesdk/src/sdk/net.ml @@ -2,8 +2,12 @@ open Lwt.Infix +let src = Logs.Src.create "net" ~doc:"Network Configuration" +module Log = (val Logs.src_log src : Logs.LOG) + let run fmt = Fmt.kstrf (fun str -> + Log.info (fun l -> l "run: %S" str); match Sys.command str with | 0 -> Lwt.return () | i -> Fmt.kstrf Lwt.fail_with "%S exited with code %d" str i diff --git a/projects/miragesdk/src/sdk/proto.capnp b/projects/miragesdk/src/sdk/proto.capnp index b204d1478..3b94b3705 100644 --- a/projects/miragesdk/src/sdk/proto.capnp +++ b/projects/miragesdk/src/sdk/proto.capnp @@ -1,19 +1,14 @@ @0x9e83562906de8259; -struct Request { - id @0 :Int32; - path @1 :List(Text); +struct Response { union { - write @2 :Data; - read @3 :Void; - delete @4 :Void; + ok @0 :Data; + notFound @1 :Void; } } -struct Response { - id @0: Int32; - union { - ok @1 :Data; - error @2 :Data; - } +interface Ctl { + write @0 (path :List(Text), data: Data) -> (); + read @1 (path :List(Text)) -> Response; + delete @2 (path :List(Text)) -> (); } diff --git a/projects/miragesdk/src/test/test.ml b/projects/miragesdk/src/test/test.ml index 4e4d87c6e..128c9b5f1 100644 --- a/projects/miragesdk/src/test/test.ml +++ b/projects/miragesdk/src/test/test.ml @@ -93,50 +93,6 @@ let test_socketpair pipe () = Lwt.return_unit -let request = Alcotest.testable Ctl.Request.pp Ctl.Request.equal -let response = Alcotest.testable Ctl.Response.pp Ctl.Response.equal - -let queries = - let open Ctl.Request in - [ - v ~id:0l ~path:["foo";"bar"] Read; - v ~id:Int32.max_int ~path:[] (Write "foo"); - v ~id:0l ~path:[] Delete; - v ~id:(-3l) ~path:["foo"] Delete; - ] - -let replies = - let open Ctl.Response in - [ - v ~id:0l (Ok ""); - v ~id:Int32.max_int (Ok "foo"); - v ~id:0l (Error ""); - v ~id:(-3l) (Error "foo"); - ] - -let failf fmt = Fmt.kstrf Alcotest.fail fmt - -let test_send t write read message messages = - let calf = Ctl.Endpoint.v @@ calf Init.Pipe.(ctl t) in - let priv = Ctl.Endpoint.v @@ priv Init.Pipe.(ctl t) in - let test m = - write calf m >>= function - | Error e -> failf "Message.write: %a" Ctl.Endpoint.pp_error e - | Ok () -> - read priv >|= function - | Ok m' -> Alcotest.(check message) "write/read" m m' - | Error e -> failf "Message.read: %a" Ctl.Endpoint.pp_error e - in - Lwt_list.iter_s test messages - -let test_request_send t () = - let open Ctl.Request in - test_send t write read request queries - -let test_response_send t () = - let open Ctl.Response in - test_send t write read response replies - let failf fmt = Fmt.kstrf Alcotest.fail fmt (* read ops *) @@ -188,6 +144,7 @@ let delete_should_work t k = | Error e -> failf "write(%a) -> error: %a" pp_path k pp_error e let test_ctl t () = + Lwt_switch.with_switch @@ fun switch -> let calf = calf Init.Pipe.(ctl t) in let priv = priv Init.Pipe.(ctl t) in let k1 = ["foo"; "bar"] in @@ -199,32 +156,30 @@ let test_ctl t () = let git_root = "/tmp/sdk/ctl" in let _ = Sys.command (Fmt.strf "rm -rf %s" git_root) in Ctl.v git_root >>= fun ctl -> - let server () = Ctl.Server.listen ~routes ctl priv in - let client () = - let t = Ctl.Client.v calf in - let allowed k v = - delete_should_work t k >>= fun () -> - read_should_none t k >>= fun () -> - write_should_work t k v >>= fun () -> - read_should_work t k v >>= fun () -> - Ctl.KV.get ctl k >|= fun v' -> - Alcotest.(check string) "in the db" v v' - in - let disallowed k v = - read_should_err t k >>= fun () -> - write_should_err t k v >>= fun () -> - delete_should_err t k - in - allowed k1 "" >>= fun () -> - allowed k2 "xxx" >>= fun () -> - allowed k3 (random_string (255 * 1024)) >>= fun () -> - disallowed k4 "" >>= fun () -> - Lwt.return_unit + let _server = + let service = Ctl.Server.service ~routes ctl in + Capnp_rpc_lwt.CapTP.of_endpoint ~switch ~offer:service (Capnp_rpc_lwt.Endpoint.of_flow ~switch (module IO) priv) in - Lwt.pick [ - client (); - server (); - ] + let client = Capnp_rpc_lwt.CapTP.of_endpoint ~switch (Capnp_rpc_lwt.Endpoint.of_flow ~switch (module IO) calf) in + let t = Capnp_rpc_lwt.CapTP.bootstrap client in + let allowed k v = + delete_should_work t k >>= fun () -> + read_should_none t k >>= fun () -> + write_should_work t k v >>= fun () -> + read_should_work t k v >>= fun () -> + Ctl.KV.get ctl k >|= fun v' -> + Alcotest.(check string) "in the db" v v' + in + let disallowed k v = + read_should_err t k >>= fun () -> + write_should_err t k v >>= fun () -> + delete_should_err t k + in + allowed k1 "" >>= fun () -> + allowed k2 "xxx" >>= fun () -> + allowed k3 (random_string (255 * 1024)) >>= fun () -> + disallowed k4 "" >>= fun () -> + Lwt.return_unit let in_memory_flow () = let flow = Mirage_flow_lwt.F.string () in @@ -268,8 +223,6 @@ let test = [ "stdout is a pipe" , `Quick, run (test_pipe Init.Pipe.(stderr t)); "net is a socket pair", `Quick, run (test_socketpair Init.Pipe.(net t)); "ctl is a socket pair", `Quick, run (test_socketpair Init.Pipe.(ctl t)); - "send requests" , `Quick, run (test_request_send t); - "send responses" , `Quick, run (test_response_send t); "ctl" , `Quick, run (test_ctl t); "exec" , `Quick, run test_exec; ]