More things

This commit is contained in:
Radon Rosborough 2023-08-18 13:41:06 -07:00
parent 029a64cf45
commit 36b8c13cc7
7 changed files with 76 additions and 840 deletions

View File

@ -17,8 +17,8 @@ export function watchPods() {
// paste, and pray.
const informer = k8sClient.makeInformer(
kubeconfig,
"/api/v1/namespaces/riju-user/pods",
() => k8s.listNamespacedPod("riju-user")
"/api/v1/namespaces/user/pods",
() => k8s.listNamespacedPod("user")
);
for (const event of ["add", "update", "delete"]) {
@ -27,7 +27,7 @@ export function watchPods() {
callbacks[pod.metadata.name](event, pod);
}
pods[pod.metadata.name] = pod;
if (event == "delete") {
if (event === "delete") {
delete callbacks[pod.metadata.name];
delete pods[pod.metadata.name];
}
@ -47,8 +47,47 @@ export function watchPods() {
callback("add", pods[podName]);
}
},
podExists: (podName) => {
return podName in pods;
close: () => {
informer.stop();
},
};
}
export function watchConfigMaps() {
const callbacks = {};
const configMaps = {};
const informer = k8sClient.makeInformer(
kubeconfig,
"/api/v1/namespaces/database",
() => k8s.listNamespacedConfigMap("database")
);
for (const event of ["add", "update", "delete"]) {
informer.event(event, (configMap) => {
if (event === "delete") {
// Ignore this explicitly
return;
}
if (configMap.metadata.name in callbacks) {
callbacks[configMap.metadata.name](event, configMap);
}
configMaps[configMaps.metadata.name] = configMap;
});
}
informer.on("error", (err) => {
console.error(err);
setTimeout(() => informer.start(), 5000);
});
informer.start();
return {
setCallback: (configMapName, callback) => {
callbacks[configMapName] = callback;
if (configMapName in configMaps) {
callback("add", configMaps[configMapName]);
}
},
close: () => {
informer.stop();
@ -57,15 +96,19 @@ export function watchPods() {
}
export async function listUserSessions() {
return (await k8s.listNamespacedPod("riju-user")).body.items.map((pod) => ({
return (await k8s.listNamespacedPod("user")).body.items.map((pod) => ({
podName: pod.metadata.name,
sessionID: pod.metadata.labels["riju.codes/user-session-id"],
}));
}
export async function createUserSession({ sessionID, langConfig, revisions }) {
export async function createUserSession({
sessionID,
containerHostname,
langImageTag,
}) {
const pod = (
await k8s.createNamespacedPod("riju-user", {
await k8s.createNamespacedPod("user", {
metadata: {
name: `riju-user-session-${sessionID}`,
labels: {
@ -73,52 +116,16 @@ export async function createUserSession({ sessionID, langConfig, revisions }) {
},
},
spec: {
hostname: langConfig.id,
volumes: [
{
name: "minio-config",
secret: {
secretName: "minio-user-login",
},
},
{
name: "riju-bin",
emptyDir: {},
},
],
hostname: containerHostname,
imagePullSecrets: [
{
name: "registry-user-login",
},
],
initContainers: [
{
name: "download",
image: "minio/mc:RELEASE.2022-12-13T00-23-28Z",
resources: {},
command: ["sh", "-c"],
args: [
`mkdir -p /root/.mc && cp -LT /mc/config.json /root/.mc/config.json &&` +
`mc cp riju/agent/${revisions.agent} /riju-bin/agent && chmod +x /riju-bin/agent &&` +
`mc cp riju/ptyify/${revisions.ptyify} /riju-bin/ptyify && chmod +x /riju-bin/ptyify`,
],
volumeMounts: [
{
name: "minio-config",
mountPath: "/mc",
readOnly: true,
},
{
name: "riju-bin",
mountPath: "/riju-bin",
},
],
},
],
containers: [
{
name: "session",
image: `localhost:30999/riju-lang:${langConfig.id}-${revisions.langImage}`,
image: `localhost:30999/riju-lang:${langImageTag}`,
resources: {
requests: {},
limits: {
@ -126,7 +133,6 @@ export async function createUserSession({ sessionID, langConfig, revisions }) {
memory: "4Gi",
},
},
command: ["/riju-bin/agent"],
env: [
// For agent
{
@ -385,6 +391,6 @@ export async function initUserSession({ watcher, podName, proxyInfo }) {
export async function deleteUserSessions(sessionsToDelete) {
for (const { podName } of sessionsToDelete) {
await k8s.deleteNamespacedPod(podName, "riju-user");
await k8s.deleteNamespacedPod(podName, "user");
}
}

16
backend/session-base.js Normal file
View File

@ -0,0 +1,16 @@
import * as k8s from "./k8s.js";
export class SessionManager {
constructor() {
this.podWatcher = k8s.watchPods();
this.configMapWatcher = k8s.watchConfigMaps();
}
getSession({ sessionID, langID }) {
const session = k8s.createUserSession({ sessionID });
}
}
export class SessionBase {
//
}

View File

@ -16,6 +16,12 @@ apiVersion: v1
metadata:
name: docker-registry
---
kind: Namespace
apiVersion: v1
metadata:
name: database
---
kind: Namespace
apiVersion: v1

View File

@ -1,90 +0,0 @@
import crypto from "crypto";
import child_process from "child_process";
import { promises as fs } from "fs";
import path from "path";
import util from "util";
import { parse } from "@babel/parser";
import { simple as babelWalk } from "babel-walk";
import { readLangConfig } from "./yaml.js";
async function getRelativeImports(filename) {
const relativeImports = [];
const program = parse(await fs.readFile(filename, "utf-8"), {
sourceType: "module",
plugins: ["classProperties"],
});
babelWalk({
ImportDeclaration: (node) => {
if (node.source.type !== "StringLiteral") {
throw new Error(`unsupported import syntax:`, node);
}
const source = node.source.value;
if (!source.startsWith(".")) {
return;
}
relativeImports.push(source);
},
})(program);
return relativeImports;
}
function pathRelativeTo(relativePath, relativeTo) {
return path.join(path.dirname(path.resolve(relativeTo)), relativePath);
}
async function getTransitiveRelativeImports(filename) {
let queue = [filename];
const found = new Set();
while (queue.length > 0) {
const filename = path.resolve(queue.pop());
if (found.has(filename)) {
continue;
}
found.add(filename);
queue = queue.concat(
(await getRelativeImports(filename)).map((result) =>
pathRelativeTo(result, filename)
)
);
}
return [...found];
}
async function getTestRunnerHash() {
const files = await getTransitiveRelativeImports("backend/test-runner.js");
files.push("package.json");
files.push("yarn.lock");
files.push("system/src/riju-system-privileged.c");
const hashes = [];
for (const file of files) {
hashes.push(
crypto
.createHash("sha1")
.update(await fs.readFile(file, "utf-8"))
.digest("hex")
);
}
return crypto.createHash("sha1").update(hashes.join(",")).digest("hex");
}
const testRunnerHash = getTestRunnerHash();
async function getTestConfigHash(lang) {
const config = Object.assign({}, await readLangConfig(lang));
delete config["install"];
delete config["info"];
return crypto.createHash("sha1").update(JSON.stringify(config)).digest("hex");
}
export async function getTestHash(lang, runtimeImageHash, langImageHash) {
return crypto
.createHash("sha1")
.update(
`${await testRunnerHash},${await getTestConfigHash(
lang
)},${runtimeImageHash},${langImageHash}`
)
.digest("hex");
}

View File

@ -15,16 +15,8 @@ function verbosely {
mkdir -p system/out
rm -f system/out/*
pushd system/res >/dev/null
verbosely xxd -i sentinel.bash > ../src/sentinel.h
popd >/dev/null
for src in system/src/*.c; do
out="${src/src/out}"
out="${out/.c}"
verbosely clang -Isystem/res -Wall -Wextra -Werror -std=c11 "${src}" -o "${out}"
if [[ "${out}" == *-privileged && -z "${UNPRIVILEGED:-}" ]]; then
verbosely sudo chown root:riju "${out}"
verbosely sudo chmod a=,g=rx,u=rwxs "${out}"
fi
done

View File

@ -1,60 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
umask 077
while read -t2 -r cmdline; do
cmd=(${cmdline})
for (( i=0; i<${#cmd[@]}; i++ )); do
arg="${cmd[$i]}"
arg="${arg}x"
arg="$(sed 's/+s/ /g' <<< "${arg}")"
arg="$(sed 's/+n/\n/g' <<< "${arg}")"
arg="$(sed 's/+t/\t/g' <<< "${arg}")"
arg="$(sed 's/+p/+/g' <<< "${arg}")"
arg="${arg%x}"
cmd[$i]="${arg}"
done
if (( "${#cmd[@]}" > 0 )); then
case "${cmd[0]}" in
ping) ;;
exec|pty)
if (( "${#cmd[@]}" < 3 )); then
echo >&2 "usage: (exec|pty) UUID ARG..."
else
if [[ "${cmd[0]}" == pty ]]; then
maybe_pty=/var/cache/riju/share/riju-pty
else
maybe_pty=
fi
uuid="${cmd[1]}"
args=("${cmd[@]:2}")
stdin="/var/cache/riju/share/cmd-${uuid}-stdin"
stdout="/var/cache/riju/share/cmd-${uuid}-stdout"
stderr="/var/cache/riju/share/cmd-${uuid}-stderr"
status="/var/cache/riju/share/cmd-${uuid}-status"
live="/var/cache/riju/share/cmd-${uuid}-live"
mkfifo "${stdin}" "${stdout}" "${stderr}" "${status}" "${live}"
(
set +e
(
runuser -u riju -- bash -c "exec ${maybe_pty:-} \"\$@\"" -- "${args[@]}" < "${stdin}" > "${stdout}" 2> "${stderr}"
echo "$?"
) > "${status}"
) &
bg=$!
while kill -0 "$bg" 2>/dev/null; do
sleep 1
echo "ping" 2>/dev/null || break
done > "${live}" &
fi
;;
*)
echo >&2 "unrecognized command: ${cmd[0]}"
;;
esac
fi
done < /var/cache/riju/share/control

View File

@ -1,634 +0,0 @@
#include <linux/prctl.h>
#define _GNU_SOURCE
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/random.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "sentinel.h"
void __attribute__((noreturn)) die(char *msg)
{
fprintf(stderr, "%s (errno %d)\n", msg, errno);
exit(1);
}
void init() { sentinel_bash[sentinel_bash_len - 1] = '\0'; }
void die_with_usage()
{
die("usage:\n"
" riju-system-privileged list\n"
" riju-system-privileged pull REPO TAG\n"
" riju-system-privileged session UUID LANG [IMAGE-HASH]\n"
" riju-system-privileged exec UUID CMDLINE...\n"
" riju-system-privileged pty UUID CMDLINE...\n"
" riju-system-privileged teardown [UUID]");
}
char *quoteArgs(int argc, char **cmdline)
{
int orig_len = 0;
for (int i = 0; i < argc; ++i)
orig_len += strlen(cmdline[i]);
int quoted_len = orig_len * 2 + argc;
char *quoted = malloc(sizeof(char) * quoted_len);
char *quoted_ptr = quoted;
for (int i = 0; i < argc; ++i) {
for (char *ptr = cmdline[i]; *ptr != '\0'; ++ptr) {
if (*ptr == ' ') {
*(quoted_ptr++) = '+';
*(quoted_ptr++) = 's';
} else if (*ptr == '\n') {
*(quoted_ptr++) = '+';
*(quoted_ptr++) = 'n';
} else if (*ptr == '\t') {
*(quoted_ptr++) = '+';
*(quoted_ptr++) = 't';
} else if (*ptr == '+') {
*(quoted_ptr++) = '+';
*(quoted_ptr++) = 'p';
} else if (isprint(*ptr)) {
*(quoted_ptr++) = *ptr;
} else {
die("riju-system-privileged got non-printable char");
}
}
if (i < argc - 1)
*(quoted_ptr++) = ' ';
}
*(quoted_ptr++) = '\0';
return quoted;
}
char *getUUID()
{
char *buf = malloc(16);
if (buf == NULL)
die("malloc failed");
if (getrandom(buf, 16, 0) != 16)
die("getrandom failed");
char *uuid;
if (asprintf(&uuid,
"%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%"
"02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14],
buf[15]) < 0)
die("asprintf failed");
return uuid;
}
char *parseUUID(char *uuid)
{
if (strnlen(uuid, 33) != 32)
die("illegal uuid");
for (char *ptr = uuid; *ptr; ++ptr)
if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9')))
die("illegal uuid");
return uuid;
}
char *parseLang(char *lang)
{
size_t len = strnlen(lang, 65);
if (len == 0 || len > 64)
die("illegal language name");
return lang;
}
char *parseImageHash(char *imageHash)
{
if (strnlen(imageHash, 41) != 40)
die("illegal imageHash");
for (char *ptr = imageHash; *ptr; ++ptr)
if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9')))
die("illegal imageHash");
return imageHash;
}
char *parseRepo(char *repo)
{
if (strnlen(repo, 501) > 500)
die("illegal repo name");
for (char *ptr = repo; *ptr; ++ptr)
if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') ||
*ptr == '/' || *ptr == '.' || *ptr == '-' || *ptr == '_'))
die("illegal repo name");
return repo;
}
char *parseTag(char *tag)
{
if (strnlen(tag, 501) > 500)
die("illegal tag name");
for (char *ptr = tag; *ptr; ++ptr)
if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') ||
*ptr == '.' || *ptr == '-' || *ptr == '_'))
die("illegal tag name");
return tag;
}
char *timeout_msg;
void sigalrm_die(int signum)
{
(void)signum;
die(timeout_msg);
}
void sigalrm_kill_parent(int signum)
{
(void)signum;
kill(getppid(), SIGTERM);
exit(EXIT_FAILURE);
}
void cmd_list()
{
// This command prints a bunch of empty lines because there is no
// way to filter to a desired set of images. Caller is expected to
// remove empty lines because it's easier in JS than C.
char *argv[] = {
"docker",
"image",
"ls",
"--format",
"{{ if eq .Repository \"riju\" }}{{ .Tag }}{{ end }}",
NULL,
};
execvp(argv[0], argv);
die("execvp failed");
}
void cmd_pull(char *repo, char *tag)
{
char *localImage, *remoteImage;
if (asprintf(&remoteImage, "%s:%s", repo, tag) < 0)
die("asprintf failed");
if (asprintf(&localImage, "riju:%s", tag) < 0)
die("asprintf failed");
pid_t orig_ppid = getpid();
pid_t pid = fork();
if (pid < 0)
die("fork failed");
else if (pid == 0) {
if (freopen("/dev/null", "w", stdout) == NULL)
die("freopen failed");
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
die("prctl failed");
if (getppid() != orig_ppid)
exit(EXIT_FAILURE);
char *argv[] = {
"docker", "inspect", "--", localImage, NULL,
};
execvp(argv[0], argv);
die("execvp failed");
}
siginfo_t info;
if (waitid(P_PID, pid, &info, WEXITED) < 0)
die("waitid failed");
if (info.si_status == 0) {
// Image exists already, no need to pull. It is only appropriate
// to use cmd_pull with immutable images.
return;
}
orig_ppid = getpid();
pid = fork();
if (pid < 0)
die("fork failed");
else if (pid == 0) {
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
die("prctl failed");
if (getppid() != orig_ppid)
exit(EXIT_FAILURE);
char *argv[] = {
"docker", "pull", "--", remoteImage, NULL,
};
execvp(argv[0], argv);
}
if (waitid(P_PID, pid, &info, WEXITED) < 0)
die("waitid failed");
if (info.si_status != 0)
die("child process failed");
char *argv[] = {
"docker", "tag", "--", remoteImage, localImage,
};
execvp(argv[0], argv);
die("execvp failed");
}
void cmd_session(char *uuid, char *lang, char *imageHash)
{
if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
die("setvbuf failed");
char *image, *container, *hostname, *share, *volume, *fifo, *rijuPtyPath,
*sessionLabel;
if (asprintf(&sessionLabel, "riju.user-session=%s", uuid) < 0)
die("asprintf failed");
if ((imageHash != NULL ? asprintf(&image, "riju:lang-%s-%s", lang, imageHash)
: asprintf(&image, "riju:lang-%s", lang)) < 0)
die("asprintf failed");
if (asprintf(&container, "riju-session-%s", uuid) < 0)
die("asprintf failed");
if (asprintf(&hostname, "HOSTNAME=%s", lang) < 0)
die("asprintf failed");
if (asprintf(&share, "/var/cache/riju/shares/%s", uuid) < 0)
die("asprintf failed");
int rv = mkdir("/var/cache/riju/shares", 0700);
if (rv < 0 && errno != EEXIST)
die("mkdir failed");
rv = mkdir(share, 0755);
if (rv < 0)
die("mkdir failed");
if (asprintf(&rijuPtyPath, "%s/riju-pty", share) < 0)
die("asprintf failed");
int fdFrom = open("/src/system/out/riju-pty", O_RDONLY);
if (fdFrom < 0)
die("open failed");
int fdTo = open(rijuPtyPath, O_WRONLY | O_CREAT | O_EXCL, 0755);
if (fdTo < 0)
die("open failed");
char buf[1024];
int len, len_written;
while ((len = read(fdFrom, buf, 1024)) > 0) {
char *ptr = buf;
while (len > 0) {
len_written = write(fdTo, ptr, len);
if (len_written < 0)
die("write failed");
len -= len_written;
ptr += len_written;
}
}
if (close(fdFrom) < 0)
die("close failed");
if (close(fdTo) < 0)
die("close failed");
if (len < 0)
die("read failed");
if (asprintf(&volume, "%s:/var/cache/riju/share", share) < 0)
die("asprintf failed");
if (asprintf(&fifo, "%s/control", share) < 0)
die("asprintf failed");
if (mknod(fifo, 0600 | S_IFIFO, 0) < 0)
die("mknod failed");
pid_t orig_ppid = getpid();
pid_t pid = fork();
if (pid < 0)
die("fork failed");
else if (pid == 0) {
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
die("prctl failed");
if (getppid() != orig_ppid)
exit(EXIT_FAILURE);
char *argv[] = {
"docker",
"run",
"--rm",
"-v",
volume,
"-e",
"HOME=/home/riju",
"-e",
hostname,
"-e",
"LANG=C.UTF-8",
"-e",
"LC_ALL=C.UTF-8",
"-e",
"LOGNAME=riju",
"-e",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin",
"-e",
"PWD=/home/riju/src",
"-e",
"SHELL=/usr/bin/bash",
"-e",
"TERM=xterm-256color",
"-e",
"TMPDIR=/tmp",
"-e",
"USER=riju",
"-e",
"USERNAME=riju",
"--user",
"root",
"--hostname",
lang,
"--name",
container,
"--cpus",
"0.6",
"--memory",
"1g",
"--pids-limit",
"4000",
"--cgroup-parent",
"riju.slice",
// Deny access to outside networking for now in order to limit
// abuse, as we've received abuse reports from AWS. We should
// be able to remove this (and indeed we'll *want* to, in
// order to support package installation) by replacing it with
// a more fine-grained network control such as limiting
// outbound bandwidth.
"--network=none",
"--label",
"riju.category=user-session",
"--label",
sessionLabel,
image,
"bash",
"-c",
(char *)sentinel_bash,
NULL,
};
execvp(argv[0], argv);
die("execvp failed");
}
struct timespec ts_10ms;
ts_10ms.tv_sec = 0;
ts_10ms.tv_nsec = 1000 * 1000 * 10;
timeout_msg = "container did not come up within 10 seconds";
if (signal(SIGALRM, sigalrm_die) == SIG_ERR)
die("signal failed");
alarm(10);
int fd;
while (1) {
fd = open(fifo, O_WRONLY);
if (fd >= 0)
break;
if (errno != ENXIO)
die("open failed");
int rv = nanosleep(&ts_10ms, NULL);
if (rv != 0 && errno != EINTR)
die("nanosleep failed");
}
if (signal(SIGALRM, SIG_IGN) == SIG_ERR)
die("signal failed");
printf("riju: container ready\n"); // magic string
struct timespec ts_1s;
ts_1s.tv_sec = 1;
ts_1s.tv_nsec = 0;
while (1) {
static const char ok[] = "ping\n";
if (write(fd, ok, sizeof(ok) / sizeof(char)) != sizeof(ok) / sizeof(char))
die("write failed");
int rv = nanosleep(&ts_1s, NULL);
if (rv != 0 && errno != EINTR)
die("nanosleep failed");
}
}
void cmd_exec(char *uuid, int argc, char **cmdline, bool pty)
{
if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
die("setvbuf failed");
char *share, *ctlFIFO, *stdinFIFO, *stdoutFIFO, *stderrFIFO, *statusFIFO,
*liveFIFO, *ctlCmd, *dataFIFO;
if (asprintf(&share, "/var/cache/riju/shares/%s", uuid) < 0)
die("asprintf failed");
if (asprintf(&ctlFIFO, "%s/control", share) < 0)
die("asprintf failed");
char *procUUID = getUUID();
if (asprintf(&stdinFIFO, "%s/cmd-%s-stdin", share, procUUID) < 0)
die("asprintf failed");
if (asprintf(&stdoutFIFO, "%s/cmd-%s-stdout", share, procUUID) < 0)
die("asprintf failed");
if (asprintf(&stderrFIFO, "%s/cmd-%s-stderr", share, procUUID) < 0)
die("asprintf failed");
if (asprintf(&statusFIFO, "%s/cmd-%s-status", share, procUUID) < 0)
die("asprintf failed");
if (asprintf(&liveFIFO, "%s/cmd-%s-live", share, procUUID) < 0)
die("asprintf failed");
int fd = open(ctlFIFO, O_WRONLY);
if (fd < 0)
die("open failed");
char *quotedArgs = quoteArgs(argc, cmdline);
int len = asprintf(&ctlCmd, "%s %s %s\n", pty ? "pty" : "exec", procUUID,
quotedArgs);
if (len < 0)
die("asprintf failed");
int len_written;
while ((len_written = write(fd, ctlCmd, len)) > 0) {
ctlCmd += len_written;
len -= len_written;
}
if (len_written < 0)
die("write failed");
close(fd);
timeout_msg = "sentinel did not set up FIFOs within 1 second";
struct timespec ts_10ms;
ts_10ms.tv_sec = 0;
ts_10ms.tv_nsec = 1000 * 1000 * 10;
pid_t orig_ppid = getpid();
pid_t pid = fork();
if (pid < 0)
die("fork failed");
else if (pid == 0) {
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
die("prctl failed");
if (getppid() != orig_ppid)
exit(EXIT_FAILURE);
dataFIFO = stdinFIFO;
} else {
pid = fork();
if (pid < 0)
die("fork failed");
else if (pid == 0) {
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
die("prctl failed");
if (getppid() != orig_ppid)
exit(EXIT_FAILURE);
dataFIFO = stdoutFIFO;
} else {
pid = fork();
if (pid < 0)
die("fork failed");
else if (pid == 0) {
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
die("prctl failed");
if (getppid() != orig_ppid)
exit(EXIT_FAILURE);
dataFIFO = stderrFIFO;
} else {
pid = fork();
if (pid < 0)
die("fork failed");
else if (pid == 0) {
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
die("prctl failed");
if (getppid() != orig_ppid)
exit(EXIT_FAILURE);
dataFIFO = liveFIFO;
} else {
dataFIFO = statusFIFO;
}
}
}
}
if (signal(SIGALRM, sigalrm_die) == SIG_ERR)
die("signal failed");
alarm(1);
while (1) {
int mode = dataFIFO == stdinFIFO ? O_WRONLY : O_RDONLY;
fd = open(dataFIFO, mode);
if (fd >= 0)
break;
if (errno != ENOENT)
die("open failed");
int rv = nanosleep(&ts_10ms, NULL);
if (rv != 0 && errno != EINTR)
die("nanosleep failed");
}
if (signal(SIGALRM, SIG_IGN) == SIG_ERR)
die("signal failed");
char buf[1024];
if (dataFIFO == stdinFIFO) {
if (close(STDOUT_FILENO) < 0)
die("close failed");
while ((len = read(STDIN_FILENO, buf, 1024)) > 0) {
char *ptr = buf;
while (len > 0) {
len_written = write(fd, ptr, len);
if (len_written < 0)
die("write failed");
len -= len_written;
ptr += len_written;
}
}
if (len < 0)
die("read failed");
} else if (dataFIFO == stdoutFIFO || dataFIFO == stderrFIFO) {
FILE *output = dataFIFO == stdoutFIFO ? stdout : stderr;
if (close(STDIN_FILENO) < 0)
die("close failed");
while ((len = read(fd, buf, 1024)) > 0) {
fwrite(buf, 1, len, output);
if (ferror(output))
die("fwrite failed");
if (feof(stdout))
break;
}
if (len < 0)
die("read failed");
} else if (dataFIFO == statusFIFO) {
if (close(STDIN_FILENO) < 0)
die("close failed");
if (close(STDOUT_FILENO) < 0)
die("close failed");
char line[1024];
char *ptr = line;
int len;
while ((len = read(fd, ptr, 1023 - (ptr - line))) > 0) {
ptr += len;
}
if (len < 0)
die("read failed");
*ptr = '\0';
char *endptr;
long status = strtol(line, &endptr, 10);
if (*endptr != '\n')
die("strtol failed");
exit(status);
} else if (dataFIFO == liveFIFO) {
char line[1024];
int len;
timeout_msg = "container died";
if (signal(SIGALRM, sigalrm_kill_parent) < 0)
die("signal failed");
if (alarm(2) < 0)
die("alarm failed");
while ((len = read(fd, line, 1024)) > 0) {
if (alarm(2) < 0)
die("alarm failed");
}
if (len < 0)
die("read failed");
}
}
void cmd_teardown(char *uuid)
{
if (setuid(0) != 0)
die("setuid failed");
char *cmdline;
if (uuid != NULL) {
if (asprintf(&cmdline, "rm -rf /var/cache/riju/shares/%s", uuid) < 0)
die("asprintf failed");
} else {
cmdline =
"comm -23 <(sudo ls /var/cache/riju/shares | sort) <(docker ps "
"-f label=riju.category=user-session --format \"{{ .Labels }}\" "
"| grep -Eo 'riju\\.user-session=[a-z0-9]+' | sed -E "
"'s/^[^=]+=//' | sort) | (cd /var/cache/riju/shares 2>/dev/null && "
"xargs rm -rf || :)";
}
char *argv[] = {"bash", "-c", cmdline, NULL};
execvp(argv[0], argv);
die("execvp failed");
}
int main(int argc, char **argv)
{
init();
if (seteuid(0) != 0)
die("seteuid failed");
if (argc < 2)
die_with_usage();
if (!strcmp(argv[1], "list")) {
if (argc != 2)
die_with_usage();
cmd_list();
return 0;
}
if (!strcmp(argv[1], "pull")) {
if (argc != 4)
die_with_usage();
char *repo = parseRepo(argv[2]);
char *tag = parseTag(argv[3]);
cmd_pull(repo, tag);
return 0;
}
if (!strcmp(argv[1], "session")) {
if (argc < 4 || argc > 5)
die_with_usage();
char *uuid = parseUUID(argv[2]);
char *lang = parseLang(argv[3]);
char *imageHash = argc == 5 ? parseImageHash(argv[4]) : NULL;
cmd_session(uuid, lang, imageHash);
return 0;
}
if (!strcmp(argv[1], "exec")) {
if (argc < 4)
die_with_usage();
cmd_exec(parseUUID(argv[2]), argc - 3, &argv[3], false);
return 0;
}
if (!strcmp(argv[1], "pty")) {
if (argc < 4)
die_with_usage();
cmd_exec(parseUUID(argv[2]), argc - 3, &argv[3], true);
return 0;
}
if (!strcmp(argv[1], "teardown")) {
if (argc < 2)
die_with_usage();
cmd_teardown(argc >= 3 ? parseUUID(argv[2]) : NULL);
return 0;
}
die_with_usage();
}