Compare commits
2 Commits
main
...
elf-packin
Author | SHA1 | Date | |
---|---|---|---|
bd0d114145 | |||
f07dab1b4b |
235
Cargo.lock
generated
235
Cargo.lock
generated
@ -2,6 +2,23 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
@ -11,12 +28,6 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.57"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@ -35,10 +46,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "bincode"
|
||||||
version = "0.13.0"
|
version = "1.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@ -75,9 +89,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.73"
|
version = "1.0.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@ -120,6 +134,27 @@ dependencies = [
|
|||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clone-shim"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"clap 3.1.15",
|
||||||
|
"close_fds",
|
||||||
|
"criterion",
|
||||||
|
"env_logger",
|
||||||
|
"exitcode",
|
||||||
|
"ipnetwork",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"nix",
|
||||||
|
"object",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "close_fds"
|
name = "close_fds"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -130,6 +165,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion"
|
name = "criterion"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@ -267,6 +311,29 @@ dependencies = [
|
|||||||
"instant",
|
"instant",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crc32fast",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "1.8.2"
|
version = "1.8.2"
|
||||||
@ -278,6 +345,9 @@ name = "hashbrown"
|
|||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
@ -288,12 +358,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "httparse"
|
|
||||||
version = "1.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@ -366,9 +430,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.126"
|
version = "0.2.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
@ -395,12 +459,22 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "miniz_oxide"
|
||||||
version = "0.24.1"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9"
|
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"memoffset",
|
"memoffset",
|
||||||
@ -426,10 +500,23 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "object"
|
||||||
version = "1.12.0"
|
version = "0.28.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"flate2",
|
||||||
|
"hashbrown",
|
||||||
|
"indexmap",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oorandom"
|
name = "oorandom"
|
||||||
@ -555,21 +642,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ring"
|
|
||||||
version = "0.16.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"spin",
|
|
||||||
"untrusted",
|
|
||||||
"web-sys",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -579,27 +651,6 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls"
|
|
||||||
version = "0.20.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"ring",
|
|
||||||
"sct",
|
|
||||||
"webpki",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-pemfile"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
@ -621,16 +672,6 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sct"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
|
||||||
dependencies = [
|
|
||||||
"ring",
|
|
||||||
"untrusted",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@ -678,12 +719,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spin"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -782,34 +817,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "version_check"
|
||||||
version = "0.7.1"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "void-orchestrator"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"clap 3.1.15",
|
|
||||||
"close_fds",
|
|
||||||
"criterion",
|
|
||||||
"env_logger",
|
|
||||||
"exitcode",
|
|
||||||
"httparse",
|
|
||||||
"ipnetwork",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"nix",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pemfile",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
@ -822,6 +833,12 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.79"
|
version = "0.2.79"
|
||||||
@ -886,16 +903,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "webpki"
|
|
||||||
version = "0.22.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
|
||||||
dependencies = [
|
|
||||||
"ring",
|
|
||||||
"untrusted",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
13
Cargo.toml
13
Cargo.toml
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "void-orchestrator"
|
name = "clone-shim"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@ -14,23 +14,18 @@ exitcode = "1"
|
|||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
bincode = "1.3"
|
||||||
ipnetwork = "0.18"
|
ipnetwork = "0.18"
|
||||||
|
|
||||||
libc = "0.2.117"
|
libc = "0.2.117"
|
||||||
nix = "0.24.1"
|
nix = "0.23.1"
|
||||||
|
|
||||||
close_fds = "0.3.2"
|
close_fds = "0.3.2"
|
||||||
tempfile = "3.3"
|
tempfile = "3.3"
|
||||||
|
object = { version = "0.28", features = ["write_core"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
anyhow = "1"
|
|
||||||
|
|
||||||
# examples/tls
|
|
||||||
httparse = "1"
|
|
||||||
rustls = "0.20"
|
|
||||||
rustls-pemfile = "1"
|
|
||||||
lazy_static = "1"
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "clone3"
|
name = "clone3"
|
||||||
|
20
README.md
20
README.md
@ -2,16 +2,6 @@
|
|||||||
|
|
||||||
## Running the examples
|
## Running the examples
|
||||||
|
|
||||||
### examples/fib
|
|
||||||
|
|
||||||
The fib example performs fibonacci trivially on a fixed number. It is the most basic example of a process that requires no privilege, excluding `Stdout` to print the result.
|
|
||||||
|
|
||||||
To run this example:
|
|
||||||
|
|
||||||
cargo build
|
|
||||||
cargo build --example fib
|
|
||||||
target/debug/clone-shim -s examples/fib/spec.json target/debug/examples/fib
|
|
||||||
|
|
||||||
### examples/basic
|
### examples/basic
|
||||||
|
|
||||||
The basic example instructs the shim to spawn two processes, each of which writes "hello from main{1,2}!" to stdout.
|
The basic example instructs the shim to spawn two processes, each of which writes "hello from main{1,2}!" to stdout.
|
||||||
@ -32,16 +22,6 @@ To run this example:
|
|||||||
cargo build --example pipes
|
cargo build --example pipes
|
||||||
target/debug/clone-shim -s examples/pipes/spec.json target/debug/examples/pipes
|
target/debug/clone-shim -s examples/pipes/spec.json target/debug/examples/pipes
|
||||||
|
|
||||||
### examples/pipes
|
|
||||||
|
|
||||||
The pipes example shows some of the power of the shim by using pipes. The process "pipe_sender" sends two messages down a pipe that it's given by the shim. These two messages each spawn a completely isolated process, "pipe_receiver", that receives that message.
|
|
||||||
|
|
||||||
To run this example:
|
|
||||||
|
|
||||||
cargo build
|
|
||||||
cargo build --example tls
|
|
||||||
target/debug/clone-shim -s examples/tls/spec.json target/debug/examples/tls
|
|
||||||
|
|
||||||
## Debugging the shim
|
## Debugging the shim
|
||||||
|
|
||||||
The shim can be debugged as with most processes, but it is exceptionally forky. Breaking before a clone in `rust-gdb` then running `set follow-fork-mode child` is often necessary. The best approach is to go in with a plan of attack.
|
The shim can be debugged as with most processes, but it is exceptionally forky. Breaking before a clone in `rust-gdb` then running `set follow-fork-mode child` is often necessary. The best approach is to go in with a plan of attack.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use void_orchestrator::clone::{clone3, CloneArgs, CloneFlags};
|
use clone_shim::clone::{clone3, CloneArgs, CloneFlags};
|
||||||
|
|
||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
println!("fib(1) = {}", fib(1));
|
|
||||||
println!("fib(7) = {}", fib(7));
|
|
||||||
println!("fib(19) = {}", fib(19));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fib(i: u64) -> u64 {
|
|
||||||
let (mut a, mut b) = (0, 1);
|
|
||||||
|
|
||||||
for _ in 0..i {
|
|
||||||
(a, b) = (b, a + b);
|
|
||||||
}
|
|
||||||
|
|
||||||
a
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"entrypoints": {
|
|
||||||
"fib": {
|
|
||||||
"environment": [
|
|
||||||
"Stdout",
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
|
|
||||||
"environment_path": "/lib/libgcc_s.so.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib/x86_64-linux-gnu/libc.so.6",
|
|
||||||
"environment_path": "/lib/libc.so.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib64/ld-linux-x86-64.so.2",
|
|
||||||
"environment_path": "/lib64/ld-linux-x86-64.so.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
use std::fs::OpenOptions;
|
|
||||||
use std::io::{self, ErrorKind, Read, Write};
|
|
||||||
use std::os::unix::net::UnixStream;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
pub(super) fn handler(mut stream: UnixStream) -> i32 {
|
|
||||||
println!("entered http handler");
|
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
let mut buf_len = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
buf.resize_with(buf_len + 4096, Default::default);
|
|
||||||
|
|
||||||
let read_bytes = stream.read(&mut buf[buf_len..]).unwrap();
|
|
||||||
buf_len += read_bytes;
|
|
||||||
|
|
||||||
if read_bytes == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut headers = [httparse::EMPTY_HEADER; 64];
|
|
||||||
let mut req = httparse::Request::new(&mut headers);
|
|
||||||
let result = req.parse(&buf).unwrap();
|
|
||||||
|
|
||||||
if result.is_partial() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let filename = if req.method != Some("GET") {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
req.path
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(filename) = filename {
|
|
||||||
if try_serve_file(&mut stream, filename).unwrap() {
|
|
||||||
return exitcode::OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let status_line = "HTTP/1.1 404 NOT FOUND";
|
|
||||||
let contents = "file not found\n";
|
|
||||||
|
|
||||||
let response = format!(
|
|
||||||
"{}\r\nContent-Length: {}\r\n\r\n{}",
|
|
||||||
status_line,
|
|
||||||
contents.len(),
|
|
||||||
contents
|
|
||||||
);
|
|
||||||
|
|
||||||
stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
exitcode::OK
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_serve_file(stream: &mut impl io::Write, filename: &str) -> io::Result<bool> {
|
|
||||||
let mut fd = match OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.open(PathBuf::from("/var/www/html/").join(filename.strip_prefix('/').unwrap_or(filename)))
|
|
||||||
{
|
|
||||||
Ok(fd) => fd,
|
|
||||||
Err(e) => {
|
|
||||||
if e.kind() == ErrorKind::NotFound {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let status_line = "HTTP/1.1 200 OK";
|
|
||||||
|
|
||||||
let response_header = format!(
|
|
||||||
"{}\r\nContent-Length: {}\r\n\r\n",
|
|
||||||
status_line,
|
|
||||||
fd.metadata()?.len(),
|
|
||||||
);
|
|
||||||
|
|
||||||
stream.write_all(response_header.as_bytes())?;
|
|
||||||
io::copy(&mut fd, stream)?;
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::ErrorKind;
|
|
||||||
use std::net::TcpListener;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
use nix::poll::{poll, PollFd, PollFlags};
|
|
||||||
use nix::sys::signal::{signal, SigHandler, Signal};
|
|
||||||
use nix::Error as NixError;
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref RUNNING: AtomicBool = AtomicBool::new(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handler(tls_handler_trigger: File, listener: TcpListener) -> i32 {
|
|
||||||
println!("connection_listener entered");
|
|
||||||
|
|
||||||
// SAFETY: only unsafe if you use the result
|
|
||||||
unsafe { signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)) }.unwrap();
|
|
||||||
|
|
||||||
listener.set_nonblocking(true).unwrap();
|
|
||||||
|
|
||||||
let mut to_poll = [PollFd::new(listener.as_raw_fd(), PollFlags::POLLIN)];
|
|
||||||
while RUNNING.load(Ordering::Relaxed) {
|
|
||||||
if let Err(e) = poll(&mut to_poll, 1000) {
|
|
||||||
if e == NixError::EINTR {
|
|
||||||
continue; // timed out
|
|
||||||
}
|
|
||||||
Err(e).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
let stream = match listener.accept() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
if e.kind() != ErrorKind::WouldBlock {
|
|
||||||
Err(e).unwrap()
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("received a new connection");
|
|
||||||
super::tls_handler(&tls_handler_trigger, stream.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
exitcode::OK
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn handle_sigint(signal: libc::c_int) {
|
|
||||||
let signal = Signal::try_from(signal).unwrap();
|
|
||||||
RUNNING.store(signal != Signal::SIGINT, Ordering::Relaxed);
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
mod http;
|
|
||||||
mod listener;
|
|
||||||
mod tls;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::net::{TcpListener, TcpStream};
|
|
||||||
use std::os::unix::net::UnixStream;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
match std::env::args().next() {
|
|
||||||
Some(s) => match s.as_str() {
|
|
||||||
"connection_listener" => connection_listener_entrypoint(),
|
|
||||||
"tls_handler" => tls_handler_entrypoint(),
|
|
||||||
"http_handler" => http_handler_entrypoint(),
|
|
||||||
|
|
||||||
_ => unimplemented!(),
|
|
||||||
},
|
|
||||||
None => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connection_listener_entrypoint() {
|
|
||||||
// imports
|
|
||||||
use std::os::unix::io::{FromRawFd, RawFd};
|
|
||||||
|
|
||||||
// argument parsing
|
|
||||||
let mut args = std::env::args();
|
|
||||||
|
|
||||||
let _entrypoint = args.next();
|
|
||||||
|
|
||||||
let tls_handler_trigger = args.next();
|
|
||||||
let tls_handler_trigger: RawFd = tls_handler_trigger
|
|
||||||
.expect("tls handler trigger required")
|
|
||||||
.parse()
|
|
||||||
.expect("tls handler trigger should be a file descriptor");
|
|
||||||
let tls_handler_trigger = unsafe { File::from_raw_fd(tls_handler_trigger) };
|
|
||||||
|
|
||||||
let tcp_listener = args.next();
|
|
||||||
let tcp_listener: RawFd = tcp_listener
|
|
||||||
.expect("tcp listener required")
|
|
||||||
.parse()
|
|
||||||
.expect("tcp listener should be a file descriptor");
|
|
||||||
let tcp_listener = unsafe { TcpListener::from_raw_fd(tcp_listener) };
|
|
||||||
|
|
||||||
// run function
|
|
||||||
std::process::exit(listener::handler(tls_handler_trigger, tcp_listener));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tls_handler(trigger_socket: &File, stream: TcpStream) {
|
|
||||||
// imports
|
|
||||||
use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags};
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
// send file descriptor(s)
|
|
||||||
let sockfd = trigger_socket.as_raw_fd();
|
|
||||||
let fds = [stream.as_raw_fd()];
|
|
||||||
|
|
||||||
sendmsg::<()>(
|
|
||||||
sockfd,
|
|
||||||
&[],
|
|
||||||
&[ControlMessage::ScmRights(&fds)],
|
|
||||||
MsgFlags::empty(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tls_handler_entrypoint() {
|
|
||||||
// imports
|
|
||||||
use std::os::unix::io::{FromRawFd, RawFd};
|
|
||||||
|
|
||||||
// argument parsing
|
|
||||||
let mut args = std::env::args();
|
|
||||||
|
|
||||||
let _entrypoint = args.next();
|
|
||||||
|
|
||||||
let http_handler_trigger = args.next();
|
|
||||||
let http_handler_trigger: RawFd = http_handler_trigger
|
|
||||||
.expect("http handler trigger required")
|
|
||||||
.parse()
|
|
||||||
.expect("http handler trigger should be a file descriptor");
|
|
||||||
let http_handler_trigger = unsafe { File::from_raw_fd(http_handler_trigger) };
|
|
||||||
|
|
||||||
let tls_cert_file = args.next();
|
|
||||||
let tls_cert_file: RawFd = tls_cert_file
|
|
||||||
.expect("tls cert file required")
|
|
||||||
.parse()
|
|
||||||
.expect("tls cert file should be a file descriptor");
|
|
||||||
let tls_cert_file = unsafe { File::from_raw_fd(tls_cert_file) };
|
|
||||||
|
|
||||||
let tls_key_file = args.next();
|
|
||||||
let tls_key_file: RawFd = tls_key_file
|
|
||||||
.expect("tls key file required")
|
|
||||||
.parse()
|
|
||||||
.expect("tls key file should be a file descriptor");
|
|
||||||
let tls_key_file = unsafe { File::from_raw_fd(tls_key_file) };
|
|
||||||
|
|
||||||
let stream = args.next();
|
|
||||||
let stream: RawFd = stream
|
|
||||||
.expect("request stream required")
|
|
||||||
.parse()
|
|
||||||
.expect("request stream should be a file descriptor");
|
|
||||||
let stream = unsafe { TcpStream::from_raw_fd(stream) };
|
|
||||||
|
|
||||||
std::process::exit(tls::handler(
|
|
||||||
http_handler_trigger,
|
|
||||||
tls_cert_file,
|
|
||||||
tls_key_file,
|
|
||||||
stream,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn http_handler(trigger_socket: &File, stream: UnixStream) {
|
|
||||||
// imports
|
|
||||||
use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags};
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
// send file descriptor(s)
|
|
||||||
let sockfd = trigger_socket.as_raw_fd();
|
|
||||||
let fds = [stream.as_raw_fd()];
|
|
||||||
|
|
||||||
sendmsg::<()>(
|
|
||||||
sockfd,
|
|
||||||
&[],
|
|
||||||
&[ControlMessage::ScmRights(&fds)],
|
|
||||||
MsgFlags::empty(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn http_handler_entrypoint() {
|
|
||||||
// imports
|
|
||||||
use std::os::unix::io::{FromRawFd, RawFd};
|
|
||||||
|
|
||||||
// argument parsing
|
|
||||||
let mut args = std::env::args();
|
|
||||||
|
|
||||||
let _entrypoint = args.next();
|
|
||||||
|
|
||||||
let stream = args.next();
|
|
||||||
let stream: RawFd = stream
|
|
||||||
.expect("request stream required")
|
|
||||||
.parse()
|
|
||||||
.expect("request stream should be a file descriptor");
|
|
||||||
let stream = unsafe { UnixStream::from_raw_fd(stream) };
|
|
||||||
|
|
||||||
std::process::exit(http::handler(stream));
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
{
|
|
||||||
"entrypoints": {
|
|
||||||
"connection_listener": {
|
|
||||||
"args": [
|
|
||||||
"Entrypoint",
|
|
||||||
{
|
|
||||||
"FileSocket": {
|
|
||||||
"Tx": "tls"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"TcpListener": {
|
|
||||||
"addr": "0.0.0.0:8443"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"environment": [
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
|
|
||||||
"environment_path": "/lib/libgcc_s.so.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib/x86_64-linux-gnu/libc.so.6",
|
|
||||||
"environment_path": "/lib/libc.so.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib64/ld-linux-x86-64.so.2",
|
|
||||||
"environment_path": "/lib64/ld-linux-x86-64.so.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"tls_handler": {
|
|
||||||
"trigger": {
|
|
||||||
"FileSocket": "tls"
|
|
||||||
},
|
|
||||||
"args": [
|
|
||||||
"Entrypoint",
|
|
||||||
{
|
|
||||||
"FileSocket": {
|
|
||||||
"Tx": "http"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"File": "/etc/ssl/certs/example.com.pem"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"File": "/etc/ssl/private/example.com.key"
|
|
||||||
},
|
|
||||||
"Trigger"
|
|
||||||
],
|
|
||||||
"environment": [
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
|
|
||||||
"environment_path": "/lib/libgcc_s.so.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib/x86_64-linux-gnu/libc.so.6",
|
|
||||||
"environment_path": "/lib/libc.so.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib64/ld-linux-x86-64.so.2",
|
|
||||||
"environment_path": "/lib64/ld-linux-x86-64.so.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"http_handler": {
|
|
||||||
"trigger": {
|
|
||||||
"FileSocket": "http"
|
|
||||||
},
|
|
||||||
"args": [
|
|
||||||
"Entrypoint",
|
|
||||||
"Trigger"
|
|
||||||
],
|
|
||||||
"environment": [
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/var/www/html",
|
|
||||||
"environment_path": "/var/www/html"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
|
|
||||||
"environment_path": "/lib/libgcc_s.so.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib/x86_64-linux-gnu/libc.so.6",
|
|
||||||
"environment_path": "/lib/libc.so.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Filesystem": {
|
|
||||||
"host_path": "/lib64/ld-linux-x86-64.so.2",
|
|
||||||
"environment_path": "/lib64/ld-linux-x86-64.so.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,176 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::{self, BufReader, ErrorKind, Read, Write};
|
|
||||||
use std::net::TcpStream;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use std::os::unix::net::UnixStream;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use nix::poll::{poll, PollFd, PollFlags};
|
|
||||||
|
|
||||||
use rustls::ServerConnection;
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 4096;
|
|
||||||
|
|
||||||
pub(crate) fn handler(
|
|
||||||
http_trigger_socket: File,
|
|
||||||
cert: File,
|
|
||||||
key: File,
|
|
||||||
mut stream: TcpStream,
|
|
||||||
) -> i32 {
|
|
||||||
let (mut socket, far_socket) = UnixStream::pair().unwrap();
|
|
||||||
|
|
||||||
let config = make_config(cert, key);
|
|
||||||
let mut tls_conn = rustls::ServerConnection::new(config).unwrap();
|
|
||||||
|
|
||||||
super::http_handler(&http_trigger_socket, far_socket);
|
|
||||||
|
|
||||||
stream.set_nonblocking(true).unwrap();
|
|
||||||
socket.set_nonblocking(true).unwrap();
|
|
||||||
|
|
||||||
let mut to_poll = [
|
|
||||||
PollFd::new(stream.as_raw_fd(), PollFlags::POLLIN),
|
|
||||||
PollFd::new(socket.as_raw_fd(), PollFlags::POLLIN),
|
|
||||||
];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
println!("starting polling");
|
|
||||||
poll(&mut to_poll, -1).unwrap();
|
|
||||||
|
|
||||||
if let Some(events) = to_poll[0].revents() {
|
|
||||||
if events.contains(PollFlags::POLLIN) {
|
|
||||||
handle_encrypted_data(&mut tls_conn, &mut stream, &mut socket).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(events) = to_poll[1].revents() {
|
|
||||||
if events.contains(PollFlags::POLLIN) {
|
|
||||||
handle_new_data(&mut tls_conn, &mut socket, &mut stream).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if events.contains(PollFlags::POLLHUP) {
|
|
||||||
println!("response writer hung up, exiting");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tls_conn.send_close_notify();
|
|
||||||
tls_conn.write_tls(&mut stream).unwrap();
|
|
||||||
|
|
||||||
exitcode::OK
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_encrypted_data(
|
|
||||||
tls_conn: &mut ServerConnection,
|
|
||||||
stream: &mut (impl Read + Write),
|
|
||||||
socket: &mut impl Write,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
println!("handling newly received encrypted data");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let read = match tls_conn.read_tls(stream) {
|
|
||||||
Err(e) => {
|
|
||||||
if e.kind() == ErrorKind::WouldBlock {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
return Err(e).context("io error reading from stream");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(n) => n,
|
|
||||||
};
|
|
||||||
|
|
||||||
if read == 0 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let process_result = tls_conn.process_new_packets();
|
|
||||||
let write_tls_result = tls_conn.write_tls(stream);
|
|
||||||
|
|
||||||
let io_state = process_result.context("tls processing failure")?;
|
|
||||||
write_tls_result.context("tls write failure")?;
|
|
||||||
|
|
||||||
if io_state.plaintext_bytes_to_read() > 0 {
|
|
||||||
let mut reader = tls_conn
|
|
||||||
.reader()
|
|
||||||
.take(io_state.plaintext_bytes_to_read() as u64);
|
|
||||||
|
|
||||||
std::io::copy(&mut reader, socket)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_new_data(
|
|
||||||
tls_conn: &mut ServerConnection,
|
|
||||||
socket: &mut impl Read,
|
|
||||||
stream: &mut impl Write,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
println!("handling new data to encrypt");
|
|
||||||
|
|
||||||
let mut buf = [0_u8; BUFFER_SIZE];
|
|
||||||
loop {
|
|
||||||
let read = non_blocking_read(socket, &mut buf)?;
|
|
||||||
if read == 0 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
tls_conn.writer().write_all(&buf[0..read])?;
|
|
||||||
tls_conn.write_tls(stream)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn non_blocking_read(reader: &mut impl io::Read, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
match reader.read(buf) {
|
|
||||||
Err(e) => {
|
|
||||||
if e.kind() == ErrorKind::WouldBlock {
|
|
||||||
Ok(0)
|
|
||||||
} else {
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(n) => Ok(n),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_config(cert: File, key: File) -> Arc<rustls::ServerConfig> {
|
|
||||||
let certs = load_certs(cert);
|
|
||||||
let privkey = load_private_key(key);
|
|
||||||
|
|
||||||
let config = rustls::ServerConfig::builder()
|
|
||||||
.with_safe_default_cipher_suites()
|
|
||||||
.with_safe_default_kx_groups()
|
|
||||||
.with_safe_default_protocol_versions()
|
|
||||||
.expect("inconsistent cipher-suites/versions specified")
|
|
||||||
.with_no_client_auth()
|
|
||||||
.with_single_cert(certs, privkey)
|
|
||||||
.expect("bad certificates/private key");
|
|
||||||
|
|
||||||
Arc::new(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_certs(certfile: File) -> Vec<rustls::Certificate> {
|
|
||||||
let mut reader = BufReader::new(certfile);
|
|
||||||
|
|
||||||
rustls_pemfile::certs(&mut reader)
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.map(|v| rustls::Certificate(v.clone()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_private_key(keyfile: File) -> rustls::PrivateKey {
|
|
||||||
let mut reader = BufReader::new(keyfile);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") {
|
|
||||||
Some(rustls_pemfile::Item::RSAKey(key)) => return rustls::PrivateKey(key),
|
|
||||||
Some(rustls_pemfile::Item::PKCS8Key(key)) => return rustls::PrivateKey(key),
|
|
||||||
Some(rustls_pemfile::Item::ECKey(key)) => return rustls::PrivateKey(key),
|
|
||||||
None => break,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!("no keys found (encrypted keys not supported)");
|
|
||||||
}
|
|
16
src/error.rs
16
src/error.rs
@ -15,13 +15,25 @@ pub enum Error {
|
|||||||
#[error("json: {0}")]
|
#[error("json: {0}")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("bincode: {0}")]
|
||||||
|
Bincode(#[from] bincode::Error),
|
||||||
|
|
||||||
|
#[error("elf: read: {0}")]
|
||||||
|
ElfRead(#[from] object::read::Error),
|
||||||
|
|
||||||
|
#[error("elf: write: {0}")]
|
||||||
|
ElfWrite(#[from] object::write::Error),
|
||||||
|
|
||||||
#[error("bad pipe specification: a pipe must have exactly one reader and one writer: {0}")]
|
#[error("bad pipe specification: a pipe must have exactly one reader and one writer: {0}")]
|
||||||
BadPipe(String),
|
BadPipe(String),
|
||||||
|
|
||||||
#[error("bad socket specification: a socket must have exactly one reader and one or more writers: {0}")]
|
#[error("bad socket specification: a socket must have exactly one reader and one writer: {0}")]
|
||||||
BadFileSocket(String),
|
BadFileSocket(String),
|
||||||
|
|
||||||
#[error("bad specification type: only .json files are supported")]
|
#[error("no specification provided")]
|
||||||
|
NoSpecification,
|
||||||
|
|
||||||
|
#[error("bad specification type: only json files are supported")]
|
||||||
BadSpecType,
|
BadSpecType,
|
||||||
|
|
||||||
#[error("bad trigger argument: this entrypoint is not triggered by something with arguments")]
|
#[error("bad trigger argument: this entrypoint is not triggered by something with arguments")]
|
||||||
|
113
src/lib.rs
113
src/lib.rs
@ -2,39 +2,51 @@ use log::{debug, info};
|
|||||||
|
|
||||||
pub mod clone;
|
pub mod clone;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod pack;
|
||||||
mod spawner;
|
mod spawner;
|
||||||
mod specification;
|
mod specification;
|
||||||
mod void;
|
mod void;
|
||||||
|
|
||||||
use error::{Error, Result};
|
use error::{Error, Result};
|
||||||
use spawner::Spawner;
|
use spawner::Spawner;
|
||||||
use specification::{Environment, Specification};
|
use specification::Specification;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
use std::os::unix::io::FromRawFd;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::sys::socket;
|
use nix::sys::socket;
|
||||||
use nix::sys::wait::{waitid, Id, WaitPidFlag, WaitStatus};
|
|
||||||
use nix::unistd;
|
use nix::unistd;
|
||||||
|
pub struct PackArgs<'a> {
|
||||||
|
pub spec: &'a Path,
|
||||||
|
pub binary: &'a Path,
|
||||||
|
pub output: &'a Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pack(args: &PackArgs) -> Result<()> {
|
||||||
|
let spec: Specification = if args.spec.extension().map(|e| e == "json") == Some(true) {
|
||||||
|
let f = std::fs::File::open(args.spec)?;
|
||||||
|
Ok(serde_json::from_reader(f)?)
|
||||||
|
} else {
|
||||||
|
Err(Error::BadSpecType)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
pack::pack_binary(args.binary, &spec, args.output)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RunArgs<'a> {
|
pub struct RunArgs<'a> {
|
||||||
pub spec: Option<&'a Path>,
|
pub spec: Option<&'a Path>,
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub daemon: bool,
|
|
||||||
|
|
||||||
pub stdout: bool,
|
|
||||||
pub stderr: bool,
|
|
||||||
|
|
||||||
pub binary: &'a Path,
|
pub binary: &'a Path,
|
||||||
pub binary_args: Vec<&'a str>,
|
pub binary_args: Vec<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: &RunArgs) -> Result<i32> {
|
pub fn run(args: &RunArgs) -> Result<()> {
|
||||||
// parse the specification
|
// parse the specification
|
||||||
let mut spec: Specification = if let Some(m) = args.spec {
|
let spec: Specification = if let Some(m) = args.spec {
|
||||||
if m.extension().map(|e| e == "json") == Some(true) {
|
if m.extension().map(|e| e == "json") == Some(true) {
|
||||||
let f = std::fs::File::open(m)?;
|
let f = std::fs::File::open(m)?;
|
||||||
Ok(serde_json::from_reader(f)?)
|
Ok(serde_json::from_reader(f)?)
|
||||||
@ -42,26 +54,17 @@ pub fn run(args: &RunArgs) -> Result<i32> {
|
|||||||
Err(Error::BadSpecType)
|
Err(Error::BadSpecType)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unimplemented!("reading spec from the elf is unimplemented")
|
let spec = pack::extract_specification(args.binary)?;
|
||||||
|
if let Some(s) = spec {
|
||||||
|
Ok(s)
|
||||||
|
} else {
|
||||||
|
Err(Error::NoSpecification)
|
||||||
|
}
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
debug!("specification read: {:?}", &spec);
|
debug!("specification read: {:?}", &spec);
|
||||||
spec.validate()?;
|
spec.validate()?;
|
||||||
|
|
||||||
if args.stdout {
|
|
||||||
debug!("forwarding stdout");
|
|
||||||
for entrypoint in &mut spec.entrypoints.values_mut() {
|
|
||||||
entrypoint.environment.insert(Environment::Stdout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.stderr {
|
|
||||||
debug!("forwarding stderr");
|
|
||||||
for entrypoint in &mut spec.entrypoints.values_mut() {
|
|
||||||
entrypoint.environment.insert(Environment::Stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create all the pipes
|
// create all the pipes
|
||||||
let (pipes, _) = spec.pipes();
|
let (pipes, _) = spec.pipes();
|
||||||
let pipes = create_pipes(pipes)?;
|
let pipes = create_pipes(pipes)?;
|
||||||
@ -81,41 +84,7 @@ pub fn run(args: &RunArgs) -> Result<i32> {
|
|||||||
}
|
}
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
if args.daemon {
|
Ok(())
|
||||||
return Ok(exitcode::OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("spawned successfully, awaiting children exiting...");
|
|
||||||
let mut exit_code = exitcode::OK;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let status = match waitid(Id::All, WaitPidFlag::WEXITED) {
|
|
||||||
Ok(v) => Ok(v),
|
|
||||||
Err(nix::Error::ECHILD) => {
|
|
||||||
info!("all child processes have exited, exiting...");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(e) => Err(Error::Nix {
|
|
||||||
msg: "waitpid",
|
|
||||||
src: e,
|
|
||||||
}),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
match status {
|
|
||||||
WaitStatus::Exited(pid, code) => {
|
|
||||||
if code != exitcode::OK {
|
|
||||||
exit_code = code;
|
|
||||||
}
|
|
||||||
debug!("child {} exited with code {}", pid, code);
|
|
||||||
}
|
|
||||||
WaitStatus::Signaled(pid, sig, _coredump) => {
|
|
||||||
debug!("child {} was terminated with signal {}", pid, sig);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(exit_code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_pipes(names: Vec<&str>) -> Result<HashMap<String, PipePair>> {
|
fn create_pipes(names: Vec<&str>) -> Result<HashMap<String, PipePair>> {
|
||||||
@ -152,11 +121,10 @@ impl PipePair {
|
|||||||
src: e,
|
src: e,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// safe to create files given the successful return of pipe(2)
|
||||||
Ok(PipePair {
|
Ok(PipePair {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
// SAFETY: valid new fd as pipe2(2) returned successfully
|
|
||||||
read: Some(unsafe { File::from_raw_fd(read) }),
|
read: Some(unsafe { File::from_raw_fd(read) }),
|
||||||
// SAFETY: valid new fd as pipe2(2) returned successfully
|
|
||||||
write: Some(unsafe { File::from_raw_fd(write) }),
|
write: Some(unsafe { File::from_raw_fd(write) }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -178,7 +146,7 @@ pub struct SocketPair {
|
|||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
read: Option<File>,
|
read: Option<File>,
|
||||||
write: File,
|
write: Option<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SocketPair {
|
impl SocketPair {
|
||||||
@ -194,30 +162,23 @@ impl SocketPair {
|
|||||||
src: e,
|
src: e,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// safe to create files given the successful return of socketpair(2)
|
||||||
Ok(SocketPair {
|
Ok(SocketPair {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
// SAFETY: valid new fd as socketpair(2) returned successfully
|
|
||||||
read: Some(unsafe { File::from_raw_fd(read) }),
|
read: Some(unsafe { File::from_raw_fd(read) }),
|
||||||
// SAFETY: valid new fd as socketpair(2) returned successfully
|
write: Some(unsafe { File::from_raw_fd(write) }),
|
||||||
write: unsafe { File::from_raw_fd(write) },
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_read(&mut self) -> Result<File> {
|
fn take_read(&mut self) -> Result<File> {
|
||||||
self.read
|
self.read
|
||||||
.take()
|
.take()
|
||||||
.ok_or_else(|| Error::BadFileSocket(self.name.to_string()))
|
.ok_or_else(|| Error::BadPipe(self.name.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self) -> Result<File> {
|
fn take_write(&mut self) -> Result<File> {
|
||||||
let dup_fd = nix::unistd::dup(self.write.as_raw_fd())
|
self.write
|
||||||
.map_err(|e| Error::Nix { msg: "dup", src: e })?;
|
.take()
|
||||||
|
.ok_or_else(|| Error::BadPipe(self.name.to_string()))
|
||||||
// SAFETY: valid new fd as dup(2) returned successfully
|
|
||||||
Ok(unsafe { File::from_raw_fd(dup_fd) })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_ref(&self) -> &File {
|
|
||||||
&self.write
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
92
src/main.rs
92
src/main.rs
@ -1,6 +1,6 @@
|
|||||||
use log::error;
|
use log::{error, info};
|
||||||
|
|
||||||
use void_orchestrator::{run, RunArgs};
|
use clone_shim::{pack, run, PackArgs, RunArgs};
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@ -12,12 +12,39 @@ fn main() {
|
|||||||
.version(env!("GIT_HASH"))
|
.version(env!("GIT_HASH"))
|
||||||
.author("Jake Hillion <jake@hillion.co.uk>")
|
.author("Jake Hillion <jake@hillion.co.uk>")
|
||||||
.about("Launch a void process application.")
|
.about("Launch a void process application.")
|
||||||
|
.subcommand_negates_reqs(true)
|
||||||
.trailing_var_arg(true)
|
.trailing_var_arg(true)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("pack")
|
||||||
|
.arg(
|
||||||
|
Arg::new("spec")
|
||||||
|
.long("specification")
|
||||||
|
.short('s')
|
||||||
|
.help("Provide the specification to pack as an external JSON file.")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("binary")
|
||||||
|
.long("binary")
|
||||||
|
.short('b')
|
||||||
|
.help("Provide the binary to pack.")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("output")
|
||||||
|
.long("out")
|
||||||
|
.short('o')
|
||||||
|
.help("Location of the output file")
|
||||||
|
.takes_value(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("spec")
|
Arg::new("spec")
|
||||||
.long("specification")
|
.long("specification")
|
||||||
.short('s')
|
.short('s')
|
||||||
.help("Provide the specification as an external JSON file.")
|
.help("Provide the specification to launch as an external JSON file.")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@ -34,25 +61,6 @@ fn main() {
|
|||||||
.help("Stop each spawned application process so that it can be attached to.")
|
.help("Stop each spawned application process so that it can be attached to.")
|
||||||
.takes_value(false),
|
.takes_value(false),
|
||||||
)
|
)
|
||||||
.arg(
|
|
||||||
Arg::new("daemon")
|
|
||||||
.long("daemon")
|
|
||||||
.short('D')
|
|
||||||
.help("Detach the shim from all child processes and exit immediately.")
|
|
||||||
.takes_value(false),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("stdout")
|
|
||||||
.long("stdout")
|
|
||||||
.help("Allow all spawned processes access to stdout (useful for debugging).")
|
|
||||||
.takes_value(false),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("stderr")
|
|
||||||
.long("stderr")
|
|
||||||
.help("Allow all spawned processes access to stderr (useful for debugging).")
|
|
||||||
.takes_value(false),
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("binary")
|
Arg::new("binary")
|
||||||
.index(1)
|
.index(1)
|
||||||
@ -74,8 +82,30 @@ fn main() {
|
|||||||
env_logger::init_from_env(env);
|
env_logger::init_from_env(env);
|
||||||
|
|
||||||
// launch process
|
// launch process
|
||||||
// execute shimmed process
|
|
||||||
std::process::exit({
|
let code = if let Some(matches) = matches.subcommand_matches("pack") {
|
||||||
|
// execute binary packing procedure
|
||||||
|
let args = PackArgs {
|
||||||
|
spec: Path::new(matches.value_of("spec").expect("spec required")),
|
||||||
|
binary: Path::new(matches.value_of("binary").expect("binary required")),
|
||||||
|
output: matches
|
||||||
|
.value_of("output")
|
||||||
|
.map(Path::new)
|
||||||
|
.unwrap_or_else(|| Path::new("a.out")),
|
||||||
|
};
|
||||||
|
|
||||||
|
match pack(&args) {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("binary packed successfully");
|
||||||
|
exitcode::OK
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("error packing binary: {}", e);
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// execute shimmed process
|
||||||
let (binary, binary_args) = {
|
let (binary, binary_args) = {
|
||||||
let mut argv = matches.values_of("binary").unwrap();
|
let mut argv = matches.values_of("binary").unwrap();
|
||||||
|
|
||||||
@ -88,21 +118,21 @@ fn main() {
|
|||||||
let args = RunArgs {
|
let args = RunArgs {
|
||||||
spec: matches.value_of("spec").map(Path::new),
|
spec: matches.value_of("spec").map(Path::new),
|
||||||
debug: matches.is_present("debug"),
|
debug: matches.is_present("debug"),
|
||||||
daemon: matches.is_present("daemon"),
|
|
||||||
|
|
||||||
stdout: matches.is_present("stdout"),
|
|
||||||
stderr: matches.is_present("stderr"),
|
|
||||||
|
|
||||||
binary,
|
binary,
|
||||||
binary_args,
|
binary_args,
|
||||||
};
|
};
|
||||||
|
|
||||||
match run(&args) {
|
match run(&args) {
|
||||||
Ok(code) => code,
|
Ok(_) => {
|
||||||
|
info!("launched successfully");
|
||||||
|
exitcode::OK
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("error: {}", e);
|
error!("error: {}", e);
|
||||||
-1
|
-1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
|
|
||||||
|
std::process::exit(code);
|
||||||
}
|
}
|
||||||
|
75
src/pack.rs
Normal file
75
src/pack.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use crate::{Result, Specification};
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use bincode::Options;
|
||||||
|
use object::endian::Endianness;
|
||||||
|
use object::read::ReadCache;
|
||||||
|
use object::read::{Object, ObjectSection};
|
||||||
|
use object::write::{StandardSegment, StreamingBuffer};
|
||||||
|
use object::SectionKind;
|
||||||
|
|
||||||
|
const SPECIFICATION_SECTION_NAME: &str = "void_specification";
|
||||||
|
|
||||||
|
pub(crate) fn pack_binary(binary: &Path, spec: &Specification, output: &Path) -> Result<()> {
|
||||||
|
let binary = File::open(binary)?;
|
||||||
|
let binary = ReadCache::new(binary);
|
||||||
|
|
||||||
|
let output = File::create(output)?;
|
||||||
|
let mut output = StreamingBuffer::new(output);
|
||||||
|
|
||||||
|
let input_object = object::File::parse(&binary)?;
|
||||||
|
|
||||||
|
let format = input_object.format();
|
||||||
|
let architecture = input_object.architecture();
|
||||||
|
let endianness = if input_object.is_little_endian() {
|
||||||
|
Endianness::Little
|
||||||
|
} else {
|
||||||
|
Endianness::Big
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut output_object = object::write::Object::new(format, architecture, endianness);
|
||||||
|
|
||||||
|
for input_section in input_object.sections() {
|
||||||
|
let output_section = output_object.add_section(
|
||||||
|
input_section.segment_name_bytes()?.unwrap_or(&[]).to_vec(),
|
||||||
|
input_section.name_bytes()?.to_vec(),
|
||||||
|
input_section.kind(),
|
||||||
|
);
|
||||||
|
|
||||||
|
output_object.set_section_data(output_section, input_section.data()?, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let spec_section = output_object.add_section(
|
||||||
|
output_object.segment_name(StandardSegment::Debug).to_vec(),
|
||||||
|
SPECIFICATION_SECTION_NAME.to_string().into(),
|
||||||
|
SectionKind::Other,
|
||||||
|
);
|
||||||
|
|
||||||
|
let spec = bincode_options().serialize(spec)?;
|
||||||
|
output_object.set_section_data(spec_section, spec, 0);
|
||||||
|
|
||||||
|
output_object.emit(&mut output)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn extract_specification(binary: &Path) -> Result<Option<Specification>> {
|
||||||
|
let binary = File::open(binary)?;
|
||||||
|
let binary = ReadCache::new(binary);
|
||||||
|
let input_object = object::File::parse(&binary)?;
|
||||||
|
|
||||||
|
let spec_section = if let Some(s) = input_object.section_by_name(SPECIFICATION_SECTION_NAME) {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let spec_data = spec_section.data()?;
|
||||||
|
|
||||||
|
Ok(Some(bincode_options().deserialize(spec_data)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bincode_options() -> impl bincode::Options {
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
}
|
@ -1,6 +1,4 @@
|
|||||||
use log::info;
|
use super::{Spawner, TriggerData};
|
||||||
|
|
||||||
use super::{RpcHandler, Spawner, TriggerData};
|
|
||||||
use crate::specification::{Arg, FileSocket, Pipe};
|
use crate::specification::{Arg, FileSocket, Pipe};
|
||||||
use crate::void::VoidBuilder;
|
use crate::void::VoidBuilder;
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
@ -9,10 +7,7 @@ use std::ffi::CString;
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
use std::os::unix::io::IntoRawFd;
|
||||||
|
|
||||||
use nix::sys::socket;
|
|
||||||
use nix::unistd::{fork, ForkResult};
|
|
||||||
|
|
||||||
pub struct PreparedArgs(Vec<PreparedArg>);
|
pub struct PreparedArgs(Vec<PreparedArg>);
|
||||||
|
|
||||||
@ -42,15 +37,11 @@ impl PreparedArgs {
|
|||||||
* for things like network sockets. update the builder
|
* for things like network sockets. update the builder
|
||||||
* with newly passed fds.
|
* with newly passed fds.
|
||||||
*/
|
*/
|
||||||
pub fn prepare_ambient(
|
pub fn prepare_ambient(builder: &mut VoidBuilder, args: &[Arg]) -> Result<Self> {
|
||||||
spawner: &Spawner,
|
|
||||||
builder: &mut VoidBuilder,
|
|
||||||
args: &[Arg],
|
|
||||||
) -> Result<Self> {
|
|
||||||
let mut v = Vec::with_capacity(args.len());
|
let mut v = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
v.push(PreparedArg::prepare_ambient(spawner, builder, arg)?);
|
v.push(PreparedArg::prepare_ambient(builder, arg)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PreparedArgs(v))
|
Ok(PreparedArgs(v))
|
||||||
@ -94,9 +85,6 @@ enum PreparedArg {
|
|||||||
/// A TCP Listener
|
/// A TCP Listener
|
||||||
TcpListener { socket: TcpListener },
|
TcpListener { socket: TcpListener },
|
||||||
|
|
||||||
/// RPC
|
|
||||||
Rpc { socket: File },
|
|
||||||
|
|
||||||
/// The rest of argv[1..], 0 or more arguments
|
/// The rest of argv[1..], 0 or more arguments
|
||||||
Trailing,
|
Trailing,
|
||||||
}
|
}
|
||||||
@ -125,28 +113,24 @@ impl PreparedArg {
|
|||||||
PreparedArg::Pipe(pipe)
|
PreparedArg::Pipe(pipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
Arg::FileSocket(FileSocket::Rx(s)) => {
|
Arg::FileSocket(s) => {
|
||||||
let socket = spawner.sockets.get_mut(s).unwrap().take_read()?;
|
let socket = match s {
|
||||||
|
FileSocket::Rx(s) => spawner.sockets.get_mut(s).unwrap().take_read(),
|
||||||
|
FileSocket::Tx(s) => spawner.sockets.get_mut(s).unwrap().take_write(),
|
||||||
|
}?;
|
||||||
|
|
||||||
builder.keep_fd(&socket);
|
builder.keep_fd(&socket);
|
||||||
PreparedArg::FileSocket(socket)
|
PreparedArg::FileSocket(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
arg => Self::prepare_ambient(spawner, builder, arg)?,
|
arg => Self::prepare_ambient(builder, arg)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ambient(spawner: &Spawner, builder: &mut VoidBuilder, arg: &Arg) -> Result<Self> {
|
fn prepare_ambient(builder: &mut VoidBuilder, arg: &Arg) -> Result<Self> {
|
||||||
Ok(match arg {
|
Ok(match arg {
|
||||||
Arg::Pipe(p) => return Err(Error::BadPipe(p.get_name().to_string())),
|
Arg::Pipe(p) => return Err(Error::BadPipe(p.get_name().to_string())),
|
||||||
Arg::FileSocket(FileSocket::Rx(s)) => return Err(Error::BadFileSocket(s.to_string())),
|
Arg::FileSocket(s) => return Err(Error::BadFileSocket(s.get_name().to_string())),
|
||||||
|
|
||||||
Arg::FileSocket(FileSocket::Tx(s)) => {
|
|
||||||
let socket = spawner.sockets.get(s).unwrap().write()?;
|
|
||||||
|
|
||||||
builder.keep_fd(&socket);
|
|
||||||
PreparedArg::FileSocket(socket)
|
|
||||||
}
|
|
||||||
|
|
||||||
Arg::File(path) => {
|
Arg::File(path) => {
|
||||||
let fd = File::open(path)?;
|
let fd = File::open(path)?;
|
||||||
@ -162,45 +146,6 @@ impl PreparedArg {
|
|||||||
PreparedArg::TcpListener { socket }
|
PreparedArg::TcpListener { socket }
|
||||||
}
|
}
|
||||||
|
|
||||||
Arg::Rpc(specs) => {
|
|
||||||
let (ambient, void) = socket::socketpair(
|
|
||||||
socket::AddressFamily::Unix,
|
|
||||||
socket::SockType::Datagram,
|
|
||||||
None,
|
|
||||||
socket::SockFlag::empty(),
|
|
||||||
)
|
|
||||||
.map_err(|e| Error::Nix {
|
|
||||||
msg: "socketpair",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// SAFETY: valid new fd as socketpair(2) returned successfully
|
|
||||||
let ambient = unsafe { File::from_raw_fd(ambient) };
|
|
||||||
// SAFETY: valid new fd as socketpair(2) returned successfully
|
|
||||||
let void = unsafe { File::from_raw_fd(void) };
|
|
||||||
|
|
||||||
// spawn this child with ambient authority
|
|
||||||
// necessary as no void ever has outgoing network capability
|
|
||||||
// SAFETY: this program is single-threaded so no safety issue
|
|
||||||
let child = unsafe { fork() }.map_err(|e| Error::Nix {
|
|
||||||
msg: "fork",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match child {
|
|
||||||
ForkResult::Child => {
|
|
||||||
let handler = RpcHandler::new(specs);
|
|
||||||
handler.handle(ambient).unwrap();
|
|
||||||
}
|
|
||||||
ForkResult::Parent { child } => {
|
|
||||||
info!("spawned rpc handler with pid {}", child);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// SAFETY: safe as socketpair returned successfully
|
|
||||||
PreparedArg::Rpc { socket: void }
|
|
||||||
}
|
|
||||||
|
|
||||||
Arg::BinaryName => PreparedArg::BinaryName,
|
Arg::BinaryName => PreparedArg::BinaryName,
|
||||||
Arg::Entrypoint => PreparedArg::Entrypoint,
|
Arg::Entrypoint => PreparedArg::Entrypoint,
|
||||||
Arg::Trigger => PreparedArg::Trigger,
|
Arg::Trigger => PreparedArg::Trigger,
|
||||||
@ -238,10 +183,6 @@ impl PreparedArg {
|
|||||||
Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()])
|
Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()])
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedArg::Rpc { socket } => {
|
|
||||||
Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()])
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedArg::Trailing => Ok(spawner
|
PreparedArg::Trailing => Ok(spawner
|
||||||
.binary_args
|
.binary_args
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
mod rpc;
|
|
||||||
|
|
||||||
use args::PreparedArgs;
|
use args::PreparedArgs;
|
||||||
use rpc::RpcHandler;
|
|
||||||
|
|
||||||
use crate::specification::{Arg, Entrypoint, Environment, Specification, Trigger};
|
use crate::specification::{Entrypoint, Environment, Specification, Trigger};
|
||||||
use crate::void::VoidBuilder;
|
use crate::void::VoidBuilder;
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use crate::{PipePair, SocketPair};
|
use crate::{PipePair, SocketPair};
|
||||||
@ -15,17 +13,14 @@ use std::collections::HashMap;
|
|||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use nix::sys::signal::{kill, Signal};
|
use nix::sys::signal::{kill, Signal};
|
||||||
use nix::sys::socket::{recvmsg, ControlMessageOwned, MsgFlags};
|
use nix::sys::socket::{recvmsg, ControlMessageOwned, MsgFlags};
|
||||||
use nix::sys::wait::{waitid, Id, WaitPidFlag, WaitStatus};
|
use nix::unistd::{self, Pid};
|
||||||
use nix::unistd::{self, fork, ForkResult, Pid};
|
|
||||||
use nix::Error as NixError;
|
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 1024;
|
const BUFFER_SIZE: usize = 1024;
|
||||||
const MAX_FILE_DESCRIPTORS: usize = 16;
|
|
||||||
|
|
||||||
pub struct Spawner<'a> {
|
pub struct Spawner<'a> {
|
||||||
pub spec: &'a Specification,
|
pub spec: &'a Specification,
|
||||||
@ -69,7 +64,10 @@ impl<'a> Spawner<'a> {
|
|||||||
match &entrypoint.trigger {
|
match &entrypoint.trigger {
|
||||||
Trigger::Startup => {
|
Trigger::Startup => {
|
||||||
let mut builder = VoidBuilder::new();
|
let mut builder = VoidBuilder::new();
|
||||||
self.mount_entrypoint(&mut builder, self.binary)?;
|
|
||||||
|
let binary = PathBuf::from(self.binary).canonicalize()?;
|
||||||
|
builder.mount(binary, "/entrypoint");
|
||||||
|
|
||||||
self.prepare_env(&mut builder, &entrypoint.environment);
|
self.prepare_env(&mut builder, &entrypoint.environment);
|
||||||
|
|
||||||
let args =
|
let args =
|
||||||
@ -102,46 +100,70 @@ impl<'a> Spawner<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Trigger::Pipe(s) => {
|
Trigger::Pipe(s) => {
|
||||||
let mut builder = VoidBuilder::new();
|
|
||||||
self.prepare_spawner(&mut builder, &entrypoint.environment, &entrypoint.args)?;
|
|
||||||
|
|
||||||
let pipe = self.pipes.get_mut(s).unwrap().take_read()?;
|
let pipe = self.pipes.get_mut(s).unwrap().take_read()?;
|
||||||
|
let binary = PathBuf::from(self.binary).canonicalize()?;
|
||||||
|
|
||||||
|
let mut builder = VoidBuilder::new();
|
||||||
|
builder.mount(binary, "/entrypoint");
|
||||||
builder.keep_fd(&pipe);
|
builder.keep_fd(&pipe);
|
||||||
|
|
||||||
|
self.prepare_env(&mut builder, &entrypoint.environment);
|
||||||
|
|
||||||
|
for env in &entrypoint.environment {
|
||||||
|
if let Environment::Filesystem {
|
||||||
|
host_path,
|
||||||
|
environment_path: _,
|
||||||
|
} = env
|
||||||
|
{
|
||||||
|
builder.mount(host_path, host_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let closure = || match self.pipe_trigger(pipe, entrypoint, name) {
|
let closure = || match self.pipe_trigger(pipe, entrypoint, name) {
|
||||||
Ok(()) => exitcode::OK,
|
Ok(()) => std::process::exit(exitcode::OK),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("error in pipe_trigger: {}", e);
|
error!("error in pipe_trigger: {}", e);
|
||||||
1
|
std::process::exit(1)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let void = builder.spawn(closure)?;
|
let void = builder.spawn(closure)?;
|
||||||
info!(
|
info!(
|
||||||
"spawned pipe trigger for entrypoint `{}` as {}",
|
"prepared pipe trigger for entrypoint `{}` as {}",
|
||||||
name.as_str(),
|
name.as_str(),
|
||||||
void
|
void
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Trigger::FileSocket(s) => {
|
Trigger::FileSocket(s) => {
|
||||||
let mut builder = VoidBuilder::new();
|
|
||||||
self.prepare_spawner(&mut builder, &entrypoint.environment, &entrypoint.args)?;
|
|
||||||
|
|
||||||
let socket = self.sockets.get_mut(s).unwrap().take_read()?;
|
let socket = self.sockets.get_mut(s).unwrap().take_read()?;
|
||||||
|
let binary = PathBuf::from(self.binary).canonicalize()?;
|
||||||
|
|
||||||
|
let mut builder = VoidBuilder::new();
|
||||||
|
builder.mount(binary, "/entrypoint");
|
||||||
builder.keep_fd(&socket);
|
builder.keep_fd(&socket);
|
||||||
|
|
||||||
|
for env in &entrypoint.environment {
|
||||||
|
if let Environment::Filesystem {
|
||||||
|
host_path,
|
||||||
|
environment_path: _,
|
||||||
|
} = env
|
||||||
|
{
|
||||||
|
builder.mount(host_path, host_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let closure = || match self.file_socket_trigger(socket, entrypoint, name) {
|
let closure = || match self.file_socket_trigger(socket, entrypoint, name) {
|
||||||
Ok(()) => exitcode::OK,
|
Ok(()) => std::process::exit(exitcode::OK),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("error in file_socket_trigger: {}", e);
|
error!("error in file_socket_trigger: {}", e);
|
||||||
1
|
std::process::exit(1)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let void = builder.spawn(closure)?;
|
let void = builder.spawn(closure)?;
|
||||||
info!(
|
info!(
|
||||||
"spawned socket trigger for entrypoint `{}` as {}",
|
"prepared socket trigger for entrypoint `{}` as {}",
|
||||||
name.as_str(),
|
name.as_str(),
|
||||||
void
|
void
|
||||||
);
|
);
|
||||||
@ -153,21 +175,9 @@ impl<'a> Spawner<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn pipe_trigger(&self, mut pipe: File, spec: &Entrypoint, name: &str) -> Result<()> {
|
fn pipe_trigger(&self, mut pipe: File, spec: &Entrypoint, name: &str) -> Result<()> {
|
||||||
// put the work in a forked process that can handle signals
|
|
||||||
Self::fork_for_trigger()?;
|
|
||||||
|
|
||||||
let mut buf = [0_u8; BUFFER_SIZE];
|
let mut buf = [0_u8; BUFFER_SIZE];
|
||||||
loop {
|
loop {
|
||||||
let read_bytes = match pipe.read(&mut buf) {
|
let read_bytes = pipe.read(&mut buf)?;
|
||||||
Ok(n) => Ok(n),
|
|
||||||
Err(e) => {
|
|
||||||
if e.kind() == std::io::ErrorKind::Interrupted {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
if read_bytes == 0 {
|
if read_bytes == 0 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -179,7 +189,7 @@ impl<'a> Spawner<'a> {
|
|||||||
|
|
||||||
self.prepare_env(&mut builder, &spec.environment);
|
self.prepare_env(&mut builder, &spec.environment);
|
||||||
|
|
||||||
let args = PreparedArgs::prepare_ambient(self, &mut builder, &spec.args)?;
|
let args = PreparedArgs::prepare_ambient(&mut builder, &spec.args)?;
|
||||||
|
|
||||||
let closure =
|
let closure =
|
||||||
|| {
|
|| {
|
||||||
@ -206,36 +216,18 @@ impl<'a> Spawner<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let void = builder.spawn(closure)?;
|
builder.spawn(closure)?;
|
||||||
info!("spawned entrypoint `{}` as {}", name, void);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_socket_trigger(&self, socket: File, spec: &Entrypoint, name: &str) -> Result<()> {
|
fn file_socket_trigger(&self, socket: File, spec: &Entrypoint, name: &str) -> Result<()> {
|
||||||
// put the work in a forked process that can handle signals
|
|
||||||
Self::fork_for_trigger()?;
|
|
||||||
|
|
||||||
let mut cmsg_buf = nix::cmsg_space!([RawFd; MAX_FILE_DESCRIPTORS]);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let msg = match recvmsg::<()>(
|
let msg = recvmsg(socket.as_raw_fd(), &[], None, MsgFlags::empty()).map_err(|e| {
|
||||||
socket.as_raw_fd(),
|
Error::Nix {
|
||||||
&mut [],
|
msg: "recvmsg",
|
||||||
Some(&mut cmsg_buf),
|
src: e,
|
||||||
MsgFlags::empty(),
|
|
||||||
) {
|
|
||||||
Ok(m) => Ok(m),
|
|
||||||
Err(e) => {
|
|
||||||
if e == NixError::EINTR {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Nix {
|
|
||||||
msg: "recvmsg",
|
|
||||||
src: e,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}?;
|
})?;
|
||||||
|
|
||||||
debug!("triggering from socket recvmsg");
|
debug!("triggering from socket recvmsg");
|
||||||
|
|
||||||
@ -255,7 +247,7 @@ impl<'a> Spawner<'a> {
|
|||||||
|
|
||||||
self.prepare_env(&mut builder, &spec.environment);
|
self.prepare_env(&mut builder, &spec.environment);
|
||||||
|
|
||||||
let args = PreparedArgs::prepare_ambient(self, &mut builder, &spec.args)?;
|
let args = PreparedArgs::prepare_ambient(&mut builder, &spec.args)?;
|
||||||
|
|
||||||
let closure = || {
|
let closure = || {
|
||||||
if self.debug {
|
if self.debug {
|
||||||
@ -281,8 +273,7 @@ impl<'a> Spawner<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let void = builder.spawn(closure)?;
|
builder.spawn(closure)?;
|
||||||
info!("spawned entrypoint `{}` as {}", name, void);
|
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
@ -290,39 +281,11 @@ impl<'a> Spawner<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fork_for_trigger() -> Result<()> {
|
|
||||||
// SAFETY: only unsafe in a multi-threaded program
|
|
||||||
if let ForkResult::Parent { child: _pid } = unsafe { fork() }.map_err(|e| Error::Nix {
|
|
||||||
msg: "fork",
|
|
||||||
src: e,
|
|
||||||
})? {
|
|
||||||
let status = waitid(Id::All, WaitPidFlag::WEXITED).map_err(|e| Error::Nix {
|
|
||||||
msg: "waitpid",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match status {
|
|
||||||
WaitStatus::Exited(_pid, code) => {
|
|
||||||
std::process::exit(code);
|
|
||||||
}
|
|
||||||
WaitStatus::Signaled(pid, sig, _coredump) => {
|
|
||||||
debug!(
|
|
||||||
"trigger: forked child {} was terminated with signal {}",
|
|
||||||
pid, sig
|
|
||||||
);
|
|
||||||
std::process::exit(-1);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_self(name: &str) -> Result<()> {
|
fn stop_self(name: &str) -> Result<()> {
|
||||||
|
let pid = Pid::this();
|
||||||
info!("stopping process `{}`", name);
|
info!("stopping process `{}`", name);
|
||||||
|
|
||||||
kill(Pid::this(), Signal::SIGSTOP).map_err(|e| Error::Nix {
|
kill(pid, Signal::SIGSTOP).map_err(|e| Error::Nix {
|
||||||
msg: "kill",
|
msg: "kill",
|
||||||
src: e,
|
src: e,
|
||||||
})?;
|
})?;
|
||||||
@ -331,67 +294,6 @@ impl<'a> Spawner<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_entrypoint(&self, builder: &mut VoidBuilder, binary: &Path) -> Result<()> {
|
|
||||||
let binary = PathBuf::from(binary).canonicalize()?;
|
|
||||||
builder.mount(binary, "/entrypoint");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn forward_mounts<'b>(
|
|
||||||
&self,
|
|
||||||
builder: &mut VoidBuilder,
|
|
||||||
environment: impl IntoIterator<Item = &'b Environment>,
|
|
||||||
arguments: impl IntoIterator<Item = &'b Arg>,
|
|
||||||
) {
|
|
||||||
for env in environment {
|
|
||||||
if let Environment::Filesystem {
|
|
||||||
host_path,
|
|
||||||
environment_path: _,
|
|
||||||
} = env
|
|
||||||
{
|
|
||||||
builder.mount(host_path, host_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in arguments {
|
|
||||||
if let Arg::File(host_path) = arg {
|
|
||||||
builder.mount(host_path, host_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn forward_files<'b>(
|
|
||||||
&self,
|
|
||||||
builder: &mut VoidBuilder,
|
|
||||||
arguments: impl IntoIterator<Item = &'b Arg>,
|
|
||||||
) {
|
|
||||||
for arg in arguments {
|
|
||||||
if let Arg::FileSocket(socket) = arg {
|
|
||||||
builder.keep_fd(self.sockets.get(socket.get_name()).unwrap().write_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_spawner<'b>(
|
|
||||||
&self,
|
|
||||||
builder: &mut VoidBuilder,
|
|
||||||
environment: impl IntoIterator<Item = &'b Environment>,
|
|
||||||
args: impl IntoIterator<Item = &'b Arg> + Copy,
|
|
||||||
) -> Result<()> {
|
|
||||||
self.mount_entrypoint(builder, self.binary)?;
|
|
||||||
self.forward_mounts(builder, environment, args);
|
|
||||||
self.forward_files(builder, args);
|
|
||||||
|
|
||||||
builder.mount("/dev/null", "/dev/null");
|
|
||||||
builder.mount("/proc", "/proc").remount_proc();
|
|
||||||
|
|
||||||
builder.keep_fd(&1);
|
|
||||||
builder.keep_fd(&2);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_env<'b>(
|
fn prepare_env<'b>(
|
||||||
&self,
|
&self,
|
||||||
builder: &mut VoidBuilder,
|
builder: &mut VoidBuilder,
|
||||||
@ -412,20 +314,6 @@ impl<'a> Spawner<'a> {
|
|||||||
Environment::DomainName(name) => {
|
Environment::DomainName(name) => {
|
||||||
builder.set_domain_name(name);
|
builder.set_domain_name(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Environment::Procfs => {
|
|
||||||
builder.mount("/proc", "/proc").remount_proc();
|
|
||||||
}
|
|
||||||
|
|
||||||
Environment::Stdin => {
|
|
||||||
builder.keep_fd(&0);
|
|
||||||
}
|
|
||||||
Environment::Stdout => {
|
|
||||||
builder.keep_fd(&1);
|
|
||||||
}
|
|
||||||
Environment::Stderr => {
|
|
||||||
builder.keep_fd(&2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,300 +0,0 @@
|
|||||||
use log::{debug, error};
|
|
||||||
|
|
||||||
use crate::specification::{AddressFamily as SpecAddressFamily, RpcSpecification};
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::net::{TcpStream, UdpSocket};
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
use nix::sys::socket::AddressFamily;
|
|
||||||
use nix::sys::socket::{recv, send, sendmsg, ControlMessage, MsgFlags};
|
|
||||||
|
|
||||||
const MAX_MSG_LENGTH: usize = 4096;
|
|
||||||
|
|
||||||
pub struct RpcHandler<'a> {
|
|
||||||
permitted_rpcs: &'a [RpcSpecification],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> RpcHandler<'a> {
|
|
||||||
pub(super) fn new(permitted_rpcs: &'a [RpcSpecification]) -> Self {
|
|
||||||
Self { permitted_rpcs }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn handle(&self, socket: File) -> Result<(), Error> {
|
|
||||||
let mut buf = vec![0; MAX_MSG_LENGTH];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let read_bytes =
|
|
||||||
recv(socket.as_raw_fd(), &mut buf, MsgFlags::empty()).map_err(|e| Error::Nix {
|
|
||||||
msg: "recvmsg",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
debug!("handling rpc");
|
|
||||||
|
|
||||||
if read_bytes < 4 {
|
|
||||||
error!("received rpc too short");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: safe as the enum repr is non_exhaustive so any value is valid and the buffer is long enough
|
|
||||||
let kind = unsafe { *(buf.as_ptr() as *const RpcKind) };
|
|
||||||
|
|
||||||
let fds = Vec::new();
|
|
||||||
if kind.num_fds() > 0 {
|
|
||||||
// get any fds to go alongside the message
|
|
||||||
// nothing which requires this currently exists
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
let resp = handle_rpc(self.permitted_rpcs, kind, &buf[4..], &fds);
|
|
||||||
|
|
||||||
let (msg, fds) = RpcResultSend::new(resp);
|
|
||||||
|
|
||||||
// sendmsg first so its there when listening for the send
|
|
||||||
if !fds.is_empty() {
|
|
||||||
let fds: Box<[i32]> = fds.iter().map(|f| f.as_raw_fd()).collect();
|
|
||||||
|
|
||||||
sendmsg::<()>(
|
|
||||||
socket.as_raw_fd(),
|
|
||||||
&[],
|
|
||||||
&[ControlMessage::ScmRights(&fds)],
|
|
||||||
MsgFlags::empty(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.map_err(|e| Error::Nix {
|
|
||||||
msg: "sendmsg",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: safe as msg is of fixed size
|
|
||||||
let msg = unsafe {
|
|
||||||
std::slice::from_raw_parts(
|
|
||||||
&msg as *const RpcResultSend as *const u8,
|
|
||||||
std::mem::size_of_val(&msg),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
send(socket.as_raw_fd(), msg, MsgFlags::empty()).map_err(|e| Error::Nix {
|
|
||||||
msg: "send",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u32)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum RpcKind {
|
|
||||||
OpenTcpSocket,
|
|
||||||
OpenUdpSocket,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RpcKind {
|
|
||||||
fn num_fds(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
RpcKind::OpenTcpSocket => 0,
|
|
||||||
RpcKind::OpenUdpSocket => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OpenSocket {
|
|
||||||
pub family: AddressFamily,
|
|
||||||
pub port: u16,
|
|
||||||
pub host: [c_char],
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum RpcResult {
|
|
||||||
OpenTcpSocket { socket: TcpStream },
|
|
||||||
OpenUdpSocket { socket: UdpSocket },
|
|
||||||
|
|
||||||
Error { error: RpcError },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum RpcResultSend {
|
|
||||||
OpenTcpSocket,
|
|
||||||
OpenUdpSocket,
|
|
||||||
|
|
||||||
Error { error: RpcError },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RpcResultSend {
|
|
||||||
fn new(from: RpcResult) -> (Self, Vec<Box<dyn AsRawFd>>) {
|
|
||||||
match from {
|
|
||||||
RpcResult::OpenTcpSocket { socket } => (Self::OpenTcpSocket, vec![Box::new(socket)]),
|
|
||||||
RpcResult::OpenUdpSocket { socket } => (Self::OpenUdpSocket, vec![Box::new(socket)]),
|
|
||||||
RpcResult::Error { error } => (Self::Error { error }, vec![]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub enum RpcError {
|
|
||||||
BadlyFormedRequest,
|
|
||||||
OperationNotPermitted,
|
|
||||||
Io { errno: i32 },
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_rpc(
|
|
||||||
permitted_rpcs: &[RpcSpecification],
|
|
||||||
kind: RpcKind,
|
|
||||||
data: &[u8],
|
|
||||||
_fds: &[File],
|
|
||||||
) -> RpcResult {
|
|
||||||
fn inner(
|
|
||||||
permitted_rpcs: &[RpcSpecification],
|
|
||||||
kind: RpcKind,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<RpcResult, RpcError> {
|
|
||||||
match kind {
|
|
||||||
RpcKind::OpenTcpSocket => {
|
|
||||||
let data = unsafe { &*(data as *const [u8] as *const OpenSocket) };
|
|
||||||
if !validate_open_tcp_socket(permitted_rpcs, data)? {
|
|
||||||
Ok(RpcResult::Error {
|
|
||||||
error: RpcError::OperationNotPermitted,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
handle_open_tcp_socket(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RpcKind::OpenUdpSocket => {
|
|
||||||
let data = unsafe { &*(data as *const [u8] as *const OpenSocket) };
|
|
||||||
if !validate_open_udp_socket(permitted_rpcs, data)? {
|
|
||||||
Ok(RpcResult::Error {
|
|
||||||
error: RpcError::OperationNotPermitted,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
handle_open_udp_socket(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match inner(permitted_rpcs, kind, data) {
|
|
||||||
Ok(o) => o,
|
|
||||||
Err(e) => RpcResult::Error { error: e },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_open_tcp_socket(
|
|
||||||
permitted_rpcs: &[RpcSpecification],
|
|
||||||
req: &OpenSocket,
|
|
||||||
) -> Result<bool, RpcError> {
|
|
||||||
for each in permitted_rpcs {
|
|
||||||
if let RpcSpecification::OpenTcpSocket { family, port, host } = each {
|
|
||||||
let mut allowed = true;
|
|
||||||
|
|
||||||
allowed &= match family {
|
|
||||||
None => true,
|
|
||||||
Some(fam) => match req.family {
|
|
||||||
AddressFamily::Inet => *fam == SpecAddressFamily::Inet,
|
|
||||||
AddressFamily::Inet6 => *fam == SpecAddressFamily::Inet6,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
allowed &= match port {
|
|
||||||
None => true,
|
|
||||||
Some(p) => req.port == *p,
|
|
||||||
};
|
|
||||||
|
|
||||||
allowed &= match host {
|
|
||||||
None => true,
|
|
||||||
Some(h) => {
|
|
||||||
CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
|
||||||
.map_err(|_| RpcError::BadlyFormedRequest)?
|
|
||||||
.to_string_lossy()
|
|
||||||
.as_ref()
|
|
||||||
== h
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if allowed {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_open_tcp_socket(req: &OpenSocket) -> Result<RpcResult, RpcError> {
|
|
||||||
let host = CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
|
||||||
.map_err(|_| RpcError::BadlyFormedRequest)?;
|
|
||||||
let host = host.to_str().map_err(|_| RpcError::BadlyFormedRequest)?;
|
|
||||||
|
|
||||||
let socket = TcpStream::connect(host).map_err(|e| RpcError::Io {
|
|
||||||
errno: e.raw_os_error().unwrap(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(RpcResult::OpenTcpSocket { socket })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_open_udp_socket(
|
|
||||||
permitted_rpcs: &[RpcSpecification],
|
|
||||||
req: &OpenSocket,
|
|
||||||
) -> Result<bool, RpcError> {
|
|
||||||
for each in permitted_rpcs {
|
|
||||||
if let RpcSpecification::OpenUdpSocket { family, port, host } = each {
|
|
||||||
let mut allowed = true;
|
|
||||||
|
|
||||||
allowed &= match family {
|
|
||||||
None => true,
|
|
||||||
Some(fam) => match req.family {
|
|
||||||
AddressFamily::Inet => *fam == SpecAddressFamily::Inet,
|
|
||||||
AddressFamily::Inet6 => *fam == SpecAddressFamily::Inet6,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
allowed &= match port {
|
|
||||||
None => true,
|
|
||||||
Some(p) => req.port == *p,
|
|
||||||
};
|
|
||||||
|
|
||||||
allowed &= match host {
|
|
||||||
None => true,
|
|
||||||
Some(h) => {
|
|
||||||
CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
|
||||||
.map_err(|_| RpcError::BadlyFormedRequest)?
|
|
||||||
.to_string_lossy()
|
|
||||||
.as_ref()
|
|
||||||
== h
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if allowed {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_open_udp_socket(req: &OpenSocket) -> Result<RpcResult, RpcError> {
|
|
||||||
let host = CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
|
||||||
.map_err(|_| RpcError::BadlyFormedRequest)?;
|
|
||||||
let host = host.to_str().map_err(|_| RpcError::BadlyFormedRequest)?;
|
|
||||||
|
|
||||||
let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| RpcError::Io {
|
|
||||||
errno: e.raw_os_error().unwrap(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
socket.connect(host).map_err(|e| RpcError::Io {
|
|
||||||
errno: e.raw_os_error().unwrap(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(RpcResult::OpenUdpSocket { socket })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_u8_slice(s: &[c_char]) -> &[u8] {
|
|
||||||
unsafe { std::slice::from_raw_parts(s.as_ptr() as *const u8, s.len()) }
|
|
||||||
}
|
|
@ -68,9 +68,6 @@ pub enum Arg {
|
|||||||
/// A TCP Listener
|
/// A TCP Listener
|
||||||
TcpListener { addr: SocketAddr },
|
TcpListener { addr: SocketAddr },
|
||||||
|
|
||||||
/// An RPC socket that accepts specified commands
|
|
||||||
Rpc(Vec<RpcSpecification>),
|
|
||||||
|
|
||||||
/// The rest of argv[1..], 0 or more arguments
|
/// The rest of argv[1..], 0 or more arguments
|
||||||
Trailing,
|
Trailing,
|
||||||
}
|
}
|
||||||
@ -81,38 +78,6 @@ impl Arg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
||||||
pub enum RpcSpecification {
|
|
||||||
/// Open a TCP socket
|
|
||||||
///
|
|
||||||
/// None for each value means that any value is allowed in the call.
|
|
||||||
/// A specified value restricts to exactly that.
|
|
||||||
OpenTcpSocket {
|
|
||||||
family: Option<AddressFamily>,
|
|
||||||
port: Option<u16>,
|
|
||||||
host: Option<String>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Open a UDP socket
|
|
||||||
///
|
|
||||||
/// None for each value means that any value is allowed in the call.
|
|
||||||
/// A specified value restricts to exactly that.
|
|
||||||
OpenUdpSocket {
|
|
||||||
family: Option<AddressFamily>,
|
|
||||||
port: Option<u16>,
|
|
||||||
host: Option<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
||||||
pub enum AddressFamily {
|
|
||||||
/// IPv4 address
|
|
||||||
Inet,
|
|
||||||
|
|
||||||
/// IPv6 address
|
|
||||||
Inet6,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||||
pub enum Pipe {
|
pub enum Pipe {
|
||||||
Rx(String),
|
Rx(String),
|
||||||
@ -152,12 +117,6 @@ pub enum Environment {
|
|||||||
|
|
||||||
Hostname(String),
|
Hostname(String),
|
||||||
DomainName(String),
|
DomainName(String),
|
||||||
|
|
||||||
Procfs,
|
|
||||||
|
|
||||||
Stdin,
|
|
||||||
Stdout,
|
|
||||||
Stderr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
||||||
@ -245,31 +204,6 @@ impl Specification {
|
|||||||
return Err(Error::BadPipe(pipe.to_string()));
|
return Err(Error::BadPipe(pipe.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate sockets match
|
|
||||||
let (read, write) = self.sockets();
|
|
||||||
let mut read_set = HashSet::with_capacity(read.len());
|
|
||||||
|
|
||||||
for socket in read {
|
|
||||||
if !read_set.insert(socket) {
|
|
||||||
return Err(Error::BadFileSocket(socket.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut write_set = HashSet::with_capacity(write.len());
|
|
||||||
for socket in write {
|
|
||||||
write_set.insert(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
for socket in &read_set {
|
|
||||||
if !write_set.contains(socket) {
|
|
||||||
return Err(Error::BadFileSocket(socket.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(socket) = (&write_set - &read_set).into_iter().next() {
|
|
||||||
return Err(Error::BadFileSocket(socket.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate trigger arguments make sense
|
// validate trigger arguments make sense
|
||||||
for entrypoint in self.entrypoints.values() {
|
for entrypoint in self.entrypoints.values() {
|
||||||
if entrypoint.args.contains(&Arg::Trigger) {
|
if entrypoint.args.contains(&Arg::Trigger) {
|
||||||
|
133
src/void.rs
133
src/void.rs
@ -1,12 +1,11 @@
|
|||||||
use log::{debug, error, info, trace};
|
use log::{debug, error};
|
||||||
|
|
||||||
use crate::clone::{clone3, CloneArgs, CloneFlags};
|
use crate::clone::{clone3, CloneArgs, CloneFlags};
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::env;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::{self, File};
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@ -14,7 +13,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use nix::fcntl::{FcntlArg, FdFlag};
|
use nix::fcntl::{FcntlArg, FdFlag};
|
||||||
use nix::mount::{mount, umount2, MntFlags, MsFlags};
|
use nix::mount::{mount, umount2, MntFlags, MsFlags};
|
||||||
use nix::sys::signal::{signal, SigHandler, Signal};
|
use nix::sys::signal::{signal, SigHandler, Signal};
|
||||||
use nix::unistd::{close, dup2, getgid, getuid, pivot_root, sethostname, Gid, Pid, Uid};
|
use nix::unistd::{close, getgid, getuid, pivot_root, sethostname, Gid, Pid, Uid};
|
||||||
|
|
||||||
use close_fds::CloseFdsBuilder;
|
use close_fds::CloseFdsBuilder;
|
||||||
|
|
||||||
@ -34,8 +33,6 @@ pub struct VoidBuilder {
|
|||||||
|
|
||||||
mounts: HashMap<PathBuf, PathBuf>,
|
mounts: HashMap<PathBuf, PathBuf>,
|
||||||
fds: HashSet<RawFd>,
|
fds: HashSet<RawFd>,
|
||||||
|
|
||||||
remount_proc: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VoidBuilder {
|
impl VoidBuilder {
|
||||||
@ -45,7 +42,6 @@ impl VoidBuilder {
|
|||||||
domain_name: None,
|
domain_name: None,
|
||||||
mounts: HashMap::new(),
|
mounts: HashMap::new(),
|
||||||
fds: HashSet::new(),
|
fds: HashSet::new(),
|
||||||
remount_proc: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,11 +65,6 @@ impl VoidBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remount_proc(&mut self) -> &mut Self {
|
|
||||||
self.remount_proc = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn(&mut self, child_fn: impl FnOnce() -> i32) -> Result<VoidHandle> {
|
pub fn spawn(&mut self, child_fn: impl FnOnce() -> i32) -> Result<VoidHandle> {
|
||||||
let mut args = CloneArgs::new(
|
let mut args = CloneArgs::new(
|
||||||
CloneFlags::CLONE_NEWCGROUP
|
CloneFlags::CLONE_NEWCGROUP
|
||||||
@ -103,23 +94,15 @@ impl VoidBuilder {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
debug!("voiding user namespace...");
|
|
||||||
self.void_user_namespace(parent_uid, parent_gid)?; // first to regain full capabilities
|
self.void_user_namespace(parent_uid, parent_gid)?; // first to regain full capabilities
|
||||||
|
|
||||||
debug!("voiding mount namespace...");
|
|
||||||
self.void_mount_namespace()?;
|
|
||||||
debug!("voiding file descriptors..."); // occur after mount to unmount /dev/null
|
|
||||||
self.void_file_descriptors()?;
|
self.void_file_descriptors()?;
|
||||||
|
|
||||||
debug!("voiding ipc namespace...");
|
|
||||||
self.void_ipc_namespace()?;
|
self.void_ipc_namespace()?;
|
||||||
debug!("voiding uts namespace...");
|
|
||||||
self.void_uts_namespace()?;
|
self.void_uts_namespace()?;
|
||||||
debug!("voiding network namespace...");
|
|
||||||
self.void_network_namespace()?;
|
self.void_network_namespace()?;
|
||||||
debug!("voiding pid namespace...");
|
|
||||||
self.void_pid_namespace()?;
|
self.void_pid_namespace()?;
|
||||||
debug!("voiding cgroup namespace...");
|
self.void_mount_namespace()?;
|
||||||
self.void_cgroup_namespace()?;
|
self.void_cgroup_namespace()?;
|
||||||
|
|
||||||
Ok::<(), Error>(())
|
Ok::<(), Error>(())
|
||||||
@ -129,12 +112,19 @@ impl VoidBuilder {
|
|||||||
error!("error preparing void: {}", e);
|
error!("error preparing void: {}", e);
|
||||||
std::process::exit(-1)
|
std::process::exit(-1)
|
||||||
} else {
|
} else {
|
||||||
info!("successfully prepared void");
|
|
||||||
std::process::exit(child_fn())
|
std::process::exit(child_fn())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("cloned child: {}", child);
|
debug!("cloned child: {}", child);
|
||||||
|
|
||||||
|
// Leak the child function's resources in the parent process.
|
||||||
|
// This avoids closing files that have been "moved" into the child.
|
||||||
|
// It is also an over-approximation, and may cause actual memory leaks.
|
||||||
|
// As the spawning process is normally short lived, this shouldn't
|
||||||
|
// be a problem.
|
||||||
|
std::mem::forget(child_fn);
|
||||||
|
|
||||||
Ok(VoidHandle { pid: child })
|
Ok(VoidHandle { pid: child })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +180,7 @@ impl VoidBuilder {
|
|||||||
* unavailable after unmounting the old root.
|
* unavailable after unmounting the old root.
|
||||||
*/
|
*/
|
||||||
fn void_mount_namespace(&self) -> Result<()> {
|
fn void_mount_namespace(&self) -> Result<()> {
|
||||||
trace!("changing the propagation type of the old root to private");
|
// change the propagation type of the old root to private
|
||||||
mount(
|
mount(
|
||||||
Option::<&str>::None,
|
Option::<&str>::None,
|
||||||
"/",
|
"/",
|
||||||
@ -203,21 +193,10 @@ impl VoidBuilder {
|
|||||||
src: e,
|
src: e,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
trace!("creating tmpdir for new root");
|
// create and consume a tmpdir to mount a tmpfs into
|
||||||
let tmp_base = {
|
let new_root = tempfile::tempdir()?.into_path();
|
||||||
let env_dir = env::temp_dir();
|
|
||||||
if env_dir.exists() {
|
|
||||||
env_dir
|
|
||||||
} else {
|
|
||||||
debug!("env_dir does not exist, assuming `/` as the base");
|
|
||||||
"/".into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// consume so it does not attempt to delete a folder which no longer exists
|
// mount a tmpfs as the new root
|
||||||
let new_root = tempfile::tempdir_in(tmp_base)?.into_path();
|
|
||||||
|
|
||||||
trace!("mounting a new root tmpfs at `{:?}`", &new_root);
|
|
||||||
mount(
|
mount(
|
||||||
Some("tmpfs"),
|
Some("tmpfs"),
|
||||||
&new_root,
|
&new_root,
|
||||||
@ -230,12 +209,13 @@ impl VoidBuilder {
|
|||||||
src: e,
|
src: e,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let put_old = new_root.join("old_root/");
|
// prepare a subdirectory to pivot the old root into
|
||||||
debug!("new_root: {:?}; put_old: {:?}", &new_root, &put_old);
|
let old_root = new_root.join("old_root/");
|
||||||
fs::create_dir_all(&put_old)?;
|
debug!("new_root: {:?}; put_old: {:?}", &new_root, &old_root);
|
||||||
|
fs::create_dir(&old_root)?;
|
||||||
|
|
||||||
trace!("pivoting old root into a subdirectory of new root");
|
// pivot the old root into a subdirectory of the new root
|
||||||
pivot_root(&new_root, &put_old).map_err(|e| Error::Nix {
|
pivot_root(&new_root, &old_root).map_err(|e| Error::Nix {
|
||||||
msg: "pivot_root",
|
msg: "pivot_root",
|
||||||
src: e,
|
src: e,
|
||||||
})?;
|
})?;
|
||||||
@ -243,22 +223,11 @@ impl VoidBuilder {
|
|||||||
let new_root = PathBuf::from("/");
|
let new_root = PathBuf::from("/");
|
||||||
let old_root = PathBuf::from("/old_root/");
|
let old_root = PathBuf::from("/old_root/");
|
||||||
|
|
||||||
trace!("changing root directory to new root");
|
// chdir after
|
||||||
std::env::set_current_dir(&new_root)?;
|
std::env::set_current_dir(&new_root)?;
|
||||||
|
|
||||||
trace!("creating bind mounts before unmounting");
|
// mount paths before unmounting old_root
|
||||||
|
for (src, dst) in &self.mounts {
|
||||||
let standard_dev_null = if self.mounts.contains_key(&PathBuf::from("/dev/null")) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((PathBuf::from("/dev/null"), PathBuf::from("/dev/null")))
|
|
||||||
};
|
|
||||||
|
|
||||||
for (src, dst) in self
|
|
||||||
.mounts
|
|
||||||
.iter()
|
|
||||||
.chain(standard_dev_null.as_ref().map(|(x, y)| (x, y)))
|
|
||||||
{
|
|
||||||
let mut src = old_root.join(src.strip_prefix("/").unwrap_or(src));
|
let mut src = old_root.join(src.strip_prefix("/").unwrap_or(src));
|
||||||
let dst = new_root.join(dst.strip_prefix("/").unwrap_or(dst));
|
let dst = new_root.join(dst.strip_prefix("/").unwrap_or(dst));
|
||||||
|
|
||||||
@ -283,29 +252,12 @@ impl VoidBuilder {
|
|||||||
fs::write(&dst, b"")?;
|
fs::write(&dst, b"")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// rbind mount
|
// bind mount
|
||||||
mount(
|
mount(
|
||||||
Some(&src),
|
Some(&src),
|
||||||
&dst,
|
&dst,
|
||||||
Option::<&str>::None,
|
Option::<&str>::None,
|
||||||
MsFlags::MS_BIND | MsFlags::MS_REC,
|
MsFlags::MS_BIND,
|
||||||
Option::<&str>::None,
|
|
||||||
)
|
|
||||||
.map_err(|e| Error::Nix {
|
|
||||||
msg: "mount",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remount proc
|
|
||||||
if self.remount_proc {
|
|
||||||
debug!("remounting /proc`");
|
|
||||||
|
|
||||||
mount(
|
|
||||||
Some("proc"),
|
|
||||||
"/proc",
|
|
||||||
Some("proc"),
|
|
||||||
MsFlags::empty(),
|
|
||||||
Option::<&str>::None,
|
Option::<&str>::None,
|
||||||
)
|
)
|
||||||
.map_err(|e| Error::Nix {
|
.map_err(|e| Error::Nix {
|
||||||
@ -395,37 +347,6 @@ impl VoidBuilder {
|
|||||||
closer.closefrom(3);
|
closer.closefrom(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite stdin/stdout/stderr without closing
|
|
||||||
{
|
|
||||||
let mut nullfd: Option<File> = None;
|
|
||||||
for stdfd in &[0, 1, 2] {
|
|
||||||
if !keep.contains(stdfd) {
|
|
||||||
trace!("voiding stdfd {}", stdfd);
|
|
||||||
|
|
||||||
let fd = nullfd
|
|
||||||
.take()
|
|
||||||
.map(Ok)
|
|
||||||
.unwrap_or_else(|| File::open("/dev/null"))?;
|
|
||||||
|
|
||||||
dup2(fd.as_raw_fd(), *stdfd).map_err(|e| Error::Nix {
|
|
||||||
msg: "dup2",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
nullfd = Some(fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.mounts.contains_key(&PathBuf::from("/dev/null")) {
|
|
||||||
debug!("unmount /dev/null after voiding file descriptors");
|
|
||||||
|
|
||||||
umount2("/dev/null", MntFlags::MNT_DETACH).map_err(|e| Error::Nix {
|
|
||||||
msg: "umount2",
|
|
||||||
src: e,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for fd in keep.as_ref() {
|
for fd in keep.as_ref() {
|
||||||
let mut flags = FdFlag::from_bits_truncate(
|
let mut flags = FdFlag::from_bits_truncate(
|
||||||
nix::fcntl::fcntl(*fd, FcntlArg::F_GETFD).map_err(|e| Error::Nix {
|
nix::fcntl::fcntl(*fd, FcntlArg::F_GETFD).map_err(|e| Error::Nix {
|
||||||
|
Loading…
Reference in New Issue
Block a user