More things
This commit is contained in:
parent
029a64cf45
commit
36b8c13cc7
102
backend/k8s.js
102
backend/k8s.js
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
//
|
||||
}
|
|
@ -16,6 +16,12 @@ apiVersion: v1
|
|||
metadata:
|
||||
name: docker-registry
|
||||
|
||||
---
|
||||
kind: Namespace
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: database
|
||||
|
||||
---
|
||||
kind: Namespace
|
||||
apiVersion: v1
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue