mirror of
https://github.com/containers/youki
synced 2024-04-20 00:54:12 +02:00
first commit!
This commit is contained in:
commit
a08887d59a
|
@ -0,0 +1,2 @@
|
|||
[build]
|
||||
target = "x86_64-unknown-linux-gnu"
|
|
@ -0,0 +1,13 @@
|
|||
FROM mcr.microsoft.com/vscode/devcontainers/rust:1
|
||||
|
||||
WORKDIR /workspaces
|
||||
RUN curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
|
||||
RUN rustup component add rust-src clippy
|
||||
RUN curl -L https://github.com/rust-analyzer/rust-analyzer/releases/latest/download/rust-analyzer-linux -o /usr/local/cargo/bin/rust-analyzer && chmod +x /usr/local/cargo/bin/rust-analyzer
|
||||
RUN wget https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.13.5.linux-amd64.tar.gz
|
||||
RUN mkdir /workspaces/go
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
ENV GOPATH /workspaces/go
|
||||
ENV YOUKI_LOGLEVEL debug
|
||||
ENV RUNTIME /workspaces/youki/target/x86_64-unknown-linux-gnu/debug/youki
|
||||
ENTRYPOINT ["sh"]
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "youki",
|
||||
"dockerFile": "Dockerfile",
|
||||
"overrideCommand": true,
|
||||
"mounts": [
|
||||
"source=/var/run/docker.sock,target=/var/run/docker-host.sock,type=bind"
|
||||
],
|
||||
"runArgs": [
|
||||
"--cap-add=SYS_PTRACE",
|
||||
"--security-opt",
|
||||
"seccomp=unconfined",
|
||||
"--privileged"
|
||||
],
|
||||
"postStartCommand": [
|
||||
".devcontainer/scripts/init.sh"
|
||||
],
|
||||
"extensions": [
|
||||
"matklad.rust-analyzer"
|
||||
],
|
||||
"settings": {
|
||||
"rust-analyzer.server.path": "/usr/local/cargo/bin/rust-analyzer",
|
||||
"rust-analyzer.checkOnSave.command": "clippy",
|
||||
// ref. https://github.com/rust-analyzer/rust-analyzer/issues/6038
|
||||
"rust-analyzer.diagnostics.disabled": [
|
||||
"unresolved-import"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
RUST_BACKTRACE=full YOUKI_LOG_LEVEL=debug YOUKI_MODE=/var/lib/docker/containers/ dockerd --experimental --add-runtime="youki=/workspaces/youki/target/x86_64-unknown-linux-gnu/debug/youki" &
|
||||
cargo build
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
go get github.com/opencontainers/runtime-tools
|
||||
cd /workspaces/go/src/github.com/opencontainers/runtime-tools
|
||||
make runtimetest validation-executables
|
||||
if [ ! -e /workspaces/runtime-tools ]; then
|
||||
ln -s /workspaces/go/src/github.com/opencontainers/runtime-tools /workspaces
|
||||
fi
|
||||
# YOUKI_LOGLEVEL=debug RUNTIME=/workspaces/youki/target/x86_64-unknown-linux-gnu/debug/youki validation/kill/kill.t
|
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
|
||||
test_cases=("default/default.t" "create/create.t" "start/start.t")
|
||||
# Testing delete and kill is too time consuming.
|
||||
# test_cases=("default/default.t" "create/create.t" "start/start.t" "delete/delete.t" "kill/kill.t")
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
COLUMNS=$(tput cols)
|
||||
expect_err_num=116
|
||||
act_err_num=0
|
||||
|
||||
for case in "${test_cases[@]}"; do
|
||||
title="Running $case"
|
||||
printf "\n%*s\n" $(((${#title}+$COLUMNS)/2)) "$title"
|
||||
IFS=$'\n' errors=($(RUST_BACKTRACE=1 cd /workspaces/runtime-tools && ../runtime-tools/validation/$case | grep "not ok"))
|
||||
|
||||
if [ ${#errors[@]} -eq 0 ]; then
|
||||
echo -e "${GREEN}Passed all tess${NC}"
|
||||
else
|
||||
for err in "${errors[@]}"; do
|
||||
act_err_num=$((++act_err_num))
|
||||
echo $err
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
if [ $act_err_num -ne $expect_err_num ]; then
|
||||
echo -e "${RED}The number of failures was as unexpected.${NC}"
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
.vagrant/
|
|
@ -0,0 +1,647 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.0-beta.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.0.0-beta.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crc32fast",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"proc-macro-hack",
|
||||
"proc-macro-nested",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ad167a2f54e832b82dbe003a046280dceffe5227b5f79e08e363a29638cfddd"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "prctl"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "059a34f111a9dee2ce1ac2826a68b24601c4298cfeb1a587c3cb493d5ab46f52"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"nix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-nested"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "procfs"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab8809e0c18450a2db0f236d2a44ec0b4c1412d0eb936233579f0990faa5d5cd"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"flate2",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "youki"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"futures",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"prctl",
|
||||
"procfs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "youki"
|
||||
version = "0.0.1"
|
||||
authors = ["utam0k <k0ma@utam0k.jp>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
clap = "3.0.0-beta.2"
|
||||
nix = "0.19.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
prctl = "1.0.0"
|
||||
libc = "0.2.84"
|
||||
log = "0.4"
|
||||
anyhow = "1.0"
|
||||
mio = { version = "0.7", features = ["os-ext", "os-poll"] }
|
||||
chrono = "0.4"
|
||||
once_cell = "1.6.0"
|
||||
procfs = "0.9.1"
|
|
@ -0,0 +1,70 @@
|
|||
<h1 align="center">youki</h1>
|
||||
<h3 align="center">Rust experimental implementation of the oci-runtime</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="LICENSE">
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Overview
|
||||
youki is an implementation of [runtime-spec](https://github.com/opencontainers/runtime-spec) in Rust, referring to [runc](https://github.com/opencontainers/runc).
|
||||
This project is in the experimental stage at this point.
|
||||
I think Rust is one of the best languages to implement oci-runtime, so I'm having fun experimenting with it.
|
||||
|
||||
## Try and play
|
||||
We prepared [devcontainer](https://code.visualstudio.com/docs/remote/containers) as a development environment.
|
||||
The following explanation assumes that devcontainer is used.
|
||||
At this stage, it sometimes fails to start the container, but don't worry about it, just retry.
|
||||
The first time it starts up will take a while, so have a cup of coffee and wait ;)
|
||||
|
||||
### Requires
|
||||
- vscode
|
||||
- docker
|
||||
|
||||
### youki with Docker
|
||||
Run the following command in a terminal inside devcontainer.
|
||||
`dockerd` is already running when you start devcontainer.
|
||||
See `.devcontainer/scripts/init.sh` for details.
|
||||
```
|
||||
$ docker run -it --rm --runtime youki hello-world
|
||||
$ docker run -it --rm --runtime youki busybox
|
||||
```
|
||||
|
||||
### Integration test
|
||||
```
|
||||
$ /workspaces/youki/.devcontainer/scripts/setup_test.sh # only the first time
|
||||
$ /workspaces/youki/.devcontainer/scripts/test.sh
|
||||
```
|
||||
|
||||
### HelloWorld with youki
|
||||
Do `Hello, World` using the log function of Youki.
|
||||
If you want to explore youki, please use it.
|
||||
Try adding the following code to the line in `src/main.rs` after initializing the logger of the main function and try to `cargo build` in your terminal.
|
||||
```
|
||||
log::debug!("Hello, World");
|
||||
```
|
||||
|
||||
When you run busybox, sh will start and stop.
|
||||
```
|
||||
$ docker run -it --rm --runtime youki --name youki busybox
|
||||
```
|
||||
|
||||
If you run the following command in a different terminal, you will see the `Hello, World` that you added above.
|
||||
```
|
||||
$ docker logs youki
|
||||
```
|
||||
|
||||
|
||||
## Features
|
||||
- [x] somehow works
|
||||
- [x] run with docker
|
||||
- [x] namespace
|
||||
- [ ] rlimit
|
||||
- [ ] cgroup
|
||||
- [ ] hook
|
||||
|
||||
## Contribution
|
||||
This project welcomes your PR and issues.
|
||||
For example, refactoring, adding features, correcting English, etc.
|
||||
If you need any help, you can contact me on [Twitter](https://twitter.com/utam0k).
|
|
@ -0,0 +1,30 @@
|
|||
use std::os::unix::io::RawFd;
|
||||
|
||||
use anyhow::Result;
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::unistd::{close, pipe2, read};
|
||||
|
||||
pub struct Cond {
|
||||
rfd: RawFd,
|
||||
wfd: RawFd,
|
||||
}
|
||||
|
||||
impl Cond {
|
||||
pub fn new() -> Result<Cond> {
|
||||
let (rfd, wfd) = pipe2(OFlag::O_CLOEXEC)?;
|
||||
Ok(Cond { rfd, wfd })
|
||||
}
|
||||
|
||||
pub fn wait(&self) -> Result<()> {
|
||||
close(self.wfd)?;
|
||||
let data: &mut [u8] = &mut [0];
|
||||
while read(self.rfd, data)? != 0 {}
|
||||
close(self.rfd)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn notify(&self) -> Result<()> {
|
||||
close(self.rfd)?;
|
||||
close(self.wfd)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use nix::unistd::Pid;
|
||||
use procfs::process::Process;
|
||||
|
||||
use crate::container::{ContainerStatus, State};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Container {
|
||||
pub state: State,
|
||||
pub root: PathBuf,
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn new(
|
||||
container_id: &str,
|
||||
status: ContainerStatus,
|
||||
pid: Option<i32>,
|
||||
bundle: &str,
|
||||
container_root: &PathBuf,
|
||||
) -> Result<Self> {
|
||||
let container_root = fs::canonicalize(container_root)?;
|
||||
let state = State::new(container_id, status, pid, bundle);
|
||||
Ok(Self {
|
||||
state,
|
||||
root: container_root,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
self.state.id.as_str()
|
||||
}
|
||||
|
||||
pub fn status(&self) -> ContainerStatus {
|
||||
self.state.status
|
||||
}
|
||||
|
||||
pub fn refresh_status(&self) -> Result<Self> {
|
||||
let new_status = match self.pid() {
|
||||
Some(pid) => {
|
||||
if let Ok(proc) = Process::new(pid.as_raw()) {
|
||||
use procfs::process::ProcState;
|
||||
match proc.stat.state().unwrap() {
|
||||
ProcState::Zombie | ProcState::Dead => ContainerStatus::Stopped,
|
||||
_ => match self.status() {
|
||||
ContainerStatus::Creating | ContainerStatus::Created => self.status(),
|
||||
_ => ContainerStatus::Running,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
ContainerStatus::Stopped
|
||||
}
|
||||
}
|
||||
None => ContainerStatus::Stopped,
|
||||
};
|
||||
self.update_status(new_status)
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
log::debug!("Sava container status: {:?} in {:?}", self, self.root);
|
||||
self.state.save(&self.root)
|
||||
}
|
||||
|
||||
pub fn can_start(&self) -> bool {
|
||||
self.state.status.can_start()
|
||||
}
|
||||
|
||||
pub fn can_kill(&self) -> bool {
|
||||
self.state.status.can_kill()
|
||||
}
|
||||
|
||||
pub fn can_delete(&self) -> bool {
|
||||
self.state.status.can_delete()
|
||||
}
|
||||
|
||||
pub fn pid(&self) -> Option<Pid> {
|
||||
self.state.pid.map(Pid::from_raw)
|
||||
}
|
||||
|
||||
pub fn set_pid(&self, pid: i32) -> Self {
|
||||
Self::new(
|
||||
self.state.id.as_str(),
|
||||
self.state.status,
|
||||
Some(pid),
|
||||
self.state.bundle.as_str(),
|
||||
&self.root,
|
||||
)
|
||||
.expect("unexpected error")
|
||||
}
|
||||
|
||||
pub fn update_status(&self, status: ContainerStatus) -> Result<Self> {
|
||||
Self::new(
|
||||
self.state.id.as_str(),
|
||||
status,
|
||||
self.state.pid,
|
||||
self.state.bundle.as_str(),
|
||||
&self.root,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load(container_root: PathBuf) -> Result<Self> {
|
||||
let state = State::load(&container_root)?;
|
||||
Ok(Self {
|
||||
state,
|
||||
root: container_root,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#[allow(clippy::module_inception)]
|
||||
mod container;
|
||||
mod state;
|
||||
pub use container::Container;
|
||||
pub use state::{ContainerStatus, State};
|
|
@ -0,0 +1,97 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::{fs::File, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const STATE_FILE_PATH: &str = "state.json";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ContainerStatus {
|
||||
// StateCreating indicates that the container is being created
|
||||
Creating,
|
||||
// StateCreated indicates that the runtime has finished the create operation
|
||||
Created,
|
||||
// StateRunning indicates that the container process has executed the
|
||||
// user-specified program but has not exited
|
||||
Running,
|
||||
// StateStopped indicates that the container process has exited
|
||||
Stopped,
|
||||
}
|
||||
|
||||
impl ContainerStatus {
|
||||
pub fn can_start(&self) -> bool {
|
||||
matches!(self, ContainerStatus::Created)
|
||||
}
|
||||
|
||||
pub fn can_kill(&self) -> bool {
|
||||
use ContainerStatus::*;
|
||||
match self {
|
||||
Creating | Stopped => false,
|
||||
Created | Running => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_delete(&self) -> bool {
|
||||
matches!(self, ContainerStatus::Stopped)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct State {
|
||||
// Version is the version of the specification that is supported.
|
||||
pub oci_version: String,
|
||||
// ID is the container ID
|
||||
pub id: String,
|
||||
// Status is the runtime status of the container.
|
||||
pub status: ContainerStatus,
|
||||
// Pid is the process ID for the container process.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pid: Option<i32>,
|
||||
// Bundle is the path to the container's bundle directory.
|
||||
pub bundle: String,
|
||||
// Annotations are key values associated with the container.
|
||||
pub annotations: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(
|
||||
container_id: &str,
|
||||
status: ContainerStatus,
|
||||
pid: Option<i32>,
|
||||
bundle: &str,
|
||||
) -> Self {
|
||||
Self {
|
||||
oci_version: "v1.0.2".to_string(),
|
||||
id: container_id.to_string(),
|
||||
status,
|
||||
pid,
|
||||
bundle: bundle.to_string(),
|
||||
annotations: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self, container_root: &PathBuf) -> Result<()> {
|
||||
let state_file_path = container_root.join(STATE_FILE_PATH);
|
||||
let file = fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(state_file_path)
|
||||
.expect("Unable to open");
|
||||
serde_json::to_writer(&file, self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(container_root: &PathBuf) -> Result<Self> {
|
||||
let state_file_path = container_root.join(STATE_FILE_PATH);
|
||||
let file = File::open(state_file_path)?;
|
||||
let state: Self = serde_json::from_reader(&file)?;
|
||||
Ok(state)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Clap;
|
||||
use nix::fcntl;
|
||||
use nix::sched;
|
||||
use nix::sys::stat;
|
||||
use nix::unistd;
|
||||
use nix::unistd::{Gid, Uid};
|
||||
|
||||
use crate::container::{Container, ContainerStatus};
|
||||
use crate::notify_socket::NotifyListener;
|
||||
use crate::process::{fork, Process};
|
||||
use crate::rootfs;
|
||||
use crate::spec;
|
||||
use crate::stdio::FileDescriptor;
|
||||
use crate::tty;
|
||||
use crate::utils;
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Create {
|
||||
#[clap(short, long)]
|
||||
pid_file: Option<String>,
|
||||
#[clap(short, long, default_value = ".")]
|
||||
bundle: PathBuf,
|
||||
#[clap(short, long)]
|
||||
console_socket: Option<String>,
|
||||
pub container_id: String,
|
||||
}
|
||||
|
||||
impl Create {
|
||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
||||
let container_dir = root_path.join(&self.container_id);
|
||||
if !container_dir.exists() {
|
||||
fs::create_dir(&container_dir).unwrap();
|
||||
} else {
|
||||
bail!("{} already exists", self.container_id)
|
||||
}
|
||||
|
||||
unistd::chdir(&self.bundle)?;
|
||||
|
||||
let spec = spec::Spec::load("config.json")?;
|
||||
|
||||
let container_dir = fs::canonicalize(container_dir)?;
|
||||
unistd::chdir(&*container_dir)?;
|
||||
|
||||
log::debug!("{:?}", &container_dir);
|
||||
let container = Container::new(
|
||||
&self.container_id,
|
||||
ContainerStatus::Creating,
|
||||
None,
|
||||
self.bundle.to_str().unwrap(),
|
||||
&container_dir,
|
||||
)?;
|
||||
container.save()?;
|
||||
|
||||
let mut notify_socket: NotifyListener = NotifyListener::new(&container_dir)?;
|
||||
|
||||
let rootfs = fs::canonicalize(&spec.root.path)?;
|
||||
|
||||
let (csocketfd, _consolefd) = {
|
||||
if let Some(console_socket) = &self.console_socket {
|
||||
let (csocketfd, consolefd) =
|
||||
tty::load_console_sockets(&container_dir, console_socket)?;
|
||||
(Some(csocketfd), Some(consolefd))
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
};
|
||||
|
||||
let process = run_container(
|
||||
self.pid_file.as_ref(),
|
||||
&mut notify_socket,
|
||||
&rootfs,
|
||||
&spec,
|
||||
csocketfd,
|
||||
container,
|
||||
)?;
|
||||
if let Process::Parent(_) = process {
|
||||
process::exit(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_container<P: AsRef<Path>>(
|
||||
pid_file: Option<P>,
|
||||
notify_socket: &mut NotifyListener,
|
||||
rootfs: &PathBuf,
|
||||
spec: &spec::Spec,
|
||||
csocketfd: Option<FileDescriptor>,
|
||||
container: Container,
|
||||
) -> Result<Process> {
|
||||
prctl::set_dumpable(false).unwrap();
|
||||
let linux = spec.linux.as_ref().unwrap();
|
||||
|
||||
let mut cf = sched::CloneFlags::empty();
|
||||
let mut to_enter = Vec::new();
|
||||
for ns in &linux.namespaces {
|
||||
let space = sched::CloneFlags::from_bits_truncate(ns.typ as i32);
|
||||
if ns.path.is_empty() {
|
||||
cf |= space;
|
||||
} else {
|
||||
let fd = fcntl::open(&*ns.path, fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
|
||||
to_enter.push((space, fd));
|
||||
}
|
||||
}
|
||||
|
||||
match fork::fork_first(
|
||||
pid_file,
|
||||
cf.contains(sched::CloneFlags::CLONE_NEWUSER),
|
||||
linux,
|
||||
&container,
|
||||
)? {
|
||||
Process::Parent(parent) => Ok(Process::Parent(parent)),
|
||||
Process::Child(child) => {
|
||||
if let Some(csocketfd) = csocketfd {
|
||||
tty::ready(csocketfd)?;
|
||||
}
|
||||
|
||||
for &(space, fd) in &to_enter {
|
||||
sched::setns(fd, space)?;
|
||||
unistd::close(fd)?;
|
||||
if space == sched::CloneFlags::CLONE_NEWUSER {
|
||||
setid(Uid::from_raw(0), Gid::from_raw(0))?;
|
||||
}
|
||||
}
|
||||
|
||||
sched::unshare(cf & !sched::CloneFlags::CLONE_NEWUSER)?;
|
||||
|
||||
match fork::fork_init(child)? {
|
||||
Process::Child(child) => Ok(Process::Child(child)),
|
||||
Process::Init(mut init) => {
|
||||
futures::executor::block_on(rootfs::prepare_rootfs(
|
||||
spec,
|
||||
rootfs,
|
||||
cf.contains(sched::CloneFlags::CLONE_NEWUSER),
|
||||
))?;
|
||||
rootfs::pivot_rootfs(&*rootfs)?;
|
||||
|
||||
init.ready()?;
|
||||
|
||||
notify_socket.wait_for_container_start()?;
|
||||
|
||||
utils::do_exec(&spec.process.args[0], &spec.process.args)?;
|
||||
container.update_status(ContainerStatus::Stopped)?.save()?;
|
||||
|
||||
Ok(Process::Init(init))
|
||||
}
|
||||
Process::Parent(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn setid(uid: Uid, gid: Gid) -> Result<()> {
|
||||
if let Err(e) = prctl::set_keep_capabilities(true) {
|
||||
bail!("set keep capabilities returned {}", e);
|
||||
};
|
||||
unistd::setresgid(gid, gid, gid)?;
|
||||
unistd::setresuid(uid, uid, uid)?;
|
||||
if let Err(e) = prctl::set_keep_capabilities(false) {
|
||||
bail!("set keep capabilities returned {}", e);
|
||||
};
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
pub mod cond;
|
||||
pub mod container;
|
||||
pub mod create;
|
||||
pub mod logger;
|
||||
pub mod notify_socket;
|
||||
pub mod process;
|
||||
pub mod rootfs;
|
||||
pub mod signal;
|
||||
pub mod spec;
|
||||
pub mod start;
|
||||
pub mod stdio;
|
||||
pub mod tty;
|
||||
pub mod utils;
|
|
@ -0,0 +1,113 @@
|
|||
use std::env;
|
||||
use std::io::{stderr, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{LevelFilter, Log, Metadata, Record};
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde_json::json;
|
||||
|
||||
pub static YOUKI_LOGGER: OnceCell<YoukiLogger> = OnceCell::new();
|
||||
pub static LOG_FILE: OnceCell<Option<File>> = OnceCell::new();
|
||||
|
||||
pub fn init(container_id: &str, log_file: Option<PathBuf>) -> Result<()> {
|
||||
let _log_file = LOG_FILE.get_or_init(|| -> Option<File> {
|
||||
if let Ok(docker_root) = env::var("YOUKI_MODE") {
|
||||
if let Some(log_file_path) = &log_file {
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(false)
|
||||
.open(log_file_path)
|
||||
.expect("fail opening log file ");
|
||||
};
|
||||
|
||||
let mut log_file_path = PathBuf::from(&docker_root);
|
||||
log_file_path.push(container_id);
|
||||
log_file_path.push(format!("{}-json.log", container_id));
|
||||
|
||||
let level_filter = if let Ok(log_level_str) = env::var("YOUKI_LOG_LEVEL") {
|
||||
LevelFilter::from_str(&log_level_str).unwrap_or(LevelFilter::Warn)
|
||||
} else {
|
||||
LevelFilter::Warn
|
||||
};
|
||||
let logger = YOUKI_LOGGER.get_or_init(|| YoukiLogger::new(level_filter.to_level()));
|
||||
log::set_logger(logger)
|
||||
.map(|()| log::set_max_level(level_filter))
|
||||
.unwrap();
|
||||
Some(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(false)
|
||||
.open(log_file_path)
|
||||
.expect("fail opening log file "),
|
||||
)
|
||||
} else if let Some(log_file_path) = log_file {
|
||||
Some(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(false)
|
||||
.open(log_file_path)
|
||||
.expect("fail opening log file "),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
pub struct YoukiLogger {
|
||||
level: Option<log::Level>,
|
||||
}
|
||||
|
||||
impl YoukiLogger {
|
||||
pub fn new(level: Option<log::Level>) -> Self {
|
||||
Self { level }
|
||||
}
|
||||
}
|
||||
|
||||
impl Log for YoukiLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
if let Some(level) = self.level {
|
||||
metadata.level() <= level
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let log_msg = match (record.file(), record.line()) {
|
||||
(Some(file), Some(line)) => json!({
|
||||
"log": format!("[{} {}:{}] {}\r\n", record.level(), file, line, record.args()),
|
||||
"stream": "stdout",
|
||||
"time": chrono::Local::now().to_rfc3339()
|
||||
}),
|
||||
(_, _) => json!({
|
||||
"log": format!("[{}] {}\r\n", record.level(), record.args()),
|
||||
"stream": "stdout",
|
||||
"time": chrono::Local::now().to_rfc3339()
|
||||
}),
|
||||
};
|
||||
if let Some(mut log_file) = LOG_FILE.get().unwrap().as_ref() {
|
||||
let _ = writeln!(log_file, "{}", log_msg.to_string());
|
||||
} else {
|
||||
let _ = writeln!(stderr(), "{}", log_msg.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
if let Some(mut log_file) = LOG_FILE.get().unwrap().as_ref() {
|
||||
log_file.flush().expect("Failed to flush");
|
||||
} else {
|
||||
stderr().flush().expect("Faild to flush");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Clap;
|
||||
use nix::sys::signal as nix_signal;
|
||||
|
||||
use youki::container::{Container, ContainerStatus};
|
||||
use youki::create;
|
||||
use youki::signal;
|
||||
use youki::start;
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(version = "1.0", author = "utam0k <k0ma@utam0k.jp>")]
|
||||
struct Opts {
|
||||
#[clap(short, long, default_value = "/run/youki")]
|
||||
root: PathBuf,
|
||||
#[clap(short, long)]
|
||||
log: Option<PathBuf>,
|
||||
#[clap(long)]
|
||||
log_format: Option<String>,
|
||||
#[clap(subcommand)]
|
||||
subcmd: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Kill {
|
||||
container_id: String,
|
||||
signal: String,
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Delete {
|
||||
container_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct StateArgs {
|
||||
pub container_id: String,
|
||||
}
|
||||
#[derive(Clap, Debug)]
|
||||
enum SubCommand {
|
||||
#[clap(version = "0.0.1", author = "utam0k <k0ma@utam0k.jp>")]
|
||||
Create(create::Create),
|
||||
#[clap(version = "0.0.1", author = "utam0k <k0ma@utam0k.jp>")]
|
||||
Start(start::Start),
|
||||
#[clap(version = "0.0.1", author = "utam0k <k0ma@utam0k.jp>")]
|
||||
Kill(Kill),
|
||||
#[clap(version = "0.0.1", author = "utam0k <k0ma@utam0k.jp>")]
|
||||
Delete(Delete),
|
||||
#[clap(version = "0.0.1", author = "utam0k <k0ma@utam0k.jp>")]
|
||||
State(StateArgs),
|
||||
}
|
||||
|
||||
impl SubCommand {
|
||||
fn get_container_id(&self) -> &String {
|
||||
match &self {
|
||||
SubCommand::Create(create) => &create.container_id,
|
||||
SubCommand::Start(start) => &start.container_id,
|
||||
SubCommand::Delete(delete) => &delete.container_id,
|
||||
SubCommand::Kill(kill) => &kill.container_id,
|
||||
SubCommand::State(state_args) => &state_args.container_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let opts = Opts::parse();
|
||||
youki::logger::init(opts.subcmd.get_container_id().as_str(), opts.log)?;
|
||||
log::debug!("Hello, world");
|
||||
|
||||
let root_path = PathBuf::from(&opts.root);
|
||||
fs::create_dir_all(&root_path)?;
|
||||
|
||||
match opts.subcmd {
|
||||
SubCommand::Create(create) => create.exec(root_path),
|
||||
SubCommand::Start(start) => start.exec(root_path),
|
||||
SubCommand::Kill(kill) => {
|
||||
let root_path = fs::canonicalize(root_path)?;
|
||||
let container_root = root_path.join(&kill.container_id);
|
||||
if !container_root.exists() {
|
||||
bail!("{} doesn't exists.", kill.container_id)
|
||||
}
|
||||
let container = Container::load(container_root)?.refresh_status()?;
|
||||
if container.can_kill() {
|
||||
let sig = signal::from_str(kill.signal.as_str())?;
|
||||
log::debug!("kill signal {} to {}", sig, container.pid().unwrap());
|
||||
nix_signal::kill(container.pid().unwrap(), sig)?;
|
||||
container.update_status(ContainerStatus::Stopped)?.save()?;
|
||||
std::process::exit(0)
|
||||
} else {
|
||||
bail!(
|
||||
"{} counld not be killed because it was {:?}",
|
||||
container.id(),
|
||||
container.status()
|
||||
)
|
||||
}
|
||||
}
|
||||
SubCommand::Delete(delete) => {
|
||||
let container_root = root_path.join(&delete.container_id);
|
||||
if !container_root.exists() {
|
||||
bail!("{} doesn't exists.", delete.container_id)
|
||||
}
|
||||
let container = Container::load(container_root)?.refresh_status()?;
|
||||
if container.can_delete() {
|
||||
if container.root.exists() {
|
||||
fs::remove_dir_all(&container.root)?;
|
||||
}
|
||||
std::process::exit(0)
|
||||
} else {
|
||||
bail!(
|
||||
"{} counld not be deleted because it was {:?}",
|
||||
container.id(),
|
||||
container.status()
|
||||
)
|
||||
}
|
||||
}
|
||||
SubCommand::State(state_args) => {
|
||||
let root_path = fs::canonicalize(root_path)?;
|
||||
let container_root = root_path.join(state_args.container_id);
|
||||
let container = Container::load(container_root)?.refresh_status()?;
|
||||
println!("{}", serde_json::to_string_pretty(&container.state)?);
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
use std::io::prelude::*;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use nix::unistd::close;
|
||||
|
||||
pub const NOTIFY_FILE: &str = "notify.sock";
|
||||
|
||||
pub struct NotifyListener {
|
||||
socket: UnixListener,
|
||||
}
|
||||
|
||||
impl NotifyListener {
|
||||
pub fn new(root: &PathBuf) -> Result<Self> {
|
||||
let _notify_file_path = root.join(NOTIFY_FILE);
|
||||
let stream = UnixListener::bind("notify.sock")?;
|
||||
Ok(Self { socket: stream })
|
||||
}
|
||||
|
||||
pub fn wait_for_container_start(&mut self) -> Result<()> {
|
||||
match self.socket.accept() {
|
||||
Ok((mut socket, _addr)) => {
|
||||
let mut response = String::new();
|
||||
socket.read_to_string(&mut response)?;
|
||||
log::debug!("receive :{}", response);
|
||||
}
|
||||
Err(e) => println!("accept function failed: {:?}", e),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close(&mut self) -> Result<()> {
|
||||
close(self.socket.as_raw_fd())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NotifySocket {}
|
||||
|
||||
impl NotifySocket {
|
||||
pub fn new(_root: &PathBuf) -> Result<Self> {
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
pub fn notify_container_start(&mut self) -> Result<()> {
|
||||
log::debug!("connection start");
|
||||
let mut stream = UnixStream::connect("notify.sock")?;
|
||||
stream.write_all(b"start container")?;
|
||||
log::debug!("write finish");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn notify_container_finish(&mut self) -> Result<()> {
|
||||
// self.socket.write_all(b"finish container")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
mod child;
|
||||
pub mod fork;
|
||||
mod init;
|
||||
pub mod message;
|
||||
mod parent;
|
||||
|
||||
pub enum Process {
|
||||
Parent(parent::ParentProcess),
|
||||
Child(child::ChildProcess),
|
||||
Init(init::InitProcess),
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
use std::io::Write;
|
||||
use std::{io::Read, time::Duration};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use mio::unix::pipe;
|
||||
use mio::unix::pipe::Receiver;
|
||||
use mio::unix::pipe::Sender;
|
||||
use mio::{Events, Interest, Poll, Token};
|
||||
use nix::unistd::Pid;
|
||||
|
||||
use crate::process::message::Message;
|
||||
|
||||
const CHILD: Token = Token(1);
|
||||
pub struct ChildProcess {
|
||||
sender_for_parent: Sender,
|
||||
receiver: Option<Receiver>,
|
||||
poll: Option<Poll>,
|
||||
}
|
||||
|
||||
impl ChildProcess {
|
||||
pub fn new(sender_for_parent: Sender) -> Result<Self> {
|
||||
Ok(Self {
|
||||
sender_for_parent,
|
||||
receiver: None,
|
||||
poll: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn setup_uds(&mut self) -> Result<Sender> {
|
||||
let (sender, mut receiver) = pipe::new()?;
|
||||
let poll = Poll::new()?;
|
||||
poll.registry()
|
||||
.register(&mut receiver, CHILD, Interest::READABLE)?;
|
||||
self.receiver = Some(receiver);
|
||||
self.poll = Some(poll);
|
||||
Ok(sender)
|
||||
}
|
||||
|
||||
pub fn ready(&mut self, init_pid: Pid) -> Result<()> {
|
||||
log::debug!(
|
||||
"child send to parent {:?}",
|
||||
(Message::ChildReady as u8).to_be_bytes()
|
||||
);
|
||||
self.write_message_for_parent(Message::ChildReady)?;
|
||||
self.sender_for_parent
|
||||
.write_all(&(init_pid.as_raw()).to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_message_for_parent(&mut self, msg: Message) -> Result<()> {
|
||||
self.sender_for_parent
|
||||
.write_all(&(msg as u8).to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wait_for_init_ready(&mut self) -> Result<()> {
|
||||
let receiver = self
|
||||
.receiver
|
||||
.as_mut()
|
||||
.expect("Complete the setup of uds in advance.");
|
||||
let poll = self
|
||||
.poll
|
||||
.as_mut()
|
||||
.expect("Complete the setup of uds in advance.");
|
||||
|
||||
let mut events = Events::with_capacity(128);
|
||||
poll.poll(&mut events, Some(Duration::from_millis(1000)))?;
|
||||
for event in events.iter() {
|
||||
if let CHILD = event.token() {
|
||||
let mut buf = [0; 1];
|
||||
receiver.read_exact(&mut buf)?;
|
||||
match Message::from(u8::from_be_bytes(buf)) {
|
||||
Message::InitReady => return Ok(()),
|
||||
msg => bail!("receive unexpected message {:?} in child process", msg),
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
bail!("unexpected message.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
use std::fs;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use anyhow::bail;
|
||||
use child::ChildProcess;
|
||||
use init::InitProcess;
|
||||
use nix::sched;
|
||||
use nix::sys::wait::{waitpid, WaitStatus};
|
||||
use nix::unistd;
|
||||
|
||||
use crate::container::ContainerStatus;
|
||||
use crate::process::{child, init, parent, Process};
|
||||
use crate::spec;
|
||||
use crate::utils;
|
||||
use crate::{cond::Cond, container::Container};
|
||||
|
||||
pub fn fork_first<P: AsRef<Path>>(
|
||||
pid_file: Option<P>,
|
||||
userns: bool,
|
||||
linux: &spec::Linux,
|
||||
container: &Container,
|
||||
) -> Result<Process> {
|
||||
let ccond = Cond::new()?;
|
||||
|
||||
let (mut parent, sender_for_parent) = parent::ParentProcess::new()?;
|
||||
let child = child::ChildProcess::new(sender_for_parent)?;
|
||||
|
||||
unsafe {
|
||||
match unistd::fork()? {
|
||||
unistd::ForkResult::Child => {
|
||||
utils::set_name("rc-user")?;
|
||||
|
||||
if let Some(ref r) = linux.resources {
|
||||
if let Some(adj) = r.oom_score_adj {
|
||||
let mut f = fs::File::create("/proc/self/oom_score_adj")?;
|
||||
f.write_all(adj.to_string().as_bytes())?;
|
||||
}
|
||||
}
|
||||
|
||||
if userns {
|
||||
sched::unshare(sched::CloneFlags::CLONE_NEWUSER)?;
|
||||
}
|
||||
|
||||
ccond.notify()?;
|
||||
|
||||
Ok(Process::Child(child))
|
||||
}
|
||||
unistd::ForkResult::Parent { child } => {
|
||||
ccond.wait()?;
|
||||
|
||||
let init_pid = parent.wait_for_child_ready()?;
|
||||
container
|
||||
.update_status(ContainerStatus::Created)?
|
||||
.set_pid(init_pid)
|
||||
.save()?;
|
||||
|
||||
if let Some(pid_file) = pid_file {
|
||||
fs::write(&pid_file, format!("{}", child))?;
|
||||
}
|
||||
Ok(Process::Parent(parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fork_init(mut child_process: ChildProcess) -> Result<Process> {
|
||||
let sender_for_child = child_process.setup_uds()?;
|
||||
unsafe {
|
||||
match unistd::fork()? {
|
||||
unistd::ForkResult::Child => Ok(Process::Init(InitProcess::new(sender_for_child))),
|
||||
unistd::ForkResult::Parent { child } => {
|
||||
child_process.wait_for_init_ready()?;
|
||||
child_process.ready(child)?;
|
||||
|
||||
match waitpid(child, None)? {
|
||||
WaitStatus::Exited(pid, status) => {
|
||||
log::debug!("exited pid: {:?}, status: {:?}", pid, status);
|
||||
exit(status);
|
||||
}
|
||||
WaitStatus::Signaled(pid, status, _) => {
|
||||
log::debug!("signaled pid: {:?}, status: {:?}", pid, status);
|
||||
exit(0);
|
||||
}
|
||||
_ => bail!("abnormal exited!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use mio::unix::pipe::Sender;
|
||||
|
||||
use crate::process::message::Message;
|
||||
pub struct InitProcess {
|
||||
sender_for_child: Sender,
|
||||
}
|
||||
|
||||
impl InitProcess {
|
||||
pub fn new(sender_for_child: Sender) -> Self {
|
||||
Self { sender_for_child }
|
||||
}
|
||||
|
||||
pub fn ready(&mut self) -> Result<()> {
|
||||
log::debug!(
|
||||
"init send to child {:?}",
|
||||
(Message::InitReady as u8).to_be_bytes()
|
||||
);
|
||||
self.write_message_for_child(Message::InitReady)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_message_for_child(&mut self, msg: Message) -> Result<()> {
|
||||
self.sender_for_child
|
||||
.write_all(&(msg as u8).to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
ChildReady = 0x00,
|
||||
InitReady = 0x01,
|
||||
}
|
||||
|
||||
impl From<u8> for Message {
|
||||
fn from(from: u8) -> Self {
|
||||
match from {
|
||||
0x00 => Message::ChildReady,
|
||||
0x01 => Message::InitReady,
|
||||
_ => panic!("unknown message."),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
use std::{io::Read, time::Duration};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use mio::unix::pipe;
|
||||
use mio::unix::pipe::{Receiver, Sender};
|
||||
use mio::{Events, Interest, Poll, Token};
|
||||
|
||||
use crate::process::message::Message;
|
||||
|
||||
const PARENT: Token = Token(0);
|
||||
pub struct ParentProcess {
|
||||
receiver: Receiver,
|
||||
poll: Poll,
|
||||
}
|
||||
|
||||
impl ParentProcess {
|
||||
pub fn new() -> Result<(Self, Sender)> {
|
||||
let (sender, mut receiver) = pipe::new()?;
|
||||
let poll = Poll::new()?;
|
||||
poll.registry()
|
||||
.register(&mut receiver, PARENT, Interest::READABLE)?;
|
||||
Ok((Self { receiver, poll }, sender))
|
||||
}
|
||||
|
||||
pub fn wait_for_child_ready(&mut self) -> Result<i32> {
|
||||
let mut events = Events::with_capacity(128);
|
||||
self.poll
|
||||
.poll(&mut events, Some(Duration::from_millis(1000)))?;
|
||||
for event in events.iter() {
|
||||
if let PARENT = event.token() {
|
||||
let mut buf = [0; 1];
|
||||
self.receiver.read_exact(&mut buf)?;
|
||||
match Message::from(u8::from_be_bytes(buf)) {
|
||||
Message::ChildReady => {
|
||||
let mut buf = [0; 4];
|
||||
self.receiver.read_exact(&mut buf)?;
|
||||
return Ok(i32::from_be_bytes(buf));
|
||||
}
|
||||
msg => bail!("receive unexpected message {:?} in parent process", msg),
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
bail!("unexpected message.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
use std::fs::OpenOptions;
|
||||
use std::fs::{canonicalize, create_dir_all, remove_file};
|
||||
use std::os::unix::fs::symlink;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use futures::future;
|
||||
use nix::errno::Errno;
|
||||
use nix::fcntl::{open, OFlag};
|
||||
use nix::mount::MsFlags;
|
||||
use nix::mount::*;
|
||||
use nix::sys::stat::{mknod, umask};
|
||||
use nix::sys::stat::{Mode, SFlag};
|
||||
use nix::unistd::{chdir, chown, close, fchdir, getcwd, pivot_root};
|
||||
use nix::unistd::{Gid, Uid};
|
||||
use nix::NixPath;
|
||||
|
||||
use crate::spec::{LinuxDevice, LinuxDeviceType, Mount, Spec};
|
||||
|
||||
fn default_devices() -> Vec<LinuxDevice> {
|
||||
vec![
|
||||
LinuxDevice {
|
||||
path: "/dev/null".to_string(),
|
||||
typ: LinuxDeviceType::C,
|
||||
major: 1,
|
||||
minor: 3,
|
||||
file_mode: Some(0o066),
|
||||
uid: None,
|
||||
gid: None,
|
||||
},
|
||||
LinuxDevice {
|
||||
path: "/dev/zero".to_string(),
|
||||
typ: LinuxDeviceType::C,
|
||||
major: 1,
|
||||
minor: 5,
|
||||
file_mode: Some(0o066),
|
||||
uid: None,
|
||||
gid: None,
|
||||
},
|
||||
LinuxDevice {
|
||||
path: "/dev/full".to_string(),
|
||||
typ: LinuxDeviceType::C,
|
||||
major: 1,
|
||||
minor: 7,
|
||||
file_mode: Some(0o066),
|
||||
uid: None,
|
||||
gid: None,
|
||||
},
|
||||
LinuxDevice {
|
||||
path: "/dev/tty".to_string(),
|
||||
typ: LinuxDeviceType::C,
|
||||
major: 5,
|
||||
minor: 0,
|
||||
file_mode: Some(0o066),
|
||||
uid: None,
|
||||
gid: None,
|
||||
},
|
||||
LinuxDevice {
|
||||
path: "/dev/urandom".to_string(),
|
||||
typ: LinuxDeviceType::C,
|
||||
major: 1,
|
||||
minor: 9,
|
||||
file_mode: Some(0o066),
|
||||
uid: None,
|
||||
gid: None,
|
||||
},
|
||||
LinuxDevice {
|
||||
path: "/dev/random".to_string(),
|
||||
typ: LinuxDeviceType::C,
|
||||
major: 1,
|
||||
minor: 8,
|
||||
file_mode: Some(0o066),
|
||||
uid: None,
|
||||
gid: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub async fn prepare_rootfs(spec: &Spec, rootfs: &PathBuf, bind_devices: bool) -> Result<()> {
|
||||
let mut flags = MsFlags::MS_REC;
|
||||
match spec.linux {
|
||||
Some(ref linux) => match linux.rootfs_propagation.as_ref() {
|
||||
"shared" => flags |= MsFlags::MS_SHARED,
|
||||
"private" => flags |= MsFlags::MS_PRIVATE,
|
||||
"slave" | "" => flags |= MsFlags::MS_SLAVE,
|
||||
_ => panic!(),
|
||||
},
|
||||
None => flags |= MsFlags::MS_SLAVE,
|
||||
};
|
||||
let linux = spec.linux.as_ref().unwrap();
|
||||
mount(None::<&str>, "/", None::<&str>, flags, None::<&str>)?;
|
||||
|
||||
log::debug!("mount root fs {:?}", rootfs);
|
||||
mount(
|
||||
Some(rootfs),
|
||||
rootfs,
|
||||
None::<&str>,
|
||||
MsFlags::MS_BIND | MsFlags::MS_REC,
|
||||
None::<&str>,
|
||||
)?;
|
||||
|
||||
for m in &spec.mounts {
|
||||
let (flags, data) = parse_mount(m);
|
||||
if m.typ == "cgroup" {
|
||||
log::warn!("A feature of cgoup is unimplemented.");
|
||||
// skip
|
||||
} else if m.destination == PathBuf::from("/dev") {
|
||||
mount_from(
|
||||
m,
|
||||
rootfs,
|
||||
flags & !MsFlags::MS_RDONLY,
|
||||
&data,
|
||||
&linux.mount_label,
|
||||
)?;
|
||||
} else {
|
||||
mount_from(m, rootfs, flags, &data, &linux.mount_label)?;
|
||||
}
|
||||
}
|
||||
|
||||
let olddir = getcwd()?;
|
||||
chdir(rootfs)?;
|
||||
|
||||
setup_default_symlinks(rootfs)?;
|
||||
create_devices(&linux.devices, bind_devices).await?;
|
||||
setup_ptmx(rootfs)?;
|
||||
|
||||
chdir(&olddir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_ptmx(rootfs: &PathBuf) -> Result<()> {
|
||||
if let Err(e) = remove_file(rootfs.join("dev/ptmx")) {
|
||||
if e.kind() != ::std::io::ErrorKind::NotFound {
|
||||
let msg = "could not delete /dev/ptmx".to_string();
|
||||
panic!(msg);
|
||||
}
|
||||
}
|
||||
symlink("pts/ptmx", rootfs.join("dev/ptmx"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_default_symlinks(rootfs: &PathBuf) -> Result<()> {
|
||||
if Path::new("/proc/kcore").exists() {
|
||||
symlink("/proc/kcore", "dev/kcore")?;
|
||||
}
|
||||
|
||||
let defaults = [
|
||||
("/proc/self/fd", "dev/fd"),
|
||||
("/proc/self/fd/0", "dev/stdin"),
|
||||
("/proc/self/fd/1", "dev/stdout"),
|
||||
("/proc/self/fd/2", "dev/stderr"),
|
||||
];
|
||||
for &(src, dst) in defaults.iter() {
|
||||
symlink(src, rootfs.join(dst))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_devices(devices: &[LinuxDevice], bind: bool) -> Result<()> {
|
||||
let old_mode = umask(Mode::from_bits_truncate(0o000));
|
||||
if bind {
|
||||
future::try_join_all(default_devices().iter().chain(devices).map(|dev| {
|
||||
if !dev.path.starts_with("/dev") || dev.path.contains("..") {
|
||||
panic!("{} is not a valid device path", dev.path);
|
||||
}
|
||||
bind_dev(dev)
|
||||
}))
|
||||
.await?;
|
||||
} else {
|
||||
future::try_join_all(default_devices().iter().chain(devices).map(|dev| {
|
||||
if !dev.path.starts_with("/dev") || dev.path.contains("..") {
|
||||
panic!("{} is not a valid device path", dev.path);
|
||||
}
|
||||
mknod_dev(dev)
|
||||
}))
|
||||
.await?;
|
||||
}
|
||||
umask(old_mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn bind_dev(dev: &LinuxDevice) -> Result<()> {
|
||||
let fd = open(
|
||||
&dev.path[1..],
|
||||
OFlag::O_RDWR | OFlag::O_CREAT,
|
||||
Mode::from_bits_truncate(0o644),
|
||||
)?;
|
||||
close(fd)?;
|
||||
mount(
|
||||
Some(&*dev.path),
|
||||
&dev.path[1..],
|
||||
None::<&str>,
|
||||
MsFlags::MS_BIND,
|
||||
None::<&str>,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mknod_dev(dev: &LinuxDevice) -> Result<()> {
|
||||
fn makedev(major: u64, minor: u64) -> u64 {
|
||||
(minor & 0xff) | ((major & 0xfff) << 8) | ((minor & !0xff) << 12) | ((major & !0xfff) << 32)
|
||||
}
|
||||
|
||||
let f = to_sflag(dev.typ)?;
|
||||
mknod(
|
||||
&dev.path[1..],
|
||||
f,
|
||||
Mode::from_bits_truncate(dev.file_mode.unwrap_or(0)),
|
||||
makedev(dev.major, dev.minor),
|
||||
)?;
|
||||
chown(
|
||||
&dev.path[1..],
|
||||
dev.uid.map(Uid::from_raw),
|
||||
dev.gid.map(Gid::from_raw),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_sflag(t: LinuxDeviceType) -> Result<SFlag> {
|
||||
Ok(match t {
|
||||
LinuxDeviceType::B => SFlag::S_IFBLK,
|
||||
LinuxDeviceType::C | LinuxDeviceType::U => SFlag::S_IFCHR,
|
||||
LinuxDeviceType::P => SFlag::S_IFIFO,
|
||||
LinuxDeviceType::A => bail!("type a is not allowed for linux device"),
|
||||
})
|
||||
}
|
||||
|
||||
fn mount_from(m: &Mount, rootfs: &PathBuf, flags: MsFlags, data: &str, label: &str) -> Result<()> {
|
||||
let d;
|
||||
if !label.is_empty() && m.typ != "proc" && m.typ != "sysfs" {
|
||||
if data.is_empty() {
|
||||
d = format! {"context=\"{}\"", label};
|
||||
} else {
|
||||
d = format! {"{},context=\"{}\"", data, label};
|
||||
}
|
||||
} else {
|
||||
d = data.to_string();
|
||||
}
|
||||
|
||||
let dest_for_host = format!(
|
||||
"{}{}",
|
||||
rootfs.to_string_lossy().into_owned(),
|
||||
m.destination.display()
|
||||
);
|
||||
let dest = Path::new(&dest_for_host);
|
||||
|
||||
let src = if m.typ == "bind" {
|
||||
let src = canonicalize(&m.source)?;
|
||||
let dir = if src.is_file() {
|
||||
Path::new(&dest).parent().unwrap()
|
||||
} else {
|
||||
Path::new(&dest)
|
||||
};
|
||||
create_dir_all(&dir).unwrap();
|
||||
// make sure file exists so we can bind over it
|
||||
if src.is_file() {
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&dest)
|
||||
.unwrap();
|
||||
}
|
||||
src
|
||||
} else {
|
||||
create_dir_all(&dest).unwrap();
|
||||
PathBuf::from(&m.source)
|
||||
};
|
||||
|
||||
if let Err(::nix::Error::Sys(errno)) = mount(Some(&*src), dest, Some(&*m.typ), flags, Some(&*d))
|
||||
{
|
||||
if errno != Errno::EINVAL {
|
||||
let chain = format!("mount of {} failed", m.destination.display());
|
||||
panic!(chain);
|
||||
}
|
||||
// try again without mount label
|
||||
mount(Some(&*src), dest, Some(&*m.typ), flags, Some(data))?;
|
||||
}
|
||||
// remount bind mounts if they have other flags (like MsFlags::MS_RDONLY)
|
||||
if flags.contains(MsFlags::MS_BIND)
|
||||
&& flags.intersects(
|
||||
!(MsFlags::MS_REC
|
||||
| MsFlags::MS_REMOUNT
|
||||
| MsFlags::MS_BIND
|
||||
| MsFlags::MS_PRIVATE
|
||||
| MsFlags::MS_SHARED
|
||||
| MsFlags::MS_SLAVE),
|
||||
)
|
||||
{
|
||||
mount(
|
||||
Some(&*dest),
|
||||
&*dest,
|
||||
None::<&str>,
|
||||
flags | MsFlags::MS_REMOUNT,
|
||||
None::<&str>,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pivot_rootfs<P: ?Sized + NixPath>(path: &P) -> Result<()> {
|
||||
let newroot = open(path, OFlag::O_DIRECTORY | OFlag::O_RDONLY, Mode::empty())?;
|
||||
|
||||
pivot_root(path, path)?;
|
||||
|
||||
umount2("/", MntFlags::MNT_DETACH)?;
|
||||
fchdir(newroot)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_mount(m: &Mount) -> (MsFlags, String) {
|
||||
let mut flags = MsFlags::empty();
|
||||
let mut data = Vec::new();
|
||||
for s in &m.options {
|
||||
if let Some((is_clear, flag)) = match s.as_str() {
|
||||
"defaults" => Some((false, MsFlags::empty())),
|
||||
"ro" => Some((false, MsFlags::MS_RDONLY)),
|
||||
"rw" => Some((true, MsFlags::MS_RDONLY)),
|
||||
"suid" => Some((true, MsFlags::MS_NOSUID)),
|
||||
"nosuid" => Some((false, MsFlags::MS_NOSUID)),
|
||||
"dev" => Some((true, MsFlags::MS_NODEV)),
|
||||
"nodev" => Some((false, MsFlags::MS_NODEV)),
|
||||
"exec" => Some((true, MsFlags::MS_NOEXEC)),
|
||||
"noexec" => Some((false, MsFlags::MS_NOEXEC)),
|
||||
"sync" => Some((false, MsFlags::MS_SYNCHRONOUS)),
|
||||
"async" => Some((true, MsFlags::MS_SYNCHRONOUS)),
|
||||
"dirsync" => Some((false, MsFlags::MS_DIRSYNC)),
|
||||
"remount" => Some((false, MsFlags::MS_REMOUNT)),
|
||||
"mand" => Some((false, MsFlags::MS_MANDLOCK)),
|
||||
"nomand" => Some((true, MsFlags::MS_MANDLOCK)),
|
||||
"atime" => Some((true, MsFlags::MS_NOATIME)),
|
||||
"noatime" => Some((false, MsFlags::MS_NOATIME)),
|
||||
"diratime" => Some((true, MsFlags::MS_NODIRATIME)),
|
||||
"nodiratime" => Some((false, MsFlags::MS_NODIRATIME)),
|
||||
"bind" => Some((false, MsFlags::MS_BIND)),
|
||||
"rbind" => Some((false, MsFlags::MS_BIND | MsFlags::MS_REC)),
|
||||
"unbindable" => Some((false, MsFlags::MS_UNBINDABLE)),
|
||||
"runbindable" => Some((false, MsFlags::MS_UNBINDABLE | MsFlags::MS_REC)),
|
||||
"private" => Some((false, MsFlags::MS_PRIVATE)),
|
||||
"rprivate" => Some((false, MsFlags::MS_PRIVATE | MsFlags::MS_REC)),
|
||||
"shared" => Some((false, MsFlags::MS_SHARED)),
|
||||
"rshared" => Some((false, MsFlags::MS_SHARED | MsFlags::MS_REC)),
|
||||
"slave" => Some((false, MsFlags::MS_SLAVE)),
|
||||
"rslave" => Some((false, MsFlags::MS_SLAVE | MsFlags::MS_REC)),
|
||||
"relatime" => Some((false, MsFlags::MS_RELATIME)),
|
||||
"norelatime" => Some((true, MsFlags::MS_RELATIME)),
|
||||
"strictatime" => Some((false, MsFlags::MS_STRICTATIME)),
|
||||
"nostrictatime" => Some((true, MsFlags::MS_STRICTATIME)),
|
||||
_ => None,
|
||||
} {
|
||||
if is_clear {
|
||||
flags &= !flag;
|
||||
} else {
|
||||
flags |= flag;
|
||||
}
|
||||
} else {
|
||||
data.push(s.as_str());
|
||||
};
|
||||
}
|
||||
(flags, data.join(","))
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
use anyhow::{bail, Result};
|
||||
use nix::sys::signal::Signal;
|
||||
|
||||
pub fn from_str(signal: &str) -> Result<Signal> {
|
||||
use Signal::*;
|
||||
Ok(match signal.to_ascii_uppercase().as_str() {
|
||||
"1" | "HUP" | "SIGHUP" => Signal::SIGHUP,
|
||||
"2" | "INT" | "SIGINT" => Signal::SIGINT,
|
||||
"3" | "QUIT" | "SIGQUIT" => Signal::SIGQUIT,
|
||||
"4" | "ILL" | "SIGILL" => Signal::SIGILL,
|
||||
"5" | "BUS" | "SIGBUS" => Signal::SIGBUS,
|
||||
"6" | "ABRT" | "IOT" | "SIGABRT" | "SIGIOT" => Signal::SIGABRT,
|
||||
"7" | "TRAP" | "SIGTRAP" => Signal::SIGTRAP,
|
||||
"8" | "FPE" | "SIGFPE" => Signal::SIGFPE,
|
||||
"9" | "KILL" | "SIGKILL" => Signal::SIGKILL,
|
||||
"10" | "USR1" | "SIGUSR1" => Signal::SIGUSR1,
|
||||
"11" | "SEGV" | "SIGSEGV" => SIGSEGV,
|
||||
"12" | "USR2" | "SIGUSR2" => SIGUSR2,
|
||||
"13" | "PIPE" | "SIGPIPE" => SIGPIPE,
|
||||
"14" | "ALRM" | "SIGALRM" => SIGALRM,
|
||||
"15" | "TERM" | "SIGTERM" => SIGTERM,
|
||||
"16" | "STKFLT" | "SIGSTKFLT" => SIGSTKFLT,
|
||||
"17" | "CHLD" | "SIGCHLD" => SIGCHLD,
|
||||
"18" | "CONT" | "SIGCONT" => SIGCONT,
|
||||
"19" | "STOP" | "SIGSTOP" => SIGSTOP,
|
||||
"20" | "TSTP" | "SIGTSTP" => SIGTSTP,
|
||||
"21" | "TTIN" | "SIGTTIN" => SIGTTIN,
|
||||
"22" | "TTOU" | "SIGTTOU" => SIGTTOU,
|
||||
"23" | "URG" | "SIGURG" => SIGURG,
|
||||
"24" | "XCPU" | "SIGXCPU" => SIGXCPU,
|
||||
"25" | "XFSZ" | "SIGXFSZ" => SIGXFSZ,
|
||||
"26" | "VTALRM" | "SIGVTALRM" => SIGVTALRM,
|
||||
"27" | "PROF" | "SIGPROF" => SIGPROF,
|
||||
"28" | "WINCH" | "SIGWINCH" => SIGWINCH,
|
||||
"29" | "IO" | "SIGIO" => SIGIO,
|
||||
"30" | "PWR" | "SIGPWR" => SIGPWR,
|
||||
"31" | "SYS" | "SIGSYS" => SIGSYS,
|
||||
_ => bail! {"{} is not a valid signal", signal},
|
||||
})
|
||||
}
|
|
@ -0,0 +1,363 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Platform {
|
||||
#[serde(default)]
|
||||
pub os: String,
|
||||
#[serde(default)]
|
||||
pub arch: String,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Serialize, Deserialize, Debug)]
|
||||
pub struct Box {
|
||||
#[serde(default)]
|
||||
pub height: u64,
|
||||
#[serde(default)]
|
||||
pub width: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct User {
|
||||
#[serde(default)]
|
||||
pub uid: u32,
|
||||
#[serde(default)]
|
||||
pub gid: u32,
|
||||
#[serde(default)]
|
||||
pub additional_gids: Vec<u32>,
|
||||
#[serde(default)]
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Process {
|
||||
#[serde(default)]
|
||||
pub terminal: bool,
|
||||
#[serde(default)]
|
||||
pub console_size: Box,
|
||||
pub user: User,
|
||||
pub args: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub env: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub cwd: String,
|
||||
#[serde(default)]
|
||||
pub no_new_privileges: bool,
|
||||
#[serde(default)]
|
||||
pub apparmor_profile: String,
|
||||
#[serde(default)]
|
||||
pub selinux_label: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Root {
|
||||
#[serde(default)]
|
||||
pub path: PathBuf,
|
||||
#[serde(default)]
|
||||
pub readonly: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Mount {
|
||||
#[serde(default)]
|
||||
pub destination: PathBuf,
|
||||
#[serde(default, rename = "type")]
|
||||
pub typ: String,
|
||||
#[serde(default)]
|
||||
pub source: PathBuf,
|
||||
#[serde(default)]
|
||||
pub options: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinuxIDMapping {
|
||||
#[serde(default, rename = "hostID")]
|
||||
pub host_id: u32,
|
||||
#[serde(default, rename = "containerID")]
|
||||
pub container_id: u32,
|
||||
#[serde(default)]
|
||||
pub size: u32,
|
||||
}
|
||||
|
||||
// a is for LinuxDeviceCgroup
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum LinuxDeviceType {
|
||||
B,
|
||||
C,
|
||||
U,
|
||||
P,
|
||||
A,
|
||||
}
|
||||
|
||||
impl Default for LinuxDeviceType {
|
||||
fn default() -> LinuxDeviceType {
|
||||
LinuxDeviceType::A
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LinuxDeviceCgroup {
|
||||
#[serde(default)]
|
||||
pub allow: bool,
|
||||
#[serde(default, rename = "type")]
|
||||
pub typ: LinuxDeviceType,
|
||||
pub major: Option<i64>,
|
||||
pub minor: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub access: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LinuxMemory {
|
||||
pub limit: Option<i64>,
|
||||
pub reservation: Option<i64>,
|
||||
pub swap: Option<i64>,
|
||||
pub kernel: Option<i64>,
|
||||
#[serde(rename = "kernelTCP")]
|
||||
pub kernel_tcp: Option<i64>,
|
||||
pub swappiness: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinuxCPU {
|
||||
pub shares: Option<u64>,
|
||||
pub quota: Option<i64>,
|
||||
pub period: Option<u64>,
|
||||
pub realtime_runtime: Option<i64>,
|
||||
pub realtime_period: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub cpus: String,
|
||||
#[serde(default)]
|
||||
pub mems: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LinuxPids {
|
||||
#[serde(default)]
|
||||
pub limit: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinuxWeightDevice {
|
||||
#[serde(default)]
|
||||
pub major: i64,
|
||||
#[serde(default)]
|
||||
pub minor: i64,
|
||||
pub weight: Option<u16>,
|
||||
pub leaf_weight: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LinuxThrottleDevice {
|
||||
#[serde(default)]
|
||||
pub major: i64,
|
||||
#[serde(default)]
|
||||
pub minor: i64,
|
||||
#[serde(default)]
|
||||
pub rate: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinuxBlockIO {
|
||||
pub blkio_weight: Option<u16>,
|
||||
pub blkio_leaf_weight: Option<u16>,
|
||||
#[serde(default)]
|
||||
pub blkio_weight_device: Vec<LinuxWeightDevice>,
|
||||
#[serde(default)]
|
||||
pub blkio_throttle_read_bps_device: Vec<LinuxThrottleDevice>,
|
||||
#[serde(default)]
|
||||
pub blkio_throttle_write_bps_device: Vec<LinuxThrottleDevice>,
|
||||
#[serde(default, rename = "blkioThrottleReadIOPSDevice")]
|
||||
pub blkio_throttle_read_iops_device: Vec<LinuxThrottleDevice>,
|
||||
#[serde(default, rename = "blkioThrottleWriteIOPSDevice")]
|
||||
pub blkio_throttle_write_iops_device: Vec<LinuxThrottleDevice>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinuxHugepageLimit {
|
||||
#[serde(default)]
|
||||
pub page_size: String,
|
||||
#[serde(default)]
|
||||
pub limit: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LinuxInterfacePriority {
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub priority: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinuxNetwork {
|
||||
#[serde(rename = "classID")]
|
||||
pub class_id: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub priorities: Vec<LinuxInterfacePriority>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinuxResources {
|
||||
#[serde(default)]
|
||||
pub devices: Vec<LinuxDeviceCgroup>,
|
||||
#[serde(default)]
|
||||
pub disable_oom_killer: bool,
|
||||
pub oom_score_adj: Option<i32>,
|
||||
pub memory: Option<LinuxMemory>,
|
||||
pub cpu: Option<LinuxCPU>,
|
||||
pub pids: Option<LinuxPids>,
|
||||
#[serde(rename = "blockIO")]
|
||||
pub block_io: Option<LinuxBlockIO>,
|
||||
#[serde(default)]
|
||||
pub hugepage_limits: Vec<LinuxHugepageLimit>,
|
||||
pub network: Option<LinuxNetwork>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum LinuxNamespaceType {
|
||||
Mount = 0x00020000,
|
||||
Cgroup = 0x02000000,
|
||||
Uts = 0x04000000,
|
||||
Ipc = 0x08000000,
|
||||
User = 0x10000000,
|
||||
Pid = 0x20000000,
|
||||
Network = 0x40000000,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LinuxNamespace {
|
||||
#[serde(rename = "type")]
|
||||
pub typ: LinuxNamespaceType,
|
||||
#[serde(default)]
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinuxDevice {
|
||||
#[serde(default)]
|
||||
pub path: String,
|
||||
#[serde(rename = "type")]
|
||||
pub typ: LinuxDeviceType,
|
||||
#[serde(default)]
|
||||
pub major: u64,
|
||||
#[serde(default)]
|
||||
pub minor: u64,
|
||||
pub file_mode: Option<u32>,
|
||||
pub uid: Option<u32>,
|
||||
pub gid: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[repr(u32)]
|
||||
pub enum LinuxSeccompAction {
|
||||
ScmpActKill = 0x00000000,
|
||||
ScmpActTrap = 0x00030000,
|
||||
ScmpActErrno = 0x00050001,
|
||||
ScmpActTrace = 0x7ff00001,
|
||||
ScmpActAllow = 0x7fff0000,
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_clike_unportable_variant)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum Arch {
|
||||
ScmpArchNative = 0x00000000,
|
||||
ScmpArchX86 = 0x40000003,
|
||||
ScmpArchX86_64 = 0xc000003e,
|
||||
ScmpArchX32 = 0x4000003e,
|
||||
ScmpArchArm = 0x40000028,
|
||||
ScmpArchAarch64 = 0xc00000b7,
|
||||
ScmpArchMips = 0x00000008,
|
||||
ScmpArchMips64 = 0x80000008,
|
||||
ScmpArchMips64n32 = 0xa0000008,
|
||||
ScmpArchMipsel = 0x40000008,
|
||||
ScmpArchMipsel64 = 0xc0000008,
|
||||
ScmpArchMipsel64n32 = 0xe0000008,
|
||||
ScmpArchPpc = 0x00000014,
|
||||
ScmpArchPpc64 = 0x80000015,
|
||||
ScmpArchPpc64le = 0xc0000015,
|
||||
ScmpArchS390 = 0x00000016,
|
||||
ScmpArchS390x = 0x80000016,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[repr(u32)]
|
||||
pub enum LinuxSeccompOperator {
|
||||
ScmpCmpNe = 1,
|
||||
ScmpCmpLt = 2,
|
||||
ScmpCmpLe = 3,
|
||||
ScmpCmpEq = 4,
|
||||
ScmpCmpGe = 5,
|
||||
ScmpCmpGt = 6,
|
||||
ScmpCmpMaskedEq = 7,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Linux {
|
||||
#[serde(default)]
|
||||
pub uid_mappings: Vec<LinuxIDMapping>,
|
||||
#[serde(default)]
|
||||
pub gid_mappings: Vec<LinuxIDMapping>,
|
||||
#[serde(default)]
|
||||
pub sysctl: HashMap<String, String>,
|
||||
pub resources: Option<LinuxResources>,
|
||||
#[serde(default)]
|
||||
pub cgroups_path: String,
|
||||
#[serde(default)]
|
||||
pub namespaces: Vec<LinuxNamespace>,
|
||||
#[serde(default)]
|
||||
pub devices: Vec<LinuxDevice>,
|
||||
#[serde(default)]
|
||||
pub rootfs_propagation: String,
|
||||
#[serde(default)]
|
||||
pub masked_paths: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub readonly_paths: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub mount_label: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Spec {
|
||||
#[serde(default, rename = "ociVersion")]
|
||||
pub version: String,
|
||||
pub platform: Option<Platform>,
|
||||
pub process: Process,
|
||||
pub root: Root,
|
||||
#[serde(default)]
|
||||
pub hostname: String,
|
||||
#[serde(default)]
|
||||
pub mounts: Vec<Mount>,
|
||||
#[serde(default)]
|
||||
pub annotations: HashMap<String, String>,
|
||||
pub linux: Option<Linux>,
|
||||
}
|
||||
|
||||
impl Spec {
|
||||
pub fn load(path: &str) -> Result<Self> {
|
||||
let file = File::open(path)?;
|
||||
let mut spec: Spec = serde_json::from_reader(&file)?;
|
||||
spec.root.path = std::fs::canonicalize(spec.root.path)?;
|
||||
Ok(spec)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Clap;
|
||||
use nix::unistd;
|
||||
|
||||
use crate::container::{Container, ContainerStatus};
|
||||
use crate::notify_socket::NotifySocket;
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Start {
|
||||
pub container_id: String,
|
||||
}
|
||||
|
||||
impl Start {
|
||||
pub fn exec(&self, root_path: PathBuf) -> Result<()> {
|
||||
let container_root = root_path.join(&self.container_id);
|
||||
if !container_root.exists() {
|
||||
bail!("{} doesn't exists.", self.container_id)
|
||||
}
|
||||
let container = Container::load(container_root)?.refresh_status()?;
|
||||
if !container.can_start() {
|
||||
let err_msg = format!(
|
||||
"{} counld not be started because it was {:?}",
|
||||
container.id(),
|
||||
container.status()
|
||||
);
|
||||
log::error!("{}", err_msg);
|
||||
bail!(err_msg);
|
||||
}
|
||||
|
||||
unistd::chdir(container.root.as_os_str())?;
|
||||
|
||||
let mut notify_socket = NotifySocket::new(&container.root)?;
|
||||
notify_socket.notify_container_start()?;
|
||||
|
||||
container.update_status(ContainerStatus::Running)?.save()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use anyhow::Result;
|
||||
use nix::unistd::dup2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileDescriptor(RawFd);
|
||||
|
||||
const STDIN: i32 = 0;
|
||||
const STDOUT: i32 = 1;
|
||||
const STDERR: i32 = 2;
|
||||
|
||||
// impl Drop for FileDescriptor {
|
||||
// fn drop(&mut self) {
|
||||
// close(self.0).expect("FileDescriptor close failed.")
|
||||
// }
|
||||
// }
|
||||
|
||||
impl AsRawFd for FileDescriptor {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for FileDescriptor {
|
||||
fn from(rawfd: u8) -> Self {
|
||||
FileDescriptor(RawFd::from(rawfd))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawFd> for FileDescriptor {
|
||||
fn from(fd: RawFd) -> Self {
|
||||
FileDescriptor(fd)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect_stdio(
|
||||
stdin: &FileDescriptor,
|
||||
stdout: &FileDescriptor,
|
||||
stderr: &FileDescriptor,
|
||||
) -> Result<()> {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
dup2(stdin.as_raw_fd(), STDIN)?;
|
||||
dup2(stdout.as_raw_fd(), STDOUT)?;
|
||||
// FIXME: Rarely does it fail.
|
||||
// error message: `Error: Resource temporarily unavailable (os error 11)`
|
||||
dup2(stderr.as_raw_fd(), STDERR)?;
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
use std::os::unix::fs::symlink;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use nix::errno::Errno;
|
||||
use nix::fcntl;
|
||||
use nix::sys::socket;
|
||||
use nix::sys::stat;
|
||||
use nix::unistd::{close, setsid};
|
||||
|
||||
use crate::stdio;
|
||||
use crate::stdio::FileDescriptor;
|
||||
|
||||
pub fn ready(console_fd: FileDescriptor) -> Result<()> {
|
||||
let openpty_result = nix::pty::openpty(None, None)?;
|
||||
let data: &[u8] = b"/dev/ptmx";
|
||||
let iov = [nix::sys::uio::IoVec::from_slice(data)];
|
||||
let fds = [openpty_result.master];
|
||||
let cmsg = socket::ControlMessage::ScmRights(&fds);
|
||||
socket::sendmsg(
|
||||
console_fd.as_raw_fd(),
|
||||
&iov,
|
||||
&[cmsg],
|
||||
socket::MsgFlags::empty(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
setsid()?;
|
||||
if unsafe { libc::ioctl(openpty_result.slave, libc::TIOCSCTTY) } < 0 {
|
||||
log::warn!("could not TIOCSCTTY");
|
||||
};
|
||||
let slave = FileDescriptor::from(openpty_result.slave);
|
||||
stdio::connect_stdio(&slave, &slave, &slave).expect("could not dup tty to stderr");
|
||||
close(console_fd.as_raw_fd())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_console_sockets(
|
||||
container_dir: &PathBuf,
|
||||
console_socket: &str,
|
||||
) -> Result<(FileDescriptor, FileDescriptor)> {
|
||||
let csocket = "console-stdout";
|
||||
symlink(console_socket, container_dir.join(csocket))?;
|
||||
|
||||
let mut csocketfd = socket::socket(
|
||||
socket::AddressFamily::Unix,
|
||||
socket::SockType::Stream,
|
||||
socket::SockFlag::empty(),
|
||||
None,
|
||||
)?;
|
||||
csocketfd = match socket::connect(
|
||||
csocketfd,
|
||||
&socket::SockAddr::Unix(socket::UnixAddr::new(&*csocket)?),
|
||||
) {
|
||||
Err(e) => {
|
||||
if e != ::nix::Error::Sys(Errno::ENOENT) {
|
||||
bail!("failed to open {}", csocket);
|
||||
}
|
||||
-1
|
||||
}
|
||||
Ok(()) => csocketfd,
|
||||
};
|
||||
let console = "console";
|
||||
let consolefd = match fcntl::open(
|
||||
&*console,
|
||||
fcntl::OFlag::O_NOCTTY | fcntl::OFlag::O_RDWR,
|
||||
stat::Mode::empty(),
|
||||
) {
|
||||
Err(e) => {
|
||||
if e != ::nix::Error::Sys(Errno::ENOENT) {
|
||||
bail!("failed to open {}", console);
|
||||
}
|
||||
-1
|
||||
}
|
||||
Ok(fd) => fd,
|
||||
};
|
||||
Ok((csocketfd.into(), consolefd.into()))
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
use std::ffi::CString;
|
||||
|
||||
use anyhow::Result;
|
||||
use nix::unistd;
|
||||
|
||||
pub fn do_exec(path: &str, args: &[String]) -> Result<()> {
|
||||
let p = CString::new(path.to_string()).unwrap();
|
||||
let a: Vec<CString> = args
|
||||
.iter()
|
||||
.map(|s| CString::new(s.to_string()).unwrap_or_default())
|
||||
.collect();
|
||||
|
||||
unistd::execvp(&p, &a)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO implement
|
||||
pub fn set_name(_name: &str) -> Result<()> {
|
||||
// prctl::set_name(name).expect("set name failed.");
|
||||
// unsafe {
|
||||
// let init = std::ffi::CString::new(name).expect("invalid process name");
|
||||
// // let len = std::ffi::CStr::from_ptr(*ARGV).to_bytes().len();
|
||||
// let len = std::ffi::CStr::from_ptr(0 as *mut i8).to_bytes().len();
|
||||
// // after fork, ARGV points to the thread's local
|
||||
// // copy of arg0.
|
||||
// // libc::strncpy(*ARGV, init.as_ptr(), len);
|
||||
// libc::strncpy(0 as *mut i8, init.as_ptr(), len);
|
||||
// // no need to set the final character to 0 since
|
||||
// // the initial string was already null-terminated.
|
||||
// }
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue