1
0
Fork 0
mirror of https://github.com/containers/youki synced 2024-04-20 00:54:12 +02:00

first commit!

This commit is contained in:
utam0k 2021-03-27 20:08:13 +09:00
commit a08887d59a
32 changed files with 2793 additions and 0 deletions

2
.cargo/config Normal file
View File

@ -0,0 +1,2 @@
[build]
target = "x86_64-unknown-linux-gnu"

13
.devcontainer/Dockerfile Normal file
View File

@ -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"]

View File

@ -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"
]
}
}

4
.devcontainer/scripts/init.sh Executable file
View File

@ -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

View File

@ -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

34
.devcontainer/scripts/test.sh Executable file
View File

@ -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

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
.vagrant/

647
Cargo.lock generated Normal file
View File

@ -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",
]

20
Cargo.toml Normal file
View File

@ -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"

70
README.md Normal file
View File

@ -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).

30
src/cond.rs Normal file
View File

@ -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(())
}
}

110
src/container/container.rs Normal file
View File

@ -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,
})
}
}

5
src/container/mod.rs Normal file
View File

@ -0,0 +1,5 @@
#[allow(clippy::module_inception)]
mod container;
mod state;
pub use container::Container;
pub use state::{ContainerStatus, State};

97
src/container/state.rs Normal file
View File

@ -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)
}
}

169
src/create.rs Normal file
View File

@ -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(())
}

13
src/lib.rs Normal file
View File

@ -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;

113
src/logger.rs Normal file
View File

@ -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");
}
}
}

126
src/main.rs Normal file
View File

@ -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);
}
}
}

59
src/notify_socket.rs Normal file
View File

@ -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(())
}
}

11
src/process.rs Normal file
View File

@ -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),
}

82
src/process/child.rs Normal file
View File

@ -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.")
}
}

93
src/process/fork.rs Normal file
View File

@ -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!"),
}
}
}
}
}

30
src/process/init.rs Normal file
View File

@ -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(())
}
}

15
src/process/message.rs Normal file
View File

@ -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."),
}
}
}

47
src/process/parent.rs Normal file
View File

@ -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.")
}
}

361
src/rootfs.rs Normal file
View File

@ -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(","))
}

40
src/signal.rs Normal file
View File

@ -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},
})
}

363
src/spec.rs Normal file
View File

@ -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)
}
}

40
src/start.rs Normal file
View File

@ -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(())
}
}

49
src/stdio.rs Normal file
View File

@ -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(())
}

79
src/tty.rs Normal file
View File

@ -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()))
}

32
src/utils.rs Normal file
View File

@ -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(())
}