Big refactor for citadel installer
This commit is contained in:
parent
109f007e33
commit
4099f19f4b
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
**/target
|
||||
**/*.rs.bk
|
||||
|
||||
|
281
citadel-mkimage/Cargo.lock → citadel-image/Cargo.lock
generated
281
citadel-mkimage/Cargo.lock → citadel-image/Cargo.lock
generated
@ -6,11 +6,6 @@ dependencies = [
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.11"
|
||||
@ -47,25 +42,6 @@ name = "bitflags"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.25"
|
||||
@ -77,7 +53,7 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "citadel-mkimage"
|
||||
name = "citadel-image"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -102,56 +78,6 @@ dependencies = [
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clear_on_drop"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.3"
|
||||
@ -172,33 +98,6 @@ dependencies = [
|
||||
"synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.2.0"
|
||||
@ -213,17 +112,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
name = "libcitadel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -254,91 +152,6 @@ dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_isaac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.43"
|
||||
@ -352,6 +165,17 @@ dependencies = [
|
||||
"redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
@ -362,27 +186,6 @@ name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.82"
|
||||
@ -398,27 +201,11 @@ dependencies = [
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.22"
|
||||
@ -466,11 +253,6 @@ dependencies = [
|
||||
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.5"
|
||||
@ -481,6 +263,11 @@ name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
@ -512,62 +299,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||
"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
|
||||
"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
|
||||
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
|
||||
"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d"
|
||||
"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16"
|
||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
|
||||
"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17"
|
||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
"checksum curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3eacf6ff1b911e3170a8c400b402e10c86dc3cb166bd69034ebbc2b785fea4c2"
|
||||
"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
|
||||
"checksum ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cd66d8a16ef71c23cf5eeb2140d8d3cd293457c6c7fd6804b593397a933fcf1e"
|
||||
"checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
|
||||
"checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
|
||||
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
|
||||
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
|
||||
"checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74"
|
||||
"checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f"
|
||||
"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09"
|
||||
"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c"
|
||||
"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c"
|
||||
"checksum rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae9d223d52ae411a33cf7e54ec6034ec165df296ccd23533d671a28252b6f66a"
|
||||
"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a"
|
||||
"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372"
|
||||
"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db"
|
||||
"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
|
||||
"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
|
||||
"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05"
|
||||
"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3"
|
||||
"checksum redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "679da7508e9a6390aeaf7fbd02a800fdc64b73fe2204dd2c8ae66d22d9d5ad5d"
|
||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||
"checksum ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe642b9dd1ba0038d78c4a3999d1ee56178b4d415c1e1fbaba83b06dce012f0"
|
||||
"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6"
|
||||
"checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154"
|
||||
"checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||
"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7"
|
||||
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
|
||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
|
||||
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
||||
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "citadel-mkimage"
|
||||
name = "citadel-image"
|
||||
version = "0.1.0"
|
||||
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
homepage = "https://github.com/subgraph/citadel"
|
@ -1,17 +1,16 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::Path;
|
||||
use std::fs::{self,File};
|
||||
use std::io::{self,Write};
|
||||
|
||||
use failure::ResultExt;
|
||||
use libcitadel::Result;
|
||||
use libcitadel::{Result,ImageHeader,util,verity};
|
||||
|
||||
use BuildConfig;
|
||||
use util;
|
||||
|
||||
pub struct UpdateBuilder {
|
||||
config: BuildConfig,
|
||||
target: PathBuf,
|
||||
|
||||
nblocks: Option<usize>,
|
||||
shasum: Option<String>,
|
||||
@ -28,18 +27,24 @@ fn align(sz: usize, n: usize) -> usize {
|
||||
|
||||
impl UpdateBuilder {
|
||||
|
||||
pub fn new(config: BuildConfig) -> Result<UpdateBuilder> {
|
||||
let builder = UpdateBuilder {
|
||||
config,
|
||||
pub fn new(config: BuildConfig) -> UpdateBuilder {
|
||||
let filename = UpdateBuilder::target_filename(&config);
|
||||
let target = config.workdir_path(&filename);
|
||||
UpdateBuilder {
|
||||
config, target,
|
||||
nblocks: None, shasum: None, verity_salt: None,
|
||||
verity_root: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
builder.copy_source_image()?;
|
||||
Ok(builder)
|
||||
fn target_filename(config: &BuildConfig) -> String {
|
||||
format!("citadel-{}-{}-{:03}", config.img_name(), config.channel(), config.version())
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Result<()> {
|
||||
info!("Copying source file to {}", self.target.display());
|
||||
fs::copy(self.config.source(), &self.target)?;
|
||||
|
||||
self.pad_image()
|
||||
.context("failed writing padding to image")?;
|
||||
|
||||
@ -55,27 +60,12 @@ impl UpdateBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn target(&self, ext: Option<&str>) -> PathBuf {
|
||||
match ext {
|
||||
Some(s) => self.config.workdir_path(&format!("citadel-{}-{}-{:03}.{}", self.config.img_name(), self.config.channel(), self.config.version(), s)),
|
||||
None => self.config.workdir_path(&format!("citadel-{}-{}-{:03}", self.config.img_name(), self.config.channel(), self.config.version()))
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_source_image(&self) -> Result<()> {
|
||||
sanity_check_source(self.config.source())?;
|
||||
let target = self.target(None);
|
||||
info!("Copying source file to {}", target.display());
|
||||
let mut from = File::open(self.config.source())?;
|
||||
let mut to = File::create(&target)?;
|
||||
io::copy(&mut from, &mut to)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pad_image(&mut self) -> Result<()> {
|
||||
let target = self.target(None);
|
||||
let meta = target.metadata()?;
|
||||
let meta = self.target.metadata()?;
|
||||
let len = meta.len() as usize;
|
||||
if len % 512 != 0 {
|
||||
bail!("Image file size is not a multiple of sector size (512 bytes)");
|
||||
}
|
||||
let padlen = align(len, BLOCK_SIZE) - len;
|
||||
|
||||
if padlen > 0 {
|
||||
@ -83,7 +73,7 @@ impl UpdateBuilder {
|
||||
let zeros = vec![0u8; padlen];
|
||||
let mut file = OpenOptions::new()
|
||||
.append(true)
|
||||
.open(&target)?;
|
||||
.open(&self.target)?;
|
||||
file.write_all(&zeros)?;
|
||||
}
|
||||
|
||||
@ -95,7 +85,7 @@ impl UpdateBuilder {
|
||||
}
|
||||
|
||||
fn calculate_shasum(&mut self) -> Result<()> {
|
||||
let shasum = util::sha256(self.target(None))?;
|
||||
let shasum = util::sha256(&self.target)?;
|
||||
info!("Sha256 of image data is {}", shasum);
|
||||
self.shasum = Some(shasum);
|
||||
Ok(())
|
||||
@ -105,7 +95,7 @@ impl UpdateBuilder {
|
||||
let hashfile = self.config.workdir_path(&format!("verity-hash-{}-{:03}", self.config.image_type(), self.config.version()));
|
||||
let outfile = self.config.workdir_path("verity-format.out");
|
||||
|
||||
let verity = util::verity_initial_hashtree(self.target(None), &hashfile)?;
|
||||
let verity = verity::generate_initial_hashtree(&self.target, &hashfile)?;
|
||||
|
||||
|
||||
fs::write(outfile, verity.output())
|
||||
@ -132,59 +122,33 @@ impl UpdateBuilder {
|
||||
|
||||
fn compress_image(&self) -> Result<()> {
|
||||
info!("Compressing image data");
|
||||
util::xz_compress(self.target(None))
|
||||
util::xz_compress(&self.target)
|
||||
}
|
||||
|
||||
fn write_final_image(&self) -> Result<()> {
|
||||
let header = self.generate_header()?;
|
||||
let mut path = self.target(None);
|
||||
let fname = path.file_name().unwrap().to_str().unwrap().to_owned();
|
||||
path.set_file_name(fname + ".img");
|
||||
let filename = format!("{}.img", UpdateBuilder::target_filename(&self.config));
|
||||
let mut image_path = self.config.workdir_path(&filename);
|
||||
|
||||
let mut out = File::create(&path)
|
||||
.context(format!("could not open output file {}", path.display()))?;
|
||||
let mut out = File::create(&image_path)
|
||||
.context(format!("could not open output file {}", image_path.display()))?;
|
||||
|
||||
out.write_all(&header)?;
|
||||
header.write_header(&out)?;
|
||||
|
||||
path.set_extension("xz");
|
||||
let mut data = File::open(&path)
|
||||
.context(format!("could not open compressed image data file {}", path.display()))?;
|
||||
image_path.set_extension("xz");
|
||||
let mut data = File::open(&image_path)
|
||||
.context(format!("could not open compressed image data file {}", image_path.display()))?;
|
||||
io::copy(&mut data, &mut out)
|
||||
.context("error copying image data to output file")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// The Image Header structure is stored in a 4096 byte block at the start of
|
||||
/// every resource image file. When an image is installed to a partition it
|
||||
/// is stored at the last 4096 byte block of the block device for the partition.
|
||||
///
|
||||
/// The layout of this structure is the following:
|
||||
///
|
||||
/// field size (bytes) offset
|
||||
/// ----- ------------ ------
|
||||
///
|
||||
/// magic 4 0
|
||||
/// status 1 4
|
||||
/// flags 1 5
|
||||
/// length 2 6
|
||||
///
|
||||
/// metainfo <length> 8
|
||||
///
|
||||
/// signature 64 8 + length
|
||||
///
|
||||
fn generate_header(&self) -> Result<Vec<u8>> {
|
||||
let mut hdr = vec![0u8; BLOCK_SIZE];
|
||||
hdr[0..4].copy_from_slice(b"SGOS");
|
||||
hdr[5] = 0x04; // FLAG_DATA_COMPRESSED
|
||||
fn generate_header(&self) -> Result<ImageHeader> {
|
||||
let hdr = ImageHeader::new();
|
||||
hdr.set_flag(ImageHeader::FLAG_DATA_COMPRESSED);
|
||||
|
||||
let metainfo = self.generate_metainfo();
|
||||
let metalen = metainfo.len();
|
||||
hdr[6] = (metalen >> 8) as u8;
|
||||
hdr[7] = metalen as u8;
|
||||
hdr[8..8+metalen].copy_from_slice(&metainfo);
|
||||
|
||||
hdr.set_metainfo_bytes(&metainfo);
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
@ -208,20 +172,3 @@ impl UpdateBuilder {
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
fn sanity_check_source<P: AsRef<Path>>(src: P) -> Result<()> {
|
||||
let src: &Path = src.as_ref();
|
||||
let meta = match src.metadata() {
|
||||
Ok(md) => md,
|
||||
Err(e) => bail!("Could not load image file {}: {}", src.display(), e),
|
||||
};
|
||||
|
||||
if !meta.file_type().is_file() {
|
||||
bail!("Image file {} exists but is not a regular file", src.display());
|
||||
}
|
||||
|
||||
if meta.len() % 512 != 0 {
|
||||
bail!("Image file size is not a multiple of sector size (512 bytes)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use toml;
|
||||
|
||||
use libcitadel::Result;
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BuildConfig {
|
||||
#[serde(rename = "image-type")]
|
||||
@ -64,7 +62,10 @@ impl BuildConfig {
|
||||
};
|
||||
let src = Path::new(&self.source);
|
||||
if !src.is_file() {
|
||||
bail!("Source path '{}' does not exist or is not a regular file", src.display());
|
||||
bail!(
|
||||
"Source path '{}' does not exist or is not a regular file",
|
||||
src.display()
|
||||
);
|
||||
}
|
||||
if self.image_type == "modules" && self.kernel_version.is_none() {
|
||||
bail!("Cannot build 'modules' image without kernel-version field");
|
173
citadel-image/src/main.rs
Normal file
173
citadel-image/src/main.rs
Normal file
@ -0,0 +1,173 @@
|
||||
#[macro_use] extern crate libcitadel;
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
extern crate clap;
|
||||
extern crate toml;
|
||||
|
||||
use std::process::exit;
|
||||
use std::path::Path;
|
||||
|
||||
use clap::{App,Arg,SubCommand,ArgMatches};
|
||||
use clap::AppSettings::*;
|
||||
|
||||
use build::UpdateBuilder;
|
||||
use config::BuildConfig;
|
||||
use libcitadel::{Result,ResourceImage,set_verbose,format_error,Partition,KeyPair};
|
||||
|
||||
mod build;
|
||||
mod config;
|
||||
|
||||
fn main() {
|
||||
let app = App::new("citadel-image")
|
||||
.about("Citadel update image builder")
|
||||
.settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder])
|
||||
|
||||
.subcommand(SubCommand::with_name("build")
|
||||
.about("Build an update image specified by a configuration file")
|
||||
.arg(Arg::with_name("build-file")
|
||||
.required(true)
|
||||
.help("Path to image build config file")))
|
||||
.subcommand(SubCommand::with_name("metainfo")
|
||||
.about("Display metainfo variables for an image file")
|
||||
.arg(Arg::with_name("path")
|
||||
.required(true)
|
||||
.help("Path to image file")))
|
||||
.subcommand(SubCommand::with_name("generate-verity")
|
||||
.about("Generate dm-verity hash tree for an image file")
|
||||
.arg(Arg::with_name("path")
|
||||
.required(true)
|
||||
.help("Path to image file")))
|
||||
.subcommand(SubCommand::with_name("verify")
|
||||
.about("Verify dm-verity hash tree for an image file")
|
||||
.arg(Arg::with_name("path")
|
||||
.required(true)
|
||||
.help("Path to image file")))
|
||||
|
||||
.subcommand(SubCommand::with_name("install-rootfs")
|
||||
.about("Install rootfs image file to a partition")
|
||||
.arg(Arg::with_name("path")
|
||||
.required(true)
|
||||
.help("Path to image file")))
|
||||
|
||||
.subcommand(SubCommand::with_name("genkeys")
|
||||
.about("Generate a pair of keys"))
|
||||
|
||||
.subcommand(SubCommand::with_name("decompress")
|
||||
.about("Decompress a compressed image file")
|
||||
.arg(Arg::with_name("path")
|
||||
.required(true)
|
||||
.help("Path to image file")));
|
||||
|
||||
set_verbose(true);
|
||||
let matches = app.get_matches();
|
||||
|
||||
let result = match matches.subcommand() {
|
||||
("build", Some(m)) => build_image(m),
|
||||
("metainfo", Some(m)) => metainfo(m),
|
||||
("generate-verity", Some(m)) => generate_verity(m),
|
||||
("verify", Some(m)) => verify(m),
|
||||
("sign-image", Some(m)) => sign_image(m),
|
||||
("genkeys", Some(_)) => genkeys(),
|
||||
("decompress", Some(m)) => decompress(m),
|
||||
("install-rootfs", Some(m)) => install_rootfs(m),
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(ref e) = result {
|
||||
println!("Error: {}", format_error(e));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let build_file = arg_matches.value_of("build-file").unwrap();
|
||||
let config = BuildConfig::load(build_file)?;
|
||||
let mut builder = UpdateBuilder::new(config);
|
||||
builder.build()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn metainfo(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let img = load_image(arg_matches)?;
|
||||
print!("{}",String::from_utf8(img.header().metainfo_bytes())?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_verity(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let img = load_image(arg_matches)?;
|
||||
if img.has_verity_hashtree() {
|
||||
info!("Image already has dm-verity hashtree appended, doing nothing.");
|
||||
} else {
|
||||
img.generate_verity_hashtree()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let img = load_image(arg_matches)?;
|
||||
let ok = img.verify_verity()?;
|
||||
if ok {
|
||||
info!("Image verification succeeded");
|
||||
} else {
|
||||
warn!("Image verification FAILED!");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_image(arg_matches: &ArgMatches) -> Result<ResourceImage> {
|
||||
let path = arg_matches.value_of("path").expect("path argument missing");
|
||||
if !Path::new(path).exists() {
|
||||
bail!("Cannot load image {}: File does not exist", path);
|
||||
}
|
||||
let img = ResourceImage::from_path(path)?;
|
||||
if !img.is_valid_image() {
|
||||
bail!("File {} is not a valid image file", path);
|
||||
}
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let img = load_image(arg_matches)?;
|
||||
let partition =choose_install_partition()?;
|
||||
img.write_to_partition(&partition)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let _img = load_image(arg_matches)?;
|
||||
info!("Not implemented yet");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn genkeys() -> Result<()> {
|
||||
let keypair = KeyPair::generate()?;
|
||||
println!("public-key = \"{}\"", keypair.public_key_hex());
|
||||
println!("private-key = \"{}\"", keypair.private_key_hex());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decompress(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let img = load_image(arg_matches)?;
|
||||
if !img.is_compressed() {
|
||||
info!("Image is not compressed, not decompressing.");
|
||||
} else {
|
||||
img.decompress()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn choose_install_partition() -> Result<Partition> {
|
||||
let partitions = Partition::rootfs_partitions()?;
|
||||
for p in &partitions {
|
||||
if !p.is_mounted() && !p.is_initialized() {
|
||||
return Ok(p.clone())
|
||||
}
|
||||
}
|
||||
for p in &partitions {
|
||||
if !p.is_mounted() {
|
||||
return Ok(p.clone())
|
||||
}
|
||||
}
|
||||
Err(format_err!("No suitable install partition found"))
|
||||
}
|
188
citadel-install/Cargo.lock
generated
Normal file
188
citadel-install/Cargo.lock
generated
Normal file
@ -0,0 +1,188 @@
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "citadel-install"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rpassword 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727"
|
||||
"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5"
|
||||
"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
|
||||
"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749"
|
||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||
"checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
|
||||
"checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74"
|
||||
"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09"
|
||||
"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c"
|
||||
"checksum rpassword 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37473170aedbe66ffa3ad3726939ba677d83c646ad4fd99e5b4bc38712f45ec"
|
||||
"checksum rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "01b90379b8664dd83460d59bdc5dd1fd3172b8913788db483ed1325171eab2f7"
|
||||
"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc"
|
||||
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
10
citadel-install/Cargo.toml
Normal file
10
citadel-install/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "citadel-install"
|
||||
version = "0.1.0"
|
||||
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
homepage = "http://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1.3"
|
||||
libc = "0.2"
|
||||
rpassword = "2.1.0"
|
150
citadel-install/src/cli.rs
Normal file
150
citadel-install/src/cli.rs
Normal file
@ -0,0 +1,150 @@
|
||||
use std::io::{self,Write};
|
||||
use std::path::Path;
|
||||
use Result;
|
||||
use util::Disk;
|
||||
use rpassword;
|
||||
use installer::Installer;
|
||||
|
||||
pub fn run_cli_install() -> Result<bool> {
|
||||
let disk = match choose_disk()? {
|
||||
Some(disk) => disk,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
display_disk(&disk);
|
||||
|
||||
let passphrase = match read_passphrase()? {
|
||||
Some(passphrase) => passphrase,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
if !confirm_install(&disk)? {
|
||||
return Ok(false);
|
||||
}
|
||||
run_install(disk, passphrase)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
|
||||
let disk = find_disk_by_path(target.as_ref())?;
|
||||
display_disk(&disk);
|
||||
|
||||
let passphrase = match read_passphrase()? {
|
||||
Some(passphrase) => passphrase,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
if !confirm_install(&disk)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
run_install(disk, passphrase)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn run_install(disk: Disk, passphrase: String) -> Result<()> {
|
||||
let mut install = Installer::new();
|
||||
install.set_target(disk.path().to_str().unwrap());
|
||||
install.set_passphrase(&passphrase);
|
||||
install.set_install_syslinux(true);
|
||||
install.verify()?;
|
||||
install.run()
|
||||
}
|
||||
|
||||
fn display_disk(disk: &Disk) {
|
||||
println!();
|
||||
println!(" Device: {}", disk.path().display());
|
||||
println!(" Size: {}", disk.size_str());
|
||||
println!(" Model: {}", disk.model());
|
||||
println!();
|
||||
}
|
||||
|
||||
fn find_disk_by_path(path: &Path) -> Result<Disk> {
|
||||
if !path.exists() {
|
||||
bail!("Target disk path {} does not exist", path.display());
|
||||
}
|
||||
for disk in Disk::probe_all()? {
|
||||
if disk.path() == path {
|
||||
return Ok(disk.clone());
|
||||
}
|
||||
}
|
||||
Err(format_err!("Installation target {} is not a valid disk", path.display()))
|
||||
}
|
||||
|
||||
fn choose_disk() -> Result<Option<Disk>> {
|
||||
let disks = Disk::probe_all()?;
|
||||
if disks.is_empty() {
|
||||
bail!("No disks found.");
|
||||
}
|
||||
|
||||
loop {
|
||||
prompt_choose_disk(&disks)?;
|
||||
let line = read_line()?;
|
||||
if line == "q" || line == "Q" {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Ok(n) = line.parse::<usize>() {
|
||||
if n > 0 && n <= disks.len() {
|
||||
return Ok(Some(disks[n-1].clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_choose_disk(disks: &[Disk]) -> Result<()> {
|
||||
println!("Available disks:\n");
|
||||
for idx in 0..disks.len() {
|
||||
println!(" [{}]: {} Size: {} Model: {}", idx + 1, disks[idx].path().display(), disks[idx].size_str(), disks[idx].model());
|
||||
}
|
||||
print!("\nChoose a disk to install to (q to quit): ");
|
||||
io::stdout().flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_line() -> Result<String> {
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
if input.ends_with('\n') {
|
||||
input.pop();
|
||||
}
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn read_passphrase() -> Result<Option<String>> {
|
||||
loop {
|
||||
println!("Enter a disk encryption passphrase (or 'q' to quit)");
|
||||
println!();
|
||||
let passphrase = rpassword::read_password_from_tty(Some(" Passphrase : "))?;
|
||||
if passphrase.is_empty() {
|
||||
println!("Passphrase cannot be empty");
|
||||
continue;
|
||||
}
|
||||
if passphrase == "q" || passphrase == "Q" {
|
||||
return Ok(None);
|
||||
}
|
||||
let confirm = rpassword::read_password_from_tty(Some(" Confirm : "))?;
|
||||
if confirm == "q" || confirm == "Q" {
|
||||
return Ok(None);
|
||||
}
|
||||
println!();
|
||||
if passphrase == confirm {
|
||||
return Ok(Some(passphrase));
|
||||
}
|
||||
println!("Passphrases do not match");
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_install(disk: &Disk) -> Result<bool> {
|
||||
println!("Are you sure you want to completely erase this this device?");
|
||||
println!();
|
||||
println!(" Device: {}", disk.path().display());
|
||||
println!(" Size: {}", disk.size_str());
|
||||
println!(" Model: {}", disk.model());
|
||||
println!();
|
||||
print!("Type YES (uppercase) to continue with install: ");
|
||||
io::stdout().flush()?;
|
||||
let answer = read_line()?;
|
||||
Ok(answer == "YES")
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
|
||||
|
||||
use {Result,PathExt};
|
||||
|
||||
// Partition Type GUID of UEFI boot (ESP) partition
|
||||
const ESP_GUID: &str = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b";
|
||||
use Result;
|
||||
use util;
|
||||
|
||||
///
|
||||
/// Represents a disk partition device on the system
|
||||
@ -20,13 +18,15 @@ pub struct DiskPartition {
|
||||
}
|
||||
|
||||
impl DiskPartition {
|
||||
/// Return list of all UEFI ESP partitions on the system as a `Vec<DiskPartition>`
|
||||
/// Return list of all vfat partitions on the system as a `Vec<DiskPartition>`
|
||||
pub fn boot_partitions() -> Result<Vec<DiskPartition>> {
|
||||
let pp = fs::read_to_string("/proc/partitions")?;
|
||||
let mut v = Vec::new();
|
||||
for line in Path::new("/proc/partitions").read_as_lines()?.iter().skip(2) {
|
||||
for line in pp.lines().skip(2)
|
||||
{
|
||||
let part = DiskPartition::from_proc_line(&line)
|
||||
.map_err(|e| format_err!("Failed to parse line '{}': {}", line, e))?;
|
||||
if part.is_esp() {
|
||||
if part.is_vfat()? {
|
||||
v.push(part);
|
||||
}
|
||||
}
|
||||
@ -48,52 +48,39 @@ impl DiskPartition {
|
||||
v[0].parse::<u8>()?, // Major
|
||||
v[1].parse::<u8>()?, // Minor
|
||||
v[2].parse::<usize>()?, // number of blocks
|
||||
v[3])) // device name
|
||||
v[3],
|
||||
)) // device name
|
||||
}
|
||||
|
||||
// create a new `DiskPartion` from parsed components of line from /proc/partitions
|
||||
fn from_line_components(major: u8, minor: u8, blocks: usize, name: &str) -> DiskPartition {
|
||||
DiskPartition {
|
||||
path: PathBuf::from("/dev").join(name),
|
||||
major, minor, blocks,
|
||||
major,
|
||||
minor,
|
||||
blocks,
|
||||
}
|
||||
}
|
||||
|
||||
// return `true` if partition has UEFI ESP partition type
|
||||
fn is_esp(&self) -> bool {
|
||||
match self.path.partition_type_guid() {
|
||||
Ok(guid) => guid == ESP_GUID,
|
||||
Err(err) => {
|
||||
warn!("Error: {}", err);
|
||||
false
|
||||
},
|
||||
}
|
||||
// return `true` if partition is VFAT type
|
||||
fn is_vfat(&self) -> Result<bool> {
|
||||
let ok = self.partition_fstype()? == "vfat";
|
||||
Ok(ok)
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn mount<P: AsRef<Path>>(&self, target: P) -> bool {
|
||||
match self.path.mount(target) {
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
false
|
||||
},
|
||||
Ok(()) => true,
|
||||
|
||||
}
|
||||
pub fn mount<P: AsRef<Path>>(&self, target: P) -> Result<()> {
|
||||
util::exec_cmdline("/usr/bin/mount", format!("{} {}", self.path.display(), target.as_ref().display()))
|
||||
}
|
||||
|
||||
pub fn umount(&self) -> bool {
|
||||
match self.path.umount() {
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
false
|
||||
},
|
||||
Ok(()) => true,
|
||||
|
||||
}
|
||||
}
|
||||
pub fn umount(&self) -> Result<()> {
|
||||
util::exec_cmdline("/usr/bin/umount", self.path().to_str().unwrap())
|
||||
}
|
||||
|
||||
fn partition_fstype(&self) -> Result<String> {
|
||||
util::exec_cmdline_with_output("/usr/bin/lsblk", format!("-dno FSTYPE {}", self.path().display()))
|
||||
}
|
||||
}
|
445
citadel-install/src/installer.rs
Normal file
445
citadel-install/src/installer.rs
Normal file
@ -0,0 +1,445 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fs::{self,File};
|
||||
use std::io::Write;
|
||||
use std::os::unix::fs as unixfs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
|
||||
use super::util;
|
||||
use super::Result;
|
||||
|
||||
const BLKDEACTIVATE: &str = "/sbin/blkdeactivate";
|
||||
const CRYPTSETUP: &str = "/sbin/cryptsetup";
|
||||
const PARTED: &str = "/sbin/parted";
|
||||
const EXTLINUX: &str = "/sbin/extlinux";
|
||||
const PVCREATE: &str = "/sbin/pvcreate";
|
||||
const VGCREATE: &str = "/sbin/vgcreate";
|
||||
const LVCREATE: &str = "/sbin/lvcreate";
|
||||
const VGCHANGE: &str = "/sbin/vgchange";
|
||||
const MKFS_VFAT: &str = "/sbin/mkfs.vfat";
|
||||
const MKFS_BTRFS: &str = "/bin/mkfs.btrfs";
|
||||
const LSBLK: &str = "/bin/lsblk";
|
||||
const BTRFS: &str = "/bin/btrfs";
|
||||
const MOUNT: &str = "/bin/mount";
|
||||
const UMOUNT: &str = "/bin/umount";
|
||||
const CHOWN: &str = "/bin/chown";
|
||||
const TAR: &str = "/bin/tar";
|
||||
const XZ: &str = "/bin/xz";
|
||||
const DD: &str = "/bin/dd";
|
||||
const CITADEL_IMAGE: &str = "/usr/bin/citadel-image";
|
||||
|
||||
const LUKS_UUID: &str = "683a17fc-4457-42cc-a946-cde67195a101";
|
||||
|
||||
const EXTRA_IMAGE_NAME: &str = "citadel-extra.img";
|
||||
|
||||
const INSTALL_MOUNT: &str = "/run/installer/mnt";
|
||||
const LUKS_PASSPHRASE_FILE: &str = "/run/installer/luks-passphrase";
|
||||
|
||||
const DEFAULT_ARTIFACT_DIRECTORY: &str = "/run/images";
|
||||
|
||||
const KERNEL_CMDLINE: &str = "add_efi_memmap intel_iommu=off cryptomgr.notests rcupdate.rcu_expedited=1 rcu_nocbs=0-64 tsc=reliable no_timer_check noreplace-smp i915.fastboot=1 citadel.nosignatures quiet splash";
|
||||
|
||||
pub struct Installer {
|
||||
install_syslinux: bool,
|
||||
target_device: String,
|
||||
passphrase: String,
|
||||
artifact_directory: String,
|
||||
logfile: Option<RefCell<File>>,
|
||||
}
|
||||
|
||||
impl Installer {
|
||||
pub fn new() -> Installer {
|
||||
Installer {
|
||||
install_syslinux: true,
|
||||
target_device: String::new(),
|
||||
passphrase: String::new(),
|
||||
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
|
||||
logfile: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_target(&mut self, target: &str) {
|
||||
self.target_device = target.to_owned()
|
||||
}
|
||||
|
||||
pub fn set_passphrase(&mut self, passphrase: &str) {
|
||||
self.passphrase = passphrase.to_owned()
|
||||
}
|
||||
|
||||
pub fn set_install_syslinux(&mut self, val: bool) {
|
||||
self.install_syslinux = val;
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<()> {
|
||||
let tools = vec![
|
||||
BLKDEACTIVATE,CRYPTSETUP,PARTED,EXTLINUX,PVCREATE,VGCREATE,LVCREATE,VGCHANGE,
|
||||
MKFS_VFAT,MKFS_BTRFS,LSBLK,BTRFS,MOUNT,UMOUNT,CHOWN,TAR,XZ,CITADEL_IMAGE,
|
||||
];
|
||||
|
||||
let modules_img = self.modules_imagename();
|
||||
let artifacts = vec![
|
||||
"bootx64.efi", "bzImage",
|
||||
modules_img.as_str(), EXTRA_IMAGE_NAME,
|
||||
];
|
||||
|
||||
if self.target_device.is_empty() {
|
||||
bail!("No target device set in install configuration");
|
||||
}
|
||||
if self.passphrase.is_empty() {
|
||||
bail!("No passphrase set in install configuration");
|
||||
}
|
||||
if !Path::new(&self.target_device).exists() {
|
||||
bail!("Target device {} does not exist", self.target_device);
|
||||
}
|
||||
|
||||
for tool in tools {
|
||||
if !Path::new(tool).exists() {
|
||||
bail!("Required installer utility program does not exist: {}", tool);
|
||||
}
|
||||
}
|
||||
|
||||
for a in artifacts {
|
||||
if !self.artifact_path(a).exists() {
|
||||
bail!("Required install artifact {} does not exist in {}", a, self.artifact_directory);
|
||||
}
|
||||
}
|
||||
|
||||
if !self.artifact_path("appimg-rootfs.tar").exists() && !self.artifact_path("appimg-rootfs.tar.xz").exists() {
|
||||
bail!("Required component appimg-rootfs.tar(.xz) does not exist in {}",self.artifact_directory);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<()> {
|
||||
let start = Instant::now();
|
||||
|
||||
fs::create_dir_all(INSTALL_MOUNT)?;
|
||||
|
||||
self.partition_disk()?;
|
||||
self.setup_luks()?;
|
||||
self.setup_lvm()?;
|
||||
|
||||
self.setup_boot()?;
|
||||
|
||||
self.create_storage()?;
|
||||
|
||||
self.output("\n")?;
|
||||
self.header("Installing rootfs partitions\n")?;
|
||||
let args = format!("install-rootfs {}", self.artifact_path("citadel-rootfs.img").display());
|
||||
self.cmd(CITADEL_IMAGE, &args)?;
|
||||
self.cmd(CITADEL_IMAGE, &args)?;
|
||||
|
||||
self.cmd(LSBLK, format!("-o NAME,SIZE,TYPE,FSTYPE {}", &self.target_device))?;
|
||||
|
||||
self.cmd(VGCHANGE, "-an citadel")?;
|
||||
self.cmd(CRYPTSETUP, "luksClose luks-install")?;
|
||||
|
||||
self.header(format!("Install completed successfully in {} seconds", start.elapsed().as_secs()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn live_setup(&self) -> Result<()> {
|
||||
self.cmd(MOUNT, "-t tmpfs var-tmpfs /sysroot/var")?;
|
||||
self.cmd(MOUNT, "-t tmpfs home-tmpfs /sysroot/home")?;
|
||||
let _ = fs::read("/sys/class/zram-control/hot_add")?;
|
||||
// Create an 8gb zram disk to use as /storage partition
|
||||
fs::write("/sys/block/zram1/comp_algorithm", "lz4")?;
|
||||
fs::write("/sys/block/zram1/disksize", "8G")?;
|
||||
self.cmd(MKFS_BTRFS, "/dev/zram1")?;
|
||||
self.cmd(MOUNT, "/dev/zram1 /sysroot/storage")?;
|
||||
fs::create_dir_all("/sysroot/storage/realms")?;
|
||||
self.cmd(MOUNT, "--bind /sysroot/storage/realms /sysroot/realms")?;
|
||||
|
||||
let cmdline = fs::read_to_string("/proc/cmdline")?;
|
||||
if cmdline.contains("citadel.live") {
|
||||
self.setup_storage(Path::new("/sysroot/storage"), false)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn partition_disk(&self) -> Result<()> {
|
||||
self.header("Partitioning target disk")?;
|
||||
self.cmd(BLKDEACTIVATE, &self.target_device)?;
|
||||
self.parted("mklabel gpt")?;
|
||||
self.parted("mkpart boot fat32 1MiB 513MiB")?;
|
||||
self.parted("set 1 boot on")?;
|
||||
self.parted("mkpart data ext4 513MiB 100%")?;
|
||||
self.parted("set 2 lvm on")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parted(&self, cmdline: &str) -> Result<()> {
|
||||
let args = format!("-s {} {}", self.target_device, cmdline);
|
||||
self.cmd(PARTED, args)
|
||||
}
|
||||
|
||||
fn setup_luks(&self) -> Result<()> {
|
||||
self.header("Setting up LUKS disk encryption")?;
|
||||
fs::write(LUKS_PASSPHRASE_FILE, self.passphrase.as_bytes())?;
|
||||
let luks_partition = self.target_partition(2);
|
||||
|
||||
let args = format!(
|
||||
"-q --uuid={} luksFormat {} {}",
|
||||
LUKS_UUID, luks_partition, LUKS_PASSPHRASE_FILE
|
||||
);
|
||||
self.cmd(CRYPTSETUP, args)?;
|
||||
|
||||
let args = format!(
|
||||
"open --type luks --key-file {} {} luks-install",
|
||||
LUKS_PASSPHRASE_FILE, luks_partition
|
||||
);
|
||||
self.cmd(CRYPTSETUP, args)?;
|
||||
fs::remove_file(LUKS_PASSPHRASE_FILE)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_lvm(&self) -> Result<()> {
|
||||
self.header("Setting up LVM volumes")?;
|
||||
self.cmd(PVCREATE, "-ff --yes /dev/mapper/luks-install")?;
|
||||
self.cmd(VGCREATE, "--yes citadel /dev/mapper/luks-install")?;
|
||||
|
||||
self.cmd(LVCREATE, "--yes --size 2g --name rootfsA citadel")?;
|
||||
self.cmd(LVCREATE, "--yes --size 2g --name rootfsB citadel")?;
|
||||
self.cmd(LVCREATE, "--yes --extents 100%VG --name storage citadel")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_boot(&self) -> Result<()> {
|
||||
self.header("Setting up /boot partition")?;
|
||||
let boot_partition = self.target_partition(1);
|
||||
self.cmd(MKFS_VFAT, format!("-F 32 {}", boot_partition))?;
|
||||
|
||||
self.cmd(MOUNT, format!("{} {}", boot_partition, INSTALL_MOUNT))?;
|
||||
|
||||
fs::create_dir_all(format!("{}/loader/entries", INSTALL_MOUNT))?;
|
||||
|
||||
self.info("Writing /boot/loader/loader.conf")?;
|
||||
fs::write(format!("{}/loader/loader.conf", INSTALL_MOUNT), self.loader_conf())?;
|
||||
|
||||
self.info("Writing /boot/entries/citadel.conf")?;
|
||||
fs::write(format!("{}/loader/entries/citadel.conf", INSTALL_MOUNT), self.boot_conf())?;
|
||||
|
||||
self.copy_artifact("bzImage", INSTALL_MOUNT)?;
|
||||
self.copy_artifact("bootx64.efi", format!("{}/EFI/BOOT", INSTALL_MOUNT))?;
|
||||
|
||||
if self.install_syslinux {
|
||||
self.setup_syslinux()?;
|
||||
}
|
||||
|
||||
self.cmd(UMOUNT, INSTALL_MOUNT)?;
|
||||
|
||||
if self.install_syslinux {
|
||||
self.setup_syslinux_post_umount()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn loader_conf(&self) -> Vec<u8> {
|
||||
let mut v = Vec::new();
|
||||
writeln!(&mut v, "default citadel").unwrap();
|
||||
writeln!(&mut v, "timeout 5").unwrap();
|
||||
v
|
||||
}
|
||||
|
||||
fn boot_conf(&self) -> Vec<u8> {
|
||||
let mut v = Vec::new();
|
||||
writeln!(&mut v, "title Subgraph OS (Citadel)").unwrap();
|
||||
writeln!(&mut v, "linux /bzImage").unwrap();
|
||||
writeln!(&mut v, "options root=/dev/mapper/rootfs {}", KERNEL_CMDLINE).unwrap();
|
||||
v
|
||||
}
|
||||
|
||||
fn setup_syslinux(&self) -> Result<()> {
|
||||
self.header("Installing syslinux")?;
|
||||
let syslinux_src = self.artifact_path("syslinux");
|
||||
if !syslinux_src.exists() {
|
||||
bail!("No syslinux directory found in artifact directory, cannot install syslinux");
|
||||
}
|
||||
let dst = Path::new(INSTALL_MOUNT).join("syslinux");
|
||||
fs::create_dir_all(&dst)?;
|
||||
self.info("Copying syslinux files to /boot/syslinux")?;
|
||||
for entry in fs::read_dir(&syslinux_src)? {
|
||||
let entry = entry?;
|
||||
fs::copy(entry.path(), dst.join(entry.file_name()))?;
|
||||
}
|
||||
self.info("Writing syslinux.cfg")?;
|
||||
fs::write(dst.join("syslinux.cfg"), self.syslinux_conf())?;
|
||||
self.cmd(EXTLINUX, format!("--install {}", dst.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_syslinux_post_umount(&self) -> Result<()> {
|
||||
let mbrbin = self.artifact_path("syslinux/gptmbr.bin");
|
||||
if !mbrbin.exists() {
|
||||
bail!("Could not find MBR image: {}", mbrbin.display());
|
||||
}
|
||||
let args = format!("bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target_device);
|
||||
self.cmd(DD, args)?;
|
||||
self.parted("set 1 legacy_boot on")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn syslinux_conf(&self) -> Vec<u8> {
|
||||
let mut v = Vec::new();
|
||||
writeln!(&mut v, "UI menu.c32").unwrap();
|
||||
writeln!(&mut v, "PROMPT 0").unwrap();
|
||||
writeln!(&mut v, "").unwrap();
|
||||
writeln!(&mut v, "MENU TITLE Boot Subgraph OS (Citadel)").unwrap();
|
||||
writeln!(&mut v, "TIMEOUT 50").unwrap();
|
||||
writeln!(&mut v, "DEFAULT subgraph").unwrap();
|
||||
writeln!(&mut v, "").unwrap();
|
||||
writeln!(&mut v, "LABEL subgraph").unwrap();
|
||||
writeln!(&mut v, " MENU LABEL Subgraph OS").unwrap();
|
||||
writeln!(&mut v, " LINUX ../bzImage").unwrap();
|
||||
writeln!(&mut v, " APPEND root=/dev/mapper/rootfs {}", KERNEL_CMDLINE).unwrap();
|
||||
v
|
||||
}
|
||||
|
||||
fn create_storage(&self) -> Result<()> {
|
||||
self.header("Setting up /storage partition")?;
|
||||
self.cmd(MKFS_BTRFS, "/dev/mapper/citadel-storage")?;
|
||||
self.cmd(MOUNT, format!("/dev/mapper/citadel-storage {}", INSTALL_MOUNT))?;
|
||||
self.setup_storage(Path::new(INSTALL_MOUNT), true)?;
|
||||
self.cmd(UMOUNT, INSTALL_MOUNT)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_storage(&self, base: &Path, copy_resources: bool) -> Result<()> {
|
||||
if copy_resources {
|
||||
self.setup_storage_resources(base)?;
|
||||
}
|
||||
|
||||
self.setup_base_appimg(base)?;
|
||||
self.setup_main_realm(base)?;
|
||||
|
||||
self.info("Creating /Shared realms directory")?;
|
||||
fs::create_dir_all(base.join("realms/Shared"))?;
|
||||
self.cmd(CHOWN, format!("1000:1000 {}/realms/Shared", base.display()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_base_appimg(&self, base: &Path) -> Result<()> {
|
||||
self.header("Unpacking appimg rootfs")?;
|
||||
let appimg_dir = base.join("appimg");
|
||||
fs::create_dir_all(&appimg_dir)?;
|
||||
self.cmd(BTRFS, format!("subvolume create {}/base.appimg", appimg_dir.display()))?;
|
||||
|
||||
let xz_rootfs = self.artifact_path("appimg-rootfs.tar.xz");
|
||||
if xz_rootfs.exists() {
|
||||
self.cmd(XZ, format!("-d {}", xz_rootfs.display()))?;
|
||||
}
|
||||
let rootfs_bundle = self.artifact_path("appimg-rootfs.tar");
|
||||
self.cmd(TAR, format!("-C {}/base.appimg -xf {}", appimg_dir.display(), rootfs_bundle.display()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_main_realm(&self, base: &Path) -> Result<()> {
|
||||
self.header("Creating main realm")?;
|
||||
|
||||
let realm = base.join("realms/realm-main");
|
||||
let home = realm.join("home");
|
||||
|
||||
self.info("Creating home directory /realms/realm-main/home")?;
|
||||
fs::create_dir_all(&home)?;
|
||||
|
||||
self.cmd(BTRFS, format!("subvolume snapshot {}/appimg/base.appimg {}/rootfs",
|
||||
base.display(), realm.display()))?;
|
||||
|
||||
self.info("Copying .bashrc and .profile into home diectory")?;
|
||||
fs::copy(realm.join("rootfs/home/user/.bashrc"), home.join(".bashrc"))?;
|
||||
fs::copy(realm.join("rootfs/home/user/.profile"), home.join(".profile"))?;
|
||||
|
||||
self.cmd(CHOWN, format!("-R 1000:1000 {}", home.display()))?;
|
||||
|
||||
self.info("Creating default.realm symlink")?;
|
||||
unixfs::symlink("realm-main", base.join("realms/default.realm"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_storage_resources(&self, base: &Path) -> Result<()> {
|
||||
let channel = util::read_rootfs_channel()?;
|
||||
let resources = base.join("resources").join(channel);
|
||||
fs::create_dir_all(&resources)?;
|
||||
|
||||
self.copy_artifact(EXTRA_IMAGE_NAME, &resources)?;
|
||||
|
||||
let modules = self.modules_imagename();
|
||||
self.copy_artifact(&modules, &resources)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn modules_imagename(&self) -> String {
|
||||
let utsname = util::uname();
|
||||
let v = utsname.release().split("-").collect::<Vec<_>>();
|
||||
format!("citadel-modules-{}.img", v[0])
|
||||
}
|
||||
|
||||
fn target_partition(&self, num: usize) -> String {
|
||||
format!("{}{}", self.target_device, num)
|
||||
}
|
||||
|
||||
fn artifact_path(&self, filename: &str) -> PathBuf {
|
||||
Path::new(&self.artifact_directory).join(filename)
|
||||
}
|
||||
|
||||
fn copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P) -> Result<()> {
|
||||
self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?;
|
||||
let src = self.artifact_path(filename);
|
||||
let target = target.as_ref();
|
||||
if !target.exists() {
|
||||
fs::create_dir_all(target)?;
|
||||
}
|
||||
let dst = target.join(filename);
|
||||
fs::copy(src, dst)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn header<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
||||
self.output(format!("\n[+] {}\n", s.as_ref()))
|
||||
}
|
||||
|
||||
fn info<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
||||
self.output(format!(" [>] {}", s.as_ref()))
|
||||
}
|
||||
|
||||
fn output<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
||||
println!("{}", s.as_ref());
|
||||
if let Some(ref file) = self.logfile {
|
||||
writeln!(file.borrow_mut(), "{}", s.as_ref())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd<S: AsRef<str>>(&self, cmd_path: &str, args: S) -> Result<()> {
|
||||
self.output(format!(" # {} {}", cmd_path, args.as_ref()))?;
|
||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
|
||||
let result = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.output()?;
|
||||
|
||||
if !result.status.success() {
|
||||
match result.status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
|
||||
for line in String::from_utf8_lossy(&result.stdout).lines() {
|
||||
self.output(format!(" {}", line))?;
|
||||
}
|
||||
|
||||
for line in String::from_utf8_lossy(&result.stderr).lines() {
|
||||
self.output(format!("! {}", line))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
150
citadel-install/src/main.rs
Normal file
150
citadel-install/src/main.rs
Normal file
@ -0,0 +1,150 @@
|
||||
#[macro_use] extern crate failure;
|
||||
extern crate libc;
|
||||
extern crate rpassword;
|
||||
|
||||
mod installer;
|
||||
mod cli;
|
||||
mod util;
|
||||
mod disks;
|
||||
|
||||
use std::result;
|
||||
use std::path::Path;
|
||||
use std::env;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use std::fs;
|
||||
use std::process::exit;
|
||||
use failure::Error;
|
||||
|
||||
pub type Result<T> = result::Result<T,Error>;
|
||||
|
||||
fn main() {
|
||||
|
||||
let mut args = env::args();
|
||||
args.next();
|
||||
let result = match args.next() {
|
||||
Some(ref s) if s == "live-setup" => live_setup(),
|
||||
Some(ref s) if s == "copy-artifacts" => copy_artifacts(),
|
||||
Some(ref s) => cli_install_to(s),
|
||||
None => cli_install(),
|
||||
};
|
||||
|
||||
if let Err(ref e) = result {
|
||||
println!("Failed: {}", format_error(e));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_error(err: &Error) -> String {
|
||||
let mut output = err.to_string();
|
||||
let mut prev = err.as_fail();
|
||||
while let Some(next) = prev.cause() {
|
||||
output.push_str(": ");
|
||||
output.push_str(&next.to_string());
|
||||
prev = next;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn live_setup() -> Result<()> {
|
||||
if !Path::new("/etc/initrd-release").exists() {
|
||||
bail!("Not running in initramfs, cannot do live-setup");
|
||||
}
|
||||
let installer = installer::Installer::new();
|
||||
installer.live_setup()
|
||||
}
|
||||
|
||||
fn copy_artifacts() -> Result<()> {
|
||||
|
||||
for _ in 0..3 {
|
||||
if try_copy_artifacts()? {
|
||||
return Ok(())
|
||||
}
|
||||
// Try again after waiting for more devices to be discovered
|
||||
println!("Failed to find partition with images, trying again in 2 seconds");
|
||||
thread::sleep(time::Duration::from_secs(2));
|
||||
}
|
||||
Err(format_err!("Could not find partition containing resource images"))
|
||||
}
|
||||
|
||||
fn try_copy_artifacts() -> Result<bool> {
|
||||
let rootfs_image = Path::new("/boot/images/citadel-rootfs.img");
|
||||
// Already mounted?
|
||||
if rootfs_image.exists() {
|
||||
deploy_artifacts()?;
|
||||
return Ok(true);
|
||||
}
|
||||
for part in disks::DiskPartition::boot_partitions()? {
|
||||
part.mount("/boot")?;
|
||||
|
||||
if rootfs_image.exists() {
|
||||
deploy_artifacts()?;
|
||||
part.umount()?;
|
||||
return Ok(true);
|
||||
}
|
||||
part.umount()?;
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn deploy_artifacts() -> Result<()> {
|
||||
let run_images = Path::new("/run/images");
|
||||
if !run_images.exists() {
|
||||
fs::create_dir_all(run_images)?;
|
||||
util::exec_cmdline("/bin/mount", "-t tmpfs -o size=4g images /run/images")?;
|
||||
}
|
||||
|
||||
for entry in fs::read_dir("/boot/images")? {
|
||||
let entry = entry?;
|
||||
println!("Copying {:?} from /boot/images to /run/images", entry.file_name());
|
||||
fs::copy(entry.path(), run_images.join(entry.file_name()))?;
|
||||
}
|
||||
println!("Copying bzImage to /run/images");
|
||||
fs::copy("/boot/bzImage", "/run/images/bzImage")?;
|
||||
|
||||
println!("Copying bootx64.efi to /run/images");
|
||||
fs::copy("/boot/EFI/BOOT/bootx64.efi", "/run/images/bootx64.efi")?;
|
||||
|
||||
deploy_syslinux_artifacts()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deploy_syslinux_artifacts() -> Result<()> {
|
||||
let boot_syslinux = Path::new("/boot/syslinux");
|
||||
|
||||
if !boot_syslinux.exists() {
|
||||
println!("Not copying syslinux components because /boot/syslinux does not exist");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Copying contents of /boot/syslinux to /run/images/syslinux");
|
||||
|
||||
let run_images_syslinux = Path::new("/run/images/syslinux");
|
||||
fs::create_dir_all(run_images_syslinux)?;
|
||||
for entry in fs::read_dir(boot_syslinux)? {
|
||||
let entry = entry?;
|
||||
if let Some(ext) = entry.path().extension() {
|
||||
if ext == "c32" || ext == "bin" {
|
||||
fs::copy(entry.path(), run_images_syslinux.join(entry.file_name()))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cli_install() -> Result<()> {
|
||||
let ok = cli::run_cli_install()?;
|
||||
if !ok {
|
||||
println!("Install cancelled...");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cli_install_to(target: &str) -> Result<()> {
|
||||
let ok = cli::run_cli_install_with(target)?;
|
||||
if !ok {
|
||||
println!("Install cancelled...");
|
||||
}
|
||||
Ok(())
|
||||
}
|
157
citadel-install/src/util.rs
Normal file
157
citadel-install/src/util.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use std::mem;
|
||||
use std::ffi::CStr;
|
||||
use std::str::from_utf8_unchecked;
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::process::{Command,ExitStatus,Stdio};
|
||||
use std::fs;
|
||||
|
||||
use libc::{self, c_char};
|
||||
use failure::ResultExt;
|
||||
|
||||
use super::Result;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UtsName(libc::utsname);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl UtsName {
|
||||
pub fn sysname(&self) -> &str {
|
||||
to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn nodename(&self) -> &str {
|
||||
to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn release(&self) -> &str {
|
||||
to_str(&(&self.0.release as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &str {
|
||||
to_str(&(&self.0.version as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn machine(&self) -> &str {
|
||||
to_str(&(&self.0.machine as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uname() -> UtsName {
|
||||
unsafe {
|
||||
let mut ret: UtsName = mem::uninitialized();
|
||||
libc::uname(&mut ret.0);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_str<'a>(s: *const *const c_char) -> &'a str {
|
||||
unsafe {
|
||||
let res = CStr::from_ptr(*s).to_bytes();
|
||||
from_utf8_unchecked(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Disk {
|
||||
path: PathBuf,
|
||||
size: usize,
|
||||
size_str: String,
|
||||
model: String,
|
||||
}
|
||||
|
||||
impl Disk {
|
||||
pub fn probe_all() -> Result<Vec<Disk>> {
|
||||
let mut v = Vec::new();
|
||||
for entry in fs::read_dir("/sys/block")? {
|
||||
let path = entry?.path();
|
||||
if Disk::is_disk_device(&path) {
|
||||
let disk = Disk::read_device(&path)?;
|
||||
v.push(disk);
|
||||
}
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
fn is_disk_device(device: &Path) -> bool {
|
||||
device.join("device/model").exists()
|
||||
}
|
||||
|
||||
fn read_device(device: &Path) -> Result<Disk> {
|
||||
let path = Path::new("/dev/").join(device.file_name().unwrap());
|
||||
|
||||
let size = fs::read_to_string(device.join("size"))?
|
||||
.trim()
|
||||
.parse::<usize>()?;
|
||||
|
||||
let size_str = format!("{}G", size >> 21);
|
||||
|
||||
let model = fs::read_to_string(device.join("device/model"))?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(Disk { path, size, size_str, model })
|
||||
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn size_str(&self) -> &str {
|
||||
&self.size_str
|
||||
}
|
||||
|
||||
pub fn model(&self) -> &str {
|
||||
&self.model
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn read_rootfs_channel() -> Result<String> {
|
||||
let s = fs::read_to_string("/etc/citadel-channel")
|
||||
.context("Failed to open /etc/citadel-channel")?;
|
||||
match s.split_whitespace().next() {
|
||||
Some(s) => Ok(s.to_owned()),
|
||||
None => Err(format_err!("Failed to parse /etc/citadel-channel contents")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec_cmdline<S: AsRef<str>>(cmd_path: &str, args: S) -> Result<()> {
|
||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
|
||||
let status = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
match status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_cmdline_with_output<S: AsRef<str>>(cmd_path: &str, args: S) -> Result<String> {
|
||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
|
||||
let res = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.context(format!("unable to execute {}", cmd_path))?;
|
||||
|
||||
check_cmd_status(cmd_path, &res.status)?;
|
||||
Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned())
|
||||
}
|
||||
|
||||
fn check_cmd_status(cmd_path: &str, status: &ExitStatus) -> Result<()> {
|
||||
if !status.success() {
|
||||
match status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
#[macro_use] extern crate libcitadel;
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
extern crate clap;
|
||||
extern crate toml;
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use clap::{App,Arg,SubCommand,ArgMatches};
|
||||
use clap::AppSettings::*;
|
||||
use failure::Error;
|
||||
|
||||
use build::UpdateBuilder;
|
||||
use config::BuildConfig;
|
||||
use libcitadel::{Result,set_verbose};
|
||||
|
||||
mod build;
|
||||
mod config;
|
||||
mod util;
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
let app = App::new("citadel-mkimage")
|
||||
.about("Citadel update image builder")
|
||||
.settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder])
|
||||
|
||||
.subcommand(SubCommand::with_name("build")
|
||||
.about("Build an update image specified by a configuration file")
|
||||
.arg(Arg::with_name("build-file")
|
||||
.required(true)
|
||||
.help("Path to image build config file")));
|
||||
|
||||
set_verbose(true);
|
||||
let matches = app.get_matches();
|
||||
|
||||
let result = match matches.subcommand() {
|
||||
("build", Some(m)) => build_image(m),
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(ref e) = result {
|
||||
println!("Error: {}", format_error(e));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn format_error(err: &Error) -> String {
|
||||
let mut output = err.to_string();
|
||||
let mut prev = err.as_fail();
|
||||
while let Some(next) = prev.cause() {
|
||||
output.push_str(": ");
|
||||
output.push_str(&next.to_string());
|
||||
prev = next;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
|
||||
fn build_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let build_file = arg_matches.value_of("build-file").unwrap();
|
||||
let config = BuildConfig::load(build_file)?;
|
||||
let mut builder = UpdateBuilder::new(config)?;
|
||||
builder.build()?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
@ -1,110 +0,0 @@
|
||||
use std::process::{Command,Stdio};
|
||||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use failure::ResultExt;
|
||||
use libcitadel::Result;
|
||||
|
||||
|
||||
pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
let output = exec_command_with_output("/usr/bin/sha256sum", &[pathstr(path.as_ref())])
|
||||
.context(format!("failed to calculate sha256 on {}", path.as_ref().display()))?;
|
||||
|
||||
let v: Vec<&str> = output.split_whitespace().collect();
|
||||
Ok(v[0].trim().to_owned())
|
||||
}
|
||||
|
||||
pub fn xz_compress<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
exec_command("/usr/bin/xz", &["-T0", pathstr(path.as_ref())])
|
||||
.context(format!("failed to compress {}", path.as_ref().display()))?;
|
||||
Ok( ())
|
||||
}
|
||||
|
||||
pub fn verity_initial_hashtree<P: AsRef<Path>, Q: AsRef<Path>>(path: P, hashfile: Q) -> Result<VerityOutput> {
|
||||
let output = exec_command_with_output("/usr/sbin/veritysetup",
|
||||
&["format",pathstr(path.as_ref()), pathstr(hashfile.as_ref())])
|
||||
.context("veritysetup format command failed")?;
|
||||
|
||||
Ok(VerityOutput::parse(&output))
|
||||
}
|
||||
|
||||
fn pathstr(path: &Path) -> &str {
|
||||
path.to_str().unwrap()
|
||||
}
|
||||
|
||||
fn exec_command(cmd_path: &str, args: &[&str]) -> Result<()> {
|
||||
let status = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.context(format!("unable to execute {}", cmd_path))?;
|
||||
|
||||
if !status.success() {
|
||||
match status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_command_with_output(cmd_path: &str, args: &[&str]) -> Result<String> {
|
||||
let res = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.context(format!("unable to execute {}", cmd_path))?;
|
||||
|
||||
if !res.status.success() {
|
||||
match res.status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned())
|
||||
}
|
||||
|
||||
/// The output from the `veritysetup format` command can be parsed as key/value
|
||||
/// pairs. This class parses the output and stores it in a map for querying.
|
||||
pub struct VerityOutput {
|
||||
output: String,
|
||||
map: HashMap<String,String>,
|
||||
}
|
||||
|
||||
impl VerityOutput {
|
||||
/// Parse the string `output` as standard output from the dm-verity
|
||||
/// `veritysetup format` command.
|
||||
fn parse(output: &str) -> VerityOutput {
|
||||
let mut vo = VerityOutput {
|
||||
output: output.to_owned(),
|
||||
map: HashMap::new(),
|
||||
};
|
||||
for line in output.lines() {
|
||||
vo.parse_line(line);
|
||||
}
|
||||
vo
|
||||
}
|
||||
|
||||
fn parse_line(&mut self, line: &str) {
|
||||
let v = line.split(':')
|
||||
.map(|s| s.trim())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if v.len() == 2 {
|
||||
self.map.insert(v[0].to_owned(), v[1].to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_hash(&self) -> Option<&str> {
|
||||
self.map.get("Root hash").map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn salt(&self) -> Option<&str> {
|
||||
self.map.get("Salt").map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn output(&self) -> &str {
|
||||
&self.output
|
||||
}
|
||||
}
|
294
citadel-mount/Cargo.lock
generated
294
citadel-mount/Cargo.lock
generated
@ -1,23 +1,24 @@
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
name = "autocfg"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.12"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.24"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -29,25 +30,6 @@ name = "bitflags"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.26"
|
||||
@ -67,62 +49,12 @@ dependencies = [
|
||||
"libcitadel 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clear_on_drop"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -133,37 +65,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.2.0"
|
||||
@ -178,17 +83,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
name = "libcitadel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -220,93 +124,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.5.5"
|
||||
name = "ring"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_isaac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@ -314,27 +144,6 @@ name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.82"
|
||||
@ -347,28 +156,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.22"
|
||||
version = "0.15.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -383,7 +176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -396,13 +189,13 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.10.0"
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
name = "untrusted"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@ -430,54 +223,29 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"checksum backtrace 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eff3830839471718ef8522b9025b399bfb713e25bc220da721364efb660d7d"
|
||||
"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
|
||||
"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727"
|
||||
"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5"
|
||||
"checksum backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3fcce89e5ad5c8949caa9434501f7b55415b3e7ad5270cb88c75a8d35e8f1279"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
|
||||
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
|
||||
"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d"
|
||||
"checksum cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "389803e36973d242e7fecb092b2de44a3d35ac62524b3b9339e51d577d668e02"
|
||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||
"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17"
|
||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
"checksum curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3eacf6ff1b911e3170a8c400b402e10c86dc3cb166bd69034ebbc2b785fea4c2"
|
||||
"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
|
||||
"checksum ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cd66d8a16ef71c23cf5eeb2140d8d3cd293457c6c7fd6804b593397a933fcf1e"
|
||||
"checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
|
||||
"checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
|
||||
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
|
||||
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
|
||||
"checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74"
|
||||
"checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f"
|
||||
"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09"
|
||||
"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c"
|
||||
"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c"
|
||||
"checksum rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae9d223d52ae411a33cf7e54ec6034ec165df296ccd23533d671a28252b6f66a"
|
||||
"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a"
|
||||
"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372"
|
||||
"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db"
|
||||
"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
|
||||
"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
|
||||
"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05"
|
||||
"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3"
|
||||
"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
|
||||
"checksum ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe642b9dd1ba0038d78c4a3999d1ee56178b4d415c1e1fbaba83b06dce012f0"
|
||||
"checksum rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "01b90379b8664dd83460d59bdc5dd1fd3172b8913788db483ed1325171eab2f7"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6"
|
||||
"checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154"
|
||||
"checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0"
|
||||
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||
"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7"
|
||||
"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc"
|
||||
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
|
||||
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
||||
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
@ -6,5 +6,5 @@ homepage = "http://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
libcitadel = { path = "../libcitadel" }
|
||||
libc = "0.2"
|
||||
failure = "0.1.3"
|
||||
libc = "0.2"
|
||||
|
@ -6,24 +6,24 @@ pub struct BootSelection {
|
||||
}
|
||||
|
||||
impl BootSelection {
|
||||
pub fn choose_install_partition(config: &Config) -> Result<Partition> {
|
||||
let bs = BootSelection::load_partitions(config)?;
|
||||
pub fn choose_install_partition() -> Result<Partition> {
|
||||
let bs = BootSelection::load_partitions()?;
|
||||
match bs._choose_install_partition() {
|
||||
Some(p) => Ok(p.clone()),
|
||||
None => bail!("no partition found for installation"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn choose_boot_partition(config: &Config) -> Result<Partition> {
|
||||
let bs = BootSelection::load_partitions(config)?;
|
||||
pub fn choose_boot_partition() -> Result<Partition> {
|
||||
let bs = BootSelection::load_partitions()?;
|
||||
match bs._choose_boot_partition() {
|
||||
Some(p) => Ok(p.clone()),
|
||||
None => bail!("no partition found to boot from"),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_partitions(config: &Config) -> Result<BootSelection> {
|
||||
let partitions = Partition::rootfs_partitions(config)
|
||||
fn load_partitions() -> Result<BootSelection> {
|
||||
let partitions = Partition::rootfs_partitions()
|
||||
.map_err(|e| format_err!("Could not load rootfs partition info: {}", e))?;
|
||||
|
||||
Ok(BootSelection {
|
||||
|
@ -3,34 +3,37 @@
|
||||
|
||||
extern crate libc;
|
||||
|
||||
use std::env;
|
||||
use std::process::exit;
|
||||
use std::env;
|
||||
|
||||
use libcitadel::{Result,Config,CommandLine,set_verbose,format_error,ResourceImage};
|
||||
|
||||
use libcitadel::{Result,Config,CommandLine,set_verbose,ResourceImage};
|
||||
|
||||
mod boot_select;
|
||||
mod rootfs;
|
||||
mod uname;
|
||||
pub use boot_select::BootSelection;
|
||||
use rootfs::Rootfs;
|
||||
|
||||
|
||||
/// mount command supports 3 subcommands
|
||||
/// mount command supports 4 subcommands
|
||||
///
|
||||
/// citadel-mount rootfs
|
||||
/// citadel-mount modules
|
||||
/// citadel-mount extra
|
||||
/// citadel-mount copy-artifacts
|
||||
///
|
||||
/// 'rootfs' creates the /dev/mapper/rootfs device which will be mounted as root filesystem
|
||||
///
|
||||
/// 'modules' mounts a resource bundle containing kernel modules
|
||||
/// 'extra' mounts a resource bundle
|
||||
/// 'extra' mounts a resource bundle containing extra files
|
||||
///
|
||||
/// 'copy-artifacts' searches for a boot partition containing an /images
|
||||
/// directory and copies all image files to /run/images. Also, it
|
||||
/// copies bzImage and EFI/BOOT/bootx64.efi
|
||||
///
|
||||
|
||||
fn main() {
|
||||
|
||||
|
||||
if CommandLine::verbose() {
|
||||
set_verbose(true);
|
||||
}
|
||||
@ -52,8 +55,8 @@ fn main() {
|
||||
_ => Err(format_err!("Bad or missing argument")),
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
warn!("Failed: {}", e);
|
||||
if let Err(ref e) = result {
|
||||
warn!("Failed: {}", format_error(e));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@ -66,17 +69,14 @@ fn mount_rootfs(config: Config) -> Result<()> {
|
||||
|
||||
fn mount_modules(config: Config) -> Result<()> {
|
||||
info!("citadel-mount modules");
|
||||
let utsname = uname::uname();
|
||||
let v = utsname.release().split("-").collect::<Vec<_>>();
|
||||
let name = format!("citadel-modules-{}", v[0]);
|
||||
let mut image = ResourceImage::find(&name)?;
|
||||
let mut image = ResourceImage::find("modules")?;
|
||||
image.mount(&config)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mount_extra(config: Config) -> Result<()> {
|
||||
info!("citadel-mount extra");
|
||||
let mut image = ResourceImage::find("citadel-extra")?;
|
||||
let mut image = ResourceImage::find("extra")?;
|
||||
image.mount(&config)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
use std::process::Command;
|
||||
|
||||
use libcitadel::{BlockDev,Result,Partition,CommandLine,Config,ImageHeader,MetaInfo,PathExt};
|
||||
use BootSelection;
|
||||
use ResourceImage;
|
||||
use libcitadel::{BlockDev,CommandLine,Config,ImageHeader,Partition,Result,verity};
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
|
||||
use BootSelection;
|
||||
use ResourceImage;
|
||||
|
||||
pub struct Rootfs {
|
||||
config: Config,
|
||||
@ -17,20 +16,12 @@ impl Rootfs {
|
||||
}
|
||||
|
||||
pub fn setup(&self) -> Result<()> {
|
||||
if let Ok(partition) = BootSelection::choose_boot_partition(&self.config) {
|
||||
match self.setup_partition(partition) {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(err) => {
|
||||
warn!("Failed to set up selected boot partition: {}", err);
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
if CommandLine::install_mode() || CommandLine::live_mode() {
|
||||
self.setup_rootfs_resource()
|
||||
} else {
|
||||
let partition = BootSelection::choose_boot_partition()?;
|
||||
self.setup_partition(partition)
|
||||
}
|
||||
|
||||
fn allow_resource(&self) -> bool {
|
||||
CommandLine::install_mode() || CommandLine::recovery_mode()
|
||||
}
|
||||
|
||||
fn setup_partition(&self, partition: Partition) -> Result<()> {
|
||||
@ -42,36 +33,38 @@ impl Rootfs {
|
||||
}
|
||||
|
||||
fn setup_rootfs_resource(&self) -> Result<()> {
|
||||
if !self.allow_resource() {
|
||||
info!("Will not search for rootfs resource image because command line flags do not permit it");
|
||||
return Ok(())
|
||||
}
|
||||
info!("Searching for rootfs resource image");
|
||||
|
||||
let img = ResourceImage::find_rootfs()?;
|
||||
let hdr = ImageHeader::from_file(img.path())?;
|
||||
let metainfo = hdr.verified_metainfo(&self.config)?;
|
||||
|
||||
if CommandLine::noverity() {
|
||||
self.setup_resource_unverified(&img, &metainfo)
|
||||
self.setup_resource_unverified(&img)
|
||||
} else {
|
||||
self.setup_resource_verified(&img, &hdr, &metainfo)
|
||||
self.setup_resource_verified(&img)
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_resource_unverified(&self, img: &ResourceImage, metainfo: &MetaInfo) -> Result<()> {
|
||||
let loop_dev = img.path().setup_loop(
|
||||
Some(ImageHeader::HEADER_SIZE),
|
||||
Some(metainfo.nblocks() * 4096))?;
|
||||
|
||||
self.setup_linear_mapping(&loop_dev)
|
||||
fn setup_resource_unverified(&self, img: &ResourceImage) -> Result<()> {
|
||||
if img.is_compressed() {
|
||||
img.decompress()?;
|
||||
}
|
||||
let loopdev = img.create_loopdev()?;
|
||||
info!("Loop device created: {}", loopdev.display());
|
||||
self.setup_linear_mapping(&loopdev)
|
||||
}
|
||||
|
||||
fn setup_resource_verified(&self, img: &ResourceImage, hdr: &ImageHeader, metainfo: &MetaInfo) -> Result<()> {
|
||||
if !hdr.has_flag(ImageHeader::FLAG_HASH_TREE) {
|
||||
img.generate_verity_hashtree(&hdr, &metainfo)?;
|
||||
fn maybe_check_signature(&self, hdr: &ImageHeader) -> Result<()> {
|
||||
if !CommandLine::nosignatures() {
|
||||
let signature = hdr.signature();
|
||||
let metainfo = hdr.metainfo()?;
|
||||
metainfo.verify(&self.config, &signature)?;
|
||||
}
|
||||
img.path().verity_setup(ImageHeader::HEADER_SIZE, metainfo.nblocks(), metainfo.verity_root(), "rootfs")
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_resource_verified(&self, img: &ResourceImage) -> Result<()> {
|
||||
let _ = img.setup_verity_device(&self.config)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_partition_unverified(&self, partition: &Partition) -> Result<()> {
|
||||
@ -81,17 +74,14 @@ impl Rootfs {
|
||||
|
||||
fn setup_partition_verified(&self, partition: &Partition) -> Result<()> {
|
||||
info!("Creating /dev/mapper/rootfs dm-verity device");
|
||||
let nblocks = partition.metainfo().nblocks();
|
||||
let roothash = partition.metainfo().verity_root();
|
||||
|
||||
partition.path().verity_setup(nblocks * 4096, nblocks, roothash, "rootfs")
|
||||
self.maybe_check_signature(partition.header())?;
|
||||
verity::setup_partition_device(partition)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_linear_mapping(&self, blockdev: &Path) -> Result<()> {
|
||||
let dev = BlockDev::open_ro(blockdev)?;
|
||||
let table = format!("0 {} linear {} 0",
|
||||
dev.nsectors()?,
|
||||
blockdev.pathstr());
|
||||
let table = format!("0 {} linear {} 0", dev.nsectors()?, blockdev.display());
|
||||
|
||||
info!("/usr/sbin/dmsetup create rootfs --table '{}'", table);
|
||||
|
||||
|
@ -1,48 +0,0 @@
|
||||
use std::mem;
|
||||
use libc::{self, c_char};
|
||||
use std::ffi::CStr;
|
||||
use std::str::from_utf8_unchecked;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UtsName(libc::utsname);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl UtsName {
|
||||
pub fn sysname(&self) -> &str {
|
||||
to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn nodename(&self) -> &str {
|
||||
to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn release(&self) -> &str {
|
||||
to_str(&(&self.0.release as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &str {
|
||||
to_str(&(&self.0.version as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn machine(&self) -> &str {
|
||||
to_str(&(&self.0.machine as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uname() -> UtsName {
|
||||
unsafe {
|
||||
let mut ret: UtsName = mem::uninitialized();
|
||||
libc::uname(&mut ret.0);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_str<'a>(s: *const *const c_char) -> &'a str {
|
||||
unsafe {
|
||||
let res = CStr::from_ptr(*s).to_bytes();
|
||||
from_utf8_unchecked(res)
|
||||
}
|
||||
}
|
||||
|
294
libcitadel/Cargo.lock
generated
294
libcitadel/Cargo.lock
generated
@ -1,23 +1,24 @@
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
name = "autocfg"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.12"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.24"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -29,25 +30,6 @@ name = "bitflags"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.26"
|
||||
@ -58,62 +40,12 @@ name = "cfg-if"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "clear_on_drop"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -124,37 +56,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.2.0"
|
||||
@ -169,17 +74,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
name = "libcitadel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -211,93 +115,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.5.5"
|
||||
name = "ring"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_isaac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@ -305,27 +135,6 @@ name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.82"
|
||||
@ -338,28 +147,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.22"
|
||||
version = "0.15.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -374,7 +167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -387,13 +180,13 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.10.0"
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
name = "untrusted"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@ -421,54 +214,29 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"checksum backtrace 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eff3830839471718ef8522b9025b399bfb713e25bc220da721364efb660d7d"
|
||||
"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
|
||||
"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727"
|
||||
"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5"
|
||||
"checksum backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3fcce89e5ad5c8949caa9434501f7b55415b3e7ad5270cb88c75a8d35e8f1279"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
|
||||
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
|
||||
"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d"
|
||||
"checksum cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "389803e36973d242e7fecb092b2de44a3d35ac62524b3b9339e51d577d668e02"
|
||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||
"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17"
|
||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
"checksum curve25519-dalek 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3eacf6ff1b911e3170a8c400b402e10c86dc3cb166bd69034ebbc2b785fea4c2"
|
||||
"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
|
||||
"checksum ed25519-dalek 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cd66d8a16ef71c23cf5eeb2140d8d3cd293457c6c7fd6804b593397a933fcf1e"
|
||||
"checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
|
||||
"checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
|
||||
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
|
||||
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
|
||||
"checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74"
|
||||
"checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f"
|
||||
"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09"
|
||||
"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c"
|
||||
"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c"
|
||||
"checksum rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae9d223d52ae411a33cf7e54ec6034ec165df296ccd23533d671a28252b6f66a"
|
||||
"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a"
|
||||
"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372"
|
||||
"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db"
|
||||
"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
|
||||
"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
|
||||
"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05"
|
||||
"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3"
|
||||
"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
|
||||
"checksum ring 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe642b9dd1ba0038d78c4a3999d1ee56178b4d415c1e1fbaba83b06dce012f0"
|
||||
"checksum rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "01b90379b8664dd83460d59bdc5dd1fd3172b8913788db483ed1325171eab2f7"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6"
|
||||
"checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154"
|
||||
"checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0"
|
||||
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||
"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7"
|
||||
"checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc"
|
||||
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
|
||||
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
||||
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
@ -6,10 +6,9 @@ authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
nix = "0.12.0"
|
||||
ring = "=0.13.2"
|
||||
untrusted = "0.6.2"
|
||||
failure = "0.1.3"
|
||||
ed25519-dalek = "0.8.1"
|
||||
sha2 = "0.7.0"
|
||||
rand = "0.6"
|
||||
toml = "0.4.10"
|
||||
serde = "1.0.82"
|
||||
serde_derive = "1.0.82"
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
|
||||
use {Result,PathExt};
|
||||
use Result;
|
||||
|
||||
lazy_static! {
|
||||
static ref CMDLINE: CommandLine = match CommandLine::load() {
|
||||
@ -41,11 +41,20 @@ impl CommandLine {
|
||||
CommandLine::var_exists("citadel.noverity")
|
||||
}
|
||||
|
||||
pub fn nosignatures() -> bool {
|
||||
CommandLine::var_exists("citadel.nosignatures")
|
||||
}
|
||||
|
||||
/// Return `true` if variable citadel.install is present on kernel command line.
|
||||
pub fn install_mode() -> bool {
|
||||
CommandLine::var_exists("citadel.install")
|
||||
}
|
||||
|
||||
/// Return `true` if variable citadel.live is present on kernel command line.
|
||||
pub fn live_mode() -> bool {
|
||||
CommandLine::var_exists("citadel.live")
|
||||
}
|
||||
|
||||
/// Return `true` if variable citadel.recovery is present on kernel command line.
|
||||
pub fn recovery_mode() -> bool {
|
||||
CommandLine::var_exists("citadel.recovery")
|
||||
@ -61,7 +70,7 @@ impl CommandLine {
|
||||
}
|
||||
|
||||
fn load() -> Result<CommandLine> {
|
||||
let s = Path::new("/proc/cmdline").read_as_string()?;
|
||||
let s = fs::read_to_string("/proc/cmdline")?;
|
||||
let varmap = CommandLineParser::new(s).parse();
|
||||
Ok(CommandLine{varmap})
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
use ed25519_dalek::{Signature,PublicKey,Keypair};
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use sha2::Sha512;
|
||||
use toml;
|
||||
|
||||
use {Result,PathExt};
|
||||
use Result;
|
||||
use keys::{KeyPair,PublicKey,Signature};
|
||||
|
||||
|
||||
const DEFAULT_CONFIG_PATH: &str = "/usr/share/citadel/citadel-image.conf";
|
||||
@ -38,7 +38,7 @@ impl Config {
|
||||
}
|
||||
|
||||
fn from_path(path: &Path) -> Result<Config> {
|
||||
let s = path.read_as_string()?;
|
||||
let s = fs::read_to_string(path)?;
|
||||
let mut config = toml::from_str::<Config>(&s)?;
|
||||
for (k,v) in config.channel.iter_mut() {
|
||||
v.name = k.to_string();
|
||||
@ -107,19 +107,19 @@ impl Channel {
|
||||
|
||||
pub fn sign(&self, data: &[u8]) -> Result<Signature> {
|
||||
let keybytes = match self.keypair {
|
||||
Some(ref hex) => hex.from_hex()?,
|
||||
Some(ref hex) => hex,
|
||||
None => bail!("No private signing key available for channel {}", self.name),
|
||||
};
|
||||
let privkey = Keypair::from_bytes(&keybytes)?;
|
||||
let sig = privkey.sign::<Sha512>(data);
|
||||
|
||||
let privkey = KeyPair::from_hex(keybytes)?;
|
||||
let sig = privkey.sign(data)?;
|
||||
Ok(sig)
|
||||
}
|
||||
|
||||
pub fn verify(&self, data: &[u8], sigbytes: &[u8]) -> Result<()> {
|
||||
let keybytes = self.pubkey.from_hex()?;
|
||||
let pubkey = PublicKey::from_bytes(&keybytes)?;
|
||||
let sig = Signature::from_bytes(sigbytes)?;
|
||||
pubkey.verify::<Sha512>(data, &sig)?;
|
||||
pubkey.verify(data, sigbytes)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::{Write,Read};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use toml;
|
||||
|
||||
use {Channel,Config,Result,BlockDev};
|
||||
use blockdev::AlignedBuffer;
|
||||
use {BlockDev, Channel, Config, Result};
|
||||
|
||||
/// Expected magic value in header
|
||||
const MAGIC: &[u8] = b"SGOS";
|
||||
@ -61,10 +61,17 @@ fn is_valid_status_code(code: u8) -> bool {
|
||||
#[derive(Clone)]
|
||||
pub struct ImageHeader(RefCell<Vec<u8>>);
|
||||
|
||||
const CODE_TO_LABEL: [&str; 7] = ["Invalid", "New", "Try Boot", "Good", "Failed Boot", "Bad Signature", "Bad Metainfo"];
|
||||
const CODE_TO_LABEL: [&str; 7] = [
|
||||
"Invalid",
|
||||
"New",
|
||||
"Try Boot",
|
||||
"Good",
|
||||
"Failed Boot",
|
||||
"Bad Signature",
|
||||
"Bad Metainfo",
|
||||
];
|
||||
|
||||
impl ImageHeader {
|
||||
|
||||
pub const FLAG_PREFER_BOOT: u8 = 0x01; // Set to override usual strategy for choosing a partition to boot and force this one.
|
||||
pub const FLAG_HASH_TREE: u8 = 0x02; // dm-verity hash tree data is appended to the image
|
||||
pub const FLAG_DATA_COMPRESSED: u8 = 0x04; // The image data is compressed and needs to be uncompressed before use.
|
||||
@ -80,7 +87,6 @@ impl ImageHeader {
|
||||
/// Size of header block
|
||||
pub const HEADER_SIZE: usize = 4096;
|
||||
|
||||
|
||||
pub fn new() -> ImageHeader {
|
||||
let v = vec![0u8; ImageHeader::HEADER_SIZE];
|
||||
let header = ImageHeader(RefCell::new(v));
|
||||
@ -89,12 +95,9 @@ impl ImageHeader {
|
||||
}
|
||||
|
||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageHeader> {
|
||||
//let mut v = vec![0u8; ImageHeader::HEADER_SIZE];
|
||||
// XXX check file size is at least HEADER_SIZE
|
||||
let mut f = File::open(path.as_ref())?;
|
||||
ImageHeader::from_reader(&mut f)
|
||||
//f.read_exact(&mut v)?;
|
||||
//Ok(ImageHeader(RefCell::new(v)))
|
||||
}
|
||||
|
||||
pub fn from_reader<R: Read>(r: &mut R) -> Result<ImageHeader> {
|
||||
@ -106,7 +109,12 @@ impl ImageHeader {
|
||||
pub fn from_partition<P: AsRef<Path>>(path: P) -> Result<ImageHeader> {
|
||||
let mut dev = BlockDev::open_ro(path.as_ref())?;
|
||||
let nsectors = dev.nsectors()?;
|
||||
ensure!(nsectors >= 8, "{} is a block device bit it's too short ({} sectors)", path.as_ref().display(), nsectors);
|
||||
ensure!(
|
||||
nsectors >= 8,
|
||||
"{} is a block device bit it's too short ({} sectors)",
|
||||
path.as_ref().display(),
|
||||
nsectors
|
||||
);
|
||||
let mut buffer = AlignedBuffer::new(ImageHeader::HEADER_SIZE);
|
||||
dev.read_sectors(nsectors - 8, buffer.as_mut())?;
|
||||
let header = ImageHeader(RefCell::new(buffer.as_ref().into()));
|
||||
@ -116,13 +124,18 @@ impl ImageHeader {
|
||||
pub fn write_partition<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||
let mut dev = BlockDev::open_rw(path.as_ref())?;
|
||||
let nsectors = dev.nsectors()?;
|
||||
ensure!(nsectors >= 8, "{} is a block device bit it's too short ({} sectors)", path.as_ref().display(), nsectors);
|
||||
ensure!(
|
||||
nsectors >= 8,
|
||||
"{} is a block device bit it's too short ({} sectors)",
|
||||
path.as_ref().display(),
|
||||
nsectors
|
||||
);
|
||||
let buffer = AlignedBuffer::from_slice(&self.0.borrow());
|
||||
dev.write_sectors(nsectors - 8, buffer.as_ref())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verified_metainfo(&self, config: &Config) -> Result<MetaInfo> {
|
||||
pub fn metainfo(&self) -> Result<MetaInfo> {
|
||||
let mlen = self.metainfo_len();
|
||||
if mlen == 0 || mlen > MAX_METAINFO_LEN {
|
||||
bail!("Invalid metainfo-len field: {}", mlen);
|
||||
@ -130,7 +143,6 @@ impl ImageHeader {
|
||||
let mbytes = self.metainfo_bytes();
|
||||
let mut metainfo = MetaInfo::new(mbytes);
|
||||
metainfo.parse_toml()?;
|
||||
metainfo.verify(config, &self.signature())?;
|
||||
Ok(metainfo)
|
||||
}
|
||||
|
||||
@ -156,7 +168,9 @@ impl ImageHeader {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u8 { self.read_u8(5) }
|
||||
pub fn flags(&self) -> u8 {
|
||||
self.read_u8(5)
|
||||
}
|
||||
|
||||
pub fn has_flag(&self, flag: u8) -> bool {
|
||||
(self.flags() & flag) == flag
|
||||
@ -207,7 +221,19 @@ impl ImageHeader {
|
||||
let mlen = self.metainfo_len();
|
||||
// XXX assert mlen is good
|
||||
let sig = channel.sign(&self.0.borrow()[8..8 + mlen])?;
|
||||
self.write_bytes(8+mlen, &sig.to_bytes());
|
||||
self.write_bytes(8 + mlen, sig.to_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_signature(&self, config: &Config) -> Result<()> {
|
||||
let metainfo = self.metainfo()?;
|
||||
let channel = match config.channel(metainfo.channel()) {
|
||||
Some(channel) => channel,
|
||||
None => bail!("Cannot verify signature for channel '{}' because it does not exist in configuration file", metainfo.channel()),
|
||||
};
|
||||
channel
|
||||
.verify(metainfo.bytes(), &self.signature())
|
||||
.context("failed to verify header signature")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -253,18 +279,17 @@ impl ImageHeader {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MetaInfo {
|
||||
bytes: Vec<u8>,
|
||||
is_parsed: bool,
|
||||
is_valid: bool,
|
||||
toml: Option<MetaInfoToml>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
struct MetaInfoToml {
|
||||
#[serde(rename = "image-type")]
|
||||
image_type: String,
|
||||
channel: String,
|
||||
version: u32,
|
||||
#[serde(rename = "base-version")]
|
||||
@ -284,20 +309,19 @@ impl MetaInfo {
|
||||
MetaInfo {
|
||||
bytes,
|
||||
is_parsed: false,
|
||||
is_valid: false,
|
||||
toml: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.is_valid
|
||||
fn bytes(&self) -> &[u8] {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
pub fn parse_toml(&mut self) -> Result<()> {
|
||||
if !self.is_parsed {
|
||||
self.is_parsed = true;
|
||||
let toml = toml::from_slice::<MetaInfoToml>(&self.bytes)
|
||||
.context("parsing header metainfo")?;
|
||||
let toml =
|
||||
toml::from_slice::<MetaInfoToml>(&self.bytes).context("parsing header metainfo")?;
|
||||
self.toml = Some(toml);
|
||||
}
|
||||
Ok(())
|
||||
@ -308,17 +332,21 @@ impl MetaInfo {
|
||||
Some(channel) => channel,
|
||||
None => bail!("Channel '{}' not found in config file", self.channel()),
|
||||
};
|
||||
channel.verify(&self.bytes, signature)
|
||||
channel
|
||||
.verify(&self.bytes, signature)
|
||||
.context("Bad metainfo signature in header")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn toml(&self) -> &MetaInfoToml {
|
||||
assert!(self.is_valid);
|
||||
self.toml.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn image_type(&self) -> &str {
|
||||
self.toml().image_type.as_str()
|
||||
}
|
||||
|
||||
pub fn channel(&self) -> &str {
|
||||
self.toml().channel.as_str()
|
||||
}
|
||||
|
@ -1,111 +1,85 @@
|
||||
use Result;
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha512;
|
||||
use ed25519_dalek::{self,PublicKey,Keypair,Signature};
|
||||
use rustc_serialize::hex::{ToHex,FromHex};
|
||||
use ring::rand;
|
||||
use ring::signature::{self,Ed25519KeyPair,ED25519_PUBLIC_KEY_LEN,ED25519_PKCS8_V2_LEN};
|
||||
use untrusted::Input;
|
||||
use rustc_serialize::hex::{FromHex,ToHex};
|
||||
|
||||
pub const SIGNATURE_LENGTH: usize = ed25519_dalek::SIGNATURE_LENGTH;
|
||||
|
||||
///
|
||||
/// Keys for signing or verifying signatures. Small convenience
|
||||
/// wrapper around `ed25519_dalek`.
|
||||
/// wrapper around `ring/ed25519`.
|
||||
///
|
||||
pub enum SigningKeys {
|
||||
KEYPAIR(Keypair),
|
||||
PUBLIC(PublicKey),
|
||||
}
|
||||
|
||||
use self::SigningKeys::*;
|
||||
|
||||
impl SigningKeys {
|
||||
|
||||
/// Generate a new pair of signing/verifying keys using
|
||||
/// the system random number generator. The resulting
|
||||
/// `ed25519_dalek::KeyPair` can be extracted in an ascii
|
||||
/// hex encoded format for storage in configuration files
|
||||
/// with the `to_hex()` method.
|
||||
pub fn generate() -> Result<SigningKeys> {
|
||||
let mut rng = OsRng::new()?;
|
||||
let pair = Keypair::generate::<Sha512,_>(&mut rng);
|
||||
Ok(SigningKeys::KEYPAIR(pair))
|
||||
}
|
||||
|
||||
/// Load a `Keypair` from ascii hex representation.
|
||||
///
|
||||
/// The `hex` string is read from a configuration file
|
||||
/// and is used here to construct a `SigningKeys` instance
|
||||
/// which can then be used for signing (or verifying).
|
||||
pub fn from_keypair_hex(hex: String) -> Result<SigningKeys> {
|
||||
let bytes = hex.from_hex()?;
|
||||
let pair = Keypair::from_bytes(&bytes)?;
|
||||
Ok(SigningKeys::KEYPAIR(pair))
|
||||
}
|
||||
|
||||
/// Load only `PublicKey` from ascii hex representation
|
||||
///
|
||||
/// The string `hex` is read from a configuration file
|
||||
/// and is used here to construct a `SigningKeys` instance
|
||||
/// which can only be used for verifying signatures (not
|
||||
/// for signing).
|
||||
pub fn from_public_hex(hex: String) -> Result<SigningKeys> {
|
||||
let bytes = hex.from_hex()?;
|
||||
let public = PublicKey::from_bytes(&bytes)?;
|
||||
Ok(SigningKeys::PUBLIC(public))
|
||||
}
|
||||
pub struct PublicKey([u8; ED25519_PUBLIC_KEY_LEN]);
|
||||
pub struct KeyPair([u8; ED25519_PKCS8_V2_LEN]);
|
||||
pub struct Signature(signature::Signature);
|
||||
|
||||
/// Return ascii hex representation of internal `Keypair`
|
||||
/// or `PublicKey` depending on which variant `self` is.
|
||||
///
|
||||
/// Caller is expected to know which variant is being
|
||||
/// converted.
|
||||
pub fn to_hex(&self) -> String {
|
||||
match *self {
|
||||
KEYPAIR(ref pair) => pair.to_bytes().to_hex(),
|
||||
PUBLIC(ref public) => public.to_bytes().to_hex(),
|
||||
impl PublicKey {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<PublicKey> {
|
||||
let mut key = [0u8; ED25519_PUBLIC_KEY_LEN];
|
||||
key.copy_from_slice(bytes);
|
||||
Ok(PublicKey(key))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return ascii hex representation of the `PublicKey` associated
|
||||
/// with this instance.
|
||||
pub fn to_public_hex(&self) -> String {
|
||||
match *self {
|
||||
KEYPAIR(ref pair) => pair.public.to_bytes().to_hex(),
|
||||
PUBLIC(ref public) => public.to_bytes().to_hex(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign `data` with the private key associated with this instance
|
||||
/// using `Sha512` as the hashing algorithm. Caller must ensure
|
||||
/// that this instance is a `KEYPAIR` variant.
|
||||
///
|
||||
/// Returns signature of `data` encoded as a `SIGNATURE_LENGTH`
|
||||
/// byte array (64 bytes).
|
||||
///
|
||||
pub fn sign(&self, data: &[u8]) -> [u8; SIGNATURE_LENGTH] {
|
||||
let signature = match *self {
|
||||
KEYPAIR(ref pair) => pair.sign::<Sha512>(data),
|
||||
_ => panic!("Not a keypair, no signing key"),
|
||||
};
|
||||
signature.to_bytes()
|
||||
}
|
||||
|
||||
/// Verify that `signature` is a valid signature for `data` using the
|
||||
/// `PublicKey` associated with this instance. `signature` must be
|
||||
/// a slice of `SIGNATURE_LENGTH` bytes.
|
||||
///
|
||||
/// Returns `Ok(())` if signature is valid.
|
||||
///
|
||||
pub fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> {
|
||||
assert_eq!(signature.len(), SIGNATURE_LENGTH, "Signature bytes are not expected length");
|
||||
let signature = Signature::from_bytes(signature)?;
|
||||
self.pubkey().verify::<Sha512>(data, &signature)?;
|
||||
let signature = Input::from(signature);
|
||||
let data = Input::from(data);
|
||||
let pubkey = Input::from(&self.0);
|
||||
signature::verify(&signature::ED25519, pubkey, data, signature)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn pubkey(&self) -> &PublicKey {
|
||||
match *self {
|
||||
KEYPAIR(ref keypair) => &keypair.public,
|
||||
PUBLIC(ref public) => &public,
|
||||
impl KeyPair {
|
||||
/// Generate a new pair of signing/verifying keys using
|
||||
/// the system random number generator. The resulting
|
||||
/// `Ed25519KeyPair` can be extracted in an ascii
|
||||
/// hex encoded pkcs#8 format for storage in configuration files
|
||||
/// with the `to_hex()` method.
|
||||
pub fn generate() -> Result<KeyPair> {
|
||||
let rng = rand::SystemRandom::new();
|
||||
let bytes = Ed25519KeyPair::generate_pkcs8(&rng)?;
|
||||
KeyPair::from_bytes(&bytes)
|
||||
|
||||
}
|
||||
|
||||
pub fn from_hex(hex: &str) -> Result<KeyPair> {
|
||||
KeyPair::from_bytes(&hex.from_hex()?)
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<KeyPair> {
|
||||
let mut pair = [0u8; ED25519_PKCS8_V2_LEN];
|
||||
pair.copy_from_slice(bytes);
|
||||
Ok(KeyPair(pair))
|
||||
}
|
||||
|
||||
pub fn public_key_bytes(&self) -> Vec<u8> {
|
||||
let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0)).expect("failed to parse pkcs8 key");
|
||||
pair.public_key_bytes().to_vec()
|
||||
}
|
||||
|
||||
pub fn private_key_bytes(&self) -> Vec<u8> {
|
||||
self.0.to_vec()
|
||||
}
|
||||
|
||||
pub fn private_key_hex(&self) -> String {
|
||||
self.0.to_hex()
|
||||
}
|
||||
pub fn public_key_hex(&self) -> String {
|
||||
let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0)).expect("failed to parse pkcs8 key");
|
||||
pair.public_key_bytes().to_hex()
|
||||
}
|
||||
|
||||
pub fn sign(&self, data: &[u8]) -> Result<Signature> {
|
||||
let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0))?;
|
||||
let signature = pair.sign(data);
|
||||
Ok(Signature(signature))
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
pub fn to_bytes(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,8 @@ macro_rules! notify {
|
||||
extern crate libc;
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
extern crate ed25519_dalek;
|
||||
extern crate sha2;
|
||||
extern crate rand;
|
||||
extern crate ring;
|
||||
extern crate untrusted;
|
||||
extern crate rustc_serialize;
|
||||
|
||||
use std::cell::RefCell;
|
||||
@ -45,25 +44,38 @@ pub fn set_verbose(val: bool) {
|
||||
VERBOSE.with(|f| { *f.borrow_mut() = val });
|
||||
}
|
||||
|
||||
pub fn format_error(err: &Error) -> String {
|
||||
let mut output = err.to_string();
|
||||
let mut prev = err.as_fail();
|
||||
while let Some(next) = prev.cause() {
|
||||
output.push_str(": ");
|
||||
output.push_str(&next.to_string());
|
||||
prev = next;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
mod blockdev;
|
||||
mod config;
|
||||
mod keys;
|
||||
mod disks;
|
||||
mod cmdline;
|
||||
mod header;
|
||||
mod partition;
|
||||
mod resource;
|
||||
mod path_ext;
|
||||
pub mod util;
|
||||
pub mod verity;
|
||||
mod mount;
|
||||
|
||||
pub use config::Config;
|
||||
pub use config::Channel;
|
||||
pub use blockdev::BlockDev;
|
||||
pub use keys::SigningKeys;
|
||||
pub use cmdline::CommandLine;
|
||||
pub use header::{ImageHeader,MetaInfo};
|
||||
pub use path_ext::{PathExt,FileTypeResult,VerityOutput};
|
||||
pub use partition::Partition;
|
||||
pub use resource::ResourceImage;
|
||||
pub use keys::KeyPair;
|
||||
pub use mount::Mount;
|
||||
|
||||
|
||||
pub type Result<T> = result::Result<T,Error>;
|
||||
|
||||
|
59
libcitadel/src/mount.rs
Normal file
59
libcitadel/src/mount.rs
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
use std::path::{PathBuf,Path};
|
||||
use std::fs;
|
||||
use Result;
|
||||
|
||||
pub struct Mount {
|
||||
source: String,
|
||||
target: PathBuf,
|
||||
fstype: String,
|
||||
options: String,
|
||||
}
|
||||
|
||||
impl Mount {
|
||||
///
|
||||
/// Returns `true` if `path` matches the source field (first field)
|
||||
/// of any of the mount lines listed in /proc/mounts
|
||||
///
|
||||
pub fn is_path_mounted<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||
let path_str = path.as_ref().to_string_lossy();
|
||||
let mounts = Mount::all_mounts()?;
|
||||
Ok(mounts.into_iter().any(|m| m.source == path_str))
|
||||
}
|
||||
|
||||
pub fn all_mounts() -> Result<Vec<Mount>> {
|
||||
let s = fs::read_to_string("/proc/mounts")?;
|
||||
Ok(s.lines().flat_map(Mount::parse_mount_line).collect())
|
||||
}
|
||||
|
||||
fn parse_mount_line(line: &str) -> Option<Mount> {
|
||||
let parts = line.split_whitespace().collect::<Vec<_>>();
|
||||
if parts.len() < 4 {
|
||||
warn!("Failed to parse mount line: {}", line);
|
||||
return None;
|
||||
}
|
||||
Some(Mount{
|
||||
source: parts[0].to_string(),
|
||||
target: PathBuf::from(parts[1]),
|
||||
fstype: parts[2].to_string(),
|
||||
options: parts[3].to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &str {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub fn target(&self) -> &Path {
|
||||
&self.target
|
||||
}
|
||||
|
||||
pub fn fstype(&self) -> &str {
|
||||
&self.fstype
|
||||
}
|
||||
|
||||
pub fn options(&self) -> &str {
|
||||
&self.options
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs;
|
||||
use {Config,Result,ImageHeader,MetaInfo,PathExt};
|
||||
use {Config,CommandLine,Result,ImageHeader,MetaInfo,Mount};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Partition {
|
||||
@ -16,27 +16,27 @@ struct HeaderInfo {
|
||||
}
|
||||
|
||||
impl Partition {
|
||||
pub fn rootfs_partitions(config: &Config) -> Result<Vec<Partition>> {
|
||||
pub fn rootfs_partitions() -> Result<Vec<Partition>> {
|
||||
let mut v = Vec::new();
|
||||
for path in rootfs_partition_paths()? {
|
||||
let partition = Partition::load(&path, config)?;
|
||||
let partition = Partition::load(&path)?;
|
||||
v.push(partition);
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
fn load(dev: &Path, config: &Config) -> Result<Partition> {
|
||||
let is_mounted = is_path_mounted(dev)?;
|
||||
let header = Partition::load_header(dev, config)?;
|
||||
fn load(dev: &Path) -> Result<Partition> {
|
||||
let is_mounted = is_in_use(dev)?;
|
||||
let header = Partition::load_header(dev)?;
|
||||
Ok(Partition::new(dev, header, is_mounted))
|
||||
}
|
||||
|
||||
fn load_header(dev: &Path, config: &Config) -> Result<Option<HeaderInfo>> {
|
||||
fn load_header(dev: &Path) -> Result<Option<HeaderInfo>> {
|
||||
let header = ImageHeader::from_partition(dev)?;
|
||||
if !header.is_magic_valid() {
|
||||
return Ok(None);
|
||||
}
|
||||
let metainfo = match header.verified_metainfo(config) {
|
||||
let metainfo = match header.metainfo() {
|
||||
Ok(metainfo) => metainfo,
|
||||
Err(e) => {
|
||||
warn!("Reading partition {}: {}", dev.display(), e);
|
||||
@ -116,11 +116,48 @@ impl Partition {
|
||||
if self.header().status() == ImageHeader::STATUS_TRY_BOOT {
|
||||
self.write_status(ImageHeader::STATUS_FAILED)?;
|
||||
}
|
||||
// XXX verify signature
|
||||
|
||||
if !CommandLine::nosignatures() {
|
||||
if let Err(e) = self.header().verify_signature(config) {
|
||||
warn!("Signature verification failed on partition: {}", e);
|
||||
self.write_status(ImageHeader::STATUS_BAD_SIG)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_in_use(path: &Path) -> Result<bool> {
|
||||
if Mount::is_path_mounted(path)? {
|
||||
return Ok(true);
|
||||
}
|
||||
let holders = count_block_holders(path)?;
|
||||
Ok(holders > 0)
|
||||
}
|
||||
|
||||
//
|
||||
// Resolve /dev/mapper/citadel-rootfsX symlink to actual device name
|
||||
// and then inspect directory /sys/block/${DEV}/holders and return
|
||||
// the number of entries this directory contains. If this directory
|
||||
// is not empty then device belongs to another device mapping.
|
||||
//
|
||||
fn count_block_holders(path: &Path) -> Result<usize> {
|
||||
if !path.exists() {
|
||||
bail!("Path to rootfs device does not exist: {}", path.display());
|
||||
}
|
||||
let resolved = fs::canonicalize(path)?;
|
||||
let fname = match resolved.file_name() {
|
||||
Some(s) => s,
|
||||
None => bail!("path does not have filename?"),
|
||||
};
|
||||
let holders_dir =
|
||||
Path::new("/sys/block")
|
||||
.join(fname)
|
||||
.join("holders");
|
||||
let count = fs::read_dir(holders_dir)?.count();
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn rootfs_partition_paths() -> Result<Vec<PathBuf>> {
|
||||
let mut rootfs_paths = Vec::new();
|
||||
for dent in fs::read_dir("/dev/mapper")? {
|
||||
@ -145,19 +182,3 @@ fn path_filename(path: &Path) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns `true` if `path` matches the source field (first field)
|
||||
/// of any of the mount lines listed in /proc/mounts
|
||||
///
|
||||
pub fn is_path_mounted(path: &Path) -> Result<bool> {
|
||||
let path_str = path.to_str().unwrap();
|
||||
|
||||
for line in Path::new("/proc/mounts").read_as_lines()? {
|
||||
if let Some(s) = line.split_whitespace().next() {
|
||||
if s == path_str {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
@ -1,354 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader,BufRead,Read};
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::process::{Command,Stdio};
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use Result;
|
||||
|
||||
/// A collection of utility methods added to `Path` to perform various types of operations
|
||||
/// on files and directories.
|
||||
pub trait PathExt {
|
||||
|
||||
/// Run sha256sum command on file `self` and return output as a hex `String`
|
||||
fn sha256(&self) -> Result<String>;
|
||||
|
||||
/// Write file `self` to partition device with dd command.
|
||||
fn copy_to_partition<P: AsRef<Path>>(&self, partition: P) -> Result<()>;
|
||||
|
||||
/// Read entire file `self` and return contents as a `String`
|
||||
fn read_as_string(&self) -> Result<String>;
|
||||
|
||||
/// Read entire file `self` and return contents as a `Vec` of individual lines.
|
||||
fn read_as_lines(&self) -> Result<Vec<String>>;
|
||||
|
||||
/// Return `true` if path `self` is mounted.
|
||||
fn is_mounted(&self) -> bool;
|
||||
|
||||
/// Compress file `self` with xz utility.
|
||||
fn xz_compress(&self) -> Result<()>;
|
||||
|
||||
/// Uncompress file `self` with xz utility.
|
||||
fn xz_uncompress(&self) -> Result<()>;
|
||||
|
||||
/// Run /usr/bin/file command on file `self` and return output as `FileTypeResult`
|
||||
fn file_type(&self) -> Result<FileTypeResult>;
|
||||
|
||||
/// Mount path `self` to `target`
|
||||
fn mount<P: AsRef<Path>>(&self, target: P) -> Result<()>;
|
||||
|
||||
/// Mount path `self` to `target` with additional argument `args` to mount command.
|
||||
fn mount_with_args<P: AsRef<Path>>(&self, target: P, args: &str) -> Result<()>;
|
||||
|
||||
/// Bind mount path `self` to path `target`.
|
||||
fn bind_mount<P: AsRef<Path>>(&self, target: P) -> Result<()>;
|
||||
|
||||
/// Set up loop device for file `self` with optional offset and size limit.
|
||||
/// Returns `PathBuf` to associated loop device upon success.
|
||||
fn setup_loop(&self, offset: Option<usize>, sizelimit: Option<usize>) -> Result<PathBuf>;
|
||||
|
||||
/// Unmount path `self`
|
||||
fn umount(&self) -> Result<()>;
|
||||
|
||||
/// Return Partition Type GUID for a block device by running lsblk command
|
||||
fn partition_type_guid(&self) -> Result<String>;
|
||||
|
||||
/// Generate dm-verity hashtree for a disk image and store in an external file
|
||||
/// Parse output from command into VerityOutput structure and return it.
|
||||
fn verity_initial_hashtree<P: AsRef<Path>>(&self, hashfile: P) -> Result<VerityOutput>;
|
||||
|
||||
/// Generate dm-verity hashtree with a given salt value and append it to the same image.
|
||||
///
|
||||
/// device
|
||||
/// Parse output from command into VerityOutput structure and return it.
|
||||
fn verity_regenerate_hashtree(&self, offset: usize, nblocks: usize, salt: &str) -> Result<VerityOutput>;
|
||||
|
||||
///
|
||||
fn verity_setup(&self, offset: usize, nblocks: usize, roothash: &str, devname: &str) -> Result<()>;
|
||||
|
||||
|
||||
|
||||
/// Return path as a string without error checking
|
||||
fn pathstr(&self) -> &str;
|
||||
}
|
||||
|
||||
impl PathExt for Path {
|
||||
fn sha256(&self) -> Result<String> {
|
||||
let output = exec_command_with_output("/usr/bin/sha256sum", &[self.pathstr()])
|
||||
.context(format!("failed to calculate sha256 on {}", self.display()))?;
|
||||
|
||||
let v: Vec<&str> = output.split_whitespace().collect();
|
||||
Ok(v[0].trim().to_owned())
|
||||
}
|
||||
|
||||
fn copy_to_partition<P: AsRef<Path>>(&self, partition: P) -> Result<()> {
|
||||
let if_arg = format!("if={}", self.pathstr());
|
||||
let of_arg = format!("of={}", partition.as_ref().pathstr());
|
||||
exec_command("/usr/bin/dd", &[ if_arg.as_str(), of_arg.as_str(), "bs=4M" ])
|
||||
.context(format!("failed to copy {} to {} with dd", self.display(), partition.as_ref().display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_as_string(&self) -> Result<String> {
|
||||
let mut f = File::open(&self)?;
|
||||
let mut buffer = String::new();
|
||||
f.read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn read_as_lines(&self) -> Result<Vec<String>> {
|
||||
let mut v = Vec::new();
|
||||
let f = File::open(&self)?;
|
||||
let reader = BufReader::new(f);
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
v.push(line);
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
fn is_mounted(&self) -> bool {
|
||||
exec_command("/usr/bin/findmnt", &[self.pathstr()]).is_ok()
|
||||
}
|
||||
|
||||
fn xz_compress(&self) -> Result<()> {
|
||||
exec_command("/usr/bin/xz", &["-T0", self.pathstr()])
|
||||
.context(format!("failed to compress {}", self.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn xz_uncompress(&self) -> Result<()> {
|
||||
exec_command("/usr/bin/xz", &["-d", self.pathstr()])
|
||||
.context(format!("failed to uncompress {}", self.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn file_type(&self) -> Result<FileTypeResult> {
|
||||
let output = exec_command_with_output("/usr/bin/file", &["-b", self.pathstr()])
|
||||
.context(format!("failed to run /usr/bin/file on {}", self.display()))?;
|
||||
|
||||
Ok(FileTypeResult(output))
|
||||
}
|
||||
|
||||
fn mount<P: AsRef<Path>>(&self, target: P) -> Result<()> {
|
||||
let target = target.as_ref().to_str().unwrap();
|
||||
exec_command("/usr/bin/mount", &[self.pathstr(), target])
|
||||
.context(format!("failed to mount {}", self.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mount_with_args<P: AsRef<Path>>(&self, target: P, args: &str) -> Result<()> {
|
||||
let target = target.as_ref().to_str().unwrap();
|
||||
exec_command("/usr/bin/mount", &[args, self.pathstr(), target])
|
||||
.context(format!("failed to mount {} with args [{}]", self.display(), args))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bind_mount<P: AsRef<Path>>(&self, target: P) -> Result<()> {
|
||||
let target = target.as_ref().to_str().unwrap();
|
||||
exec_command("/usr/bin/mount", &["--bind", self.pathstr(), target])
|
||||
.context(format!("failed to bind mount {} to {}", self.display(), target))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_loop(&self, offset: Option<usize>, sizelimit: Option<usize>) -> Result<PathBuf> {
|
||||
let offset_str: String;
|
||||
let sizelimit_str: String;
|
||||
|
||||
let mut v = Vec::new();
|
||||
|
||||
if let Some(val) = offset {
|
||||
v.push("--offset");
|
||||
offset_str = val.to_string();
|
||||
v.push(&offset_str);
|
||||
}
|
||||
|
||||
if let Some(val) = sizelimit {
|
||||
v.push("--sizelimit");
|
||||
sizelimit_str = val.to_string();
|
||||
v.push(&sizelimit_str);
|
||||
}
|
||||
|
||||
v.push("-f");
|
||||
v.push(self.pathstr());
|
||||
|
||||
let output = exec_command_with_output("/sbin/losetup", &v)
|
||||
.context(format!("failed to run /sbin/losetup on {}", self.display()))?;
|
||||
Ok(PathBuf::from(output))
|
||||
}
|
||||
|
||||
fn umount(&self) -> Result<()> {
|
||||
exec_command("/usr/bin/umount", &[self.pathstr()])
|
||||
.context(format!("failed to umount {}", self.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn partition_type_guid(&self) -> Result<String> {
|
||||
let output = exec_command_with_output("/usr/bin/lsblk", &["-dno", "PARTTYPE", self.pathstr()])
|
||||
.context(format!("failed to run lsblk on {}", self.display()))?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn verity_initial_hashtree<P: AsRef<Path>>(&self, hashfile: P) -> Result<VerityOutput> {
|
||||
let output = exec_command_with_output("/usr/sbin/veritysetup",
|
||||
&["format", self.pathstr(), hashfile.as_ref().pathstr()])
|
||||
.context("veritysetup format command failed")?;
|
||||
|
||||
Ok(VerityOutput::parse(&output))
|
||||
}
|
||||
|
||||
fn verity_regenerate_hashtree(&self, offset: usize, nblocks: usize, salt: &str) -> Result<VerityOutput> {
|
||||
let arg_offset = format!("--hash-offset={}", offset);
|
||||
let arg_blocks = format!("--data-blocks={}", nblocks);
|
||||
let arg_salt = format!("--salt={}", salt);
|
||||
let arg_path = self.pathstr();
|
||||
|
||||
let output = exec_command_with_output("/usr/sbin/veritysetup",
|
||||
&[arg_offset.as_str(), arg_blocks.as_str(), arg_salt.as_str(),
|
||||
"format", arg_path, arg_path])
|
||||
.context("running veritysetup command failed")?;
|
||||
|
||||
Ok(VerityOutput::parse(&output))
|
||||
}
|
||||
|
||||
fn verity_setup(&self, offset: usize, nblocks: usize, roothash: &str, devname: &str) -> Result<()> {
|
||||
let arg_offset = format!("--hash-offset={}", offset);
|
||||
let arg_blocks = format!("--data-blocks={}", nblocks);
|
||||
let arg_path = self.pathstr();
|
||||
|
||||
exec_command("/usr/sbin/veritysetup",
|
||||
&[arg_offset.as_str(), arg_blocks.as_str(), "create",
|
||||
devname, arg_path, arg_path, roothash])
|
||||
.context("running veritysetup failed")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pathstr(&self) -> &str {
|
||||
self.to_str().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn exec_command(cmd_path: &str, args: &[&str]) -> bool {
|
||||
Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.expect(&format!("unable to execute {}", cmd_path))
|
||||
.success()
|
||||
}
|
||||
|
||||
|
||||
fn exec_command_with_output(cmd_path: &str, args: &[&str]) -> (bool, String) {
|
||||
let res = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.expect(&format!("unable to execute {}", cmd_path));
|
||||
|
||||
if res.status.success() {
|
||||
(true, String::from_utf8(res.stdout).unwrap().trim().to_owned())
|
||||
} else {
|
||||
(false, String::new())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
fn exec_command(cmd_path: &str, args: &[&str]) -> Result<()> {
|
||||
let status = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.context(format!("unable to execute {}", cmd_path))?;
|
||||
|
||||
if !status.success() {
|
||||
match status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_command_with_output(cmd_path: &str, args: &[&str]) -> Result<String> {
|
||||
let res = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.context(format!("unable to execute {}", cmd_path))?;
|
||||
|
||||
if !res.status.success() {
|
||||
match res.status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned())
|
||||
}
|
||||
|
||||
pub struct FileTypeResult(String);
|
||||
|
||||
impl FileTypeResult {
|
||||
pub fn is_xz_compressed(&self) -> bool {
|
||||
self.0.starts_with("XZ")
|
||||
}
|
||||
|
||||
pub fn is_ext2_image(&self) -> bool {
|
||||
self.0.starts_with("Linux rev 1.0 ext2 filesystem data")
|
||||
}
|
||||
|
||||
pub fn output(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// The output from the `veritysetup format` command can be parsed as key/value
|
||||
/// pairs. This class parses the output and stores it in a map for querying.
|
||||
pub struct VerityOutput {
|
||||
output: String,
|
||||
map: HashMap<String,String>,
|
||||
}
|
||||
|
||||
impl VerityOutput {
|
||||
/// Parse the string `output` as standard output from the dm-verity
|
||||
/// `veritysetup format` command.
|
||||
fn parse(output: &str) -> VerityOutput {
|
||||
let mut vo = VerityOutput {
|
||||
output: output.to_owned(),
|
||||
map: HashMap::new(),
|
||||
};
|
||||
for line in output.lines() {
|
||||
vo.parse_line(line);
|
||||
}
|
||||
vo
|
||||
}
|
||||
|
||||
fn parse_line(&mut self, line: &str) {
|
||||
let v = line.split(':')
|
||||
.map(|s| s.trim())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if v.len() == 2 {
|
||||
self.map.insert(v[0].to_owned(), v[1].to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_hash(&self) -> Option<&str> {
|
||||
self.map.get("Root hash").map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn salt(&self) -> Option<&str> {
|
||||
self.map.get("Salt").map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn output(&self) -> &str {
|
||||
&self.output
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,20 +1,14 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self,Read};
|
||||
use std::io::{self,Seek,SeekFrom};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use disks::DiskPartition;
|
||||
use Result;
|
||||
use CommandLine;
|
||||
use PathExt;
|
||||
use ImageHeader;
|
||||
use Config;
|
||||
use MetaInfo;
|
||||
use {CommandLine,Config,ImageHeader,MetaInfo,Result,Partition,Mount,verity,util};
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
const STORAGE_BASEDIR: &str = "/sysroot/storage/resources";
|
||||
const BOOT_BASEDIR: &str = "/boot/images";
|
||||
const RUN_DIRECTORY: &str = "/run/images";
|
||||
|
||||
|
||||
/// Locates and mounts a resource image file.
|
||||
///
|
||||
/// Resource image files are files containing a disk image that can be
|
||||
@ -23,69 +17,89 @@ const RUN_DIRECTORY: &str = "/run/images";
|
||||
/// contains a list of bind mounts to perform from the mounted tree to
|
||||
/// the system rootfs.
|
||||
///
|
||||
/// dm-verity will be set up for the mounted image unless the `citadel.noverity`
|
||||
/// variable is set on the kernel command line.
|
||||
/// Various kernel command line options control how the resource file is
|
||||
/// searched for and how it is mounted.
|
||||
///
|
||||
/// Resource image files will first be searched for in the `/storage/resources/`
|
||||
/// directory (with `/sysroot` prepended since these mounts are performed in initramfs).
|
||||
/// If the storage device does not exist or kernel command line variables are set
|
||||
/// indicating either an install mode or recovery mode boot then search of storage
|
||||
/// directory is not performed.
|
||||
/// citadel.noverity: Mount image without dm-verity. Also do not verify header signature.
|
||||
/// citadel.nosignatures: Do not verify header signature.
|
||||
///
|
||||
/// If not located in `/storage/resources` the image file will be searched for on all
|
||||
/// UEFI ESP partitions on the system. If found on a boot partition, it will be
|
||||
/// copied to `/run/images` and uncompressed if necessary.
|
||||
/// A requested image file will be searched for first in /run/images and if not found there the
|
||||
/// usual location of /storage/resources is searched.
|
||||
///
|
||||
pub struct ResourceImage {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
header: ImageHeader,
|
||||
metainfo: MetaInfo,
|
||||
}
|
||||
|
||||
impl ResourceImage {
|
||||
|
||||
/// Locate and return a resource image with `name`.
|
||||
/// First the /storage/resources directory is searched, and if not found there,
|
||||
/// each EFI boot partition will also be searched.
|
||||
/// First the /run/images directory is searched, and if not found there,
|
||||
/// the image will be searched for in /storage/resources/$channel
|
||||
pub fn find(name: &str) -> Result<ResourceImage> {
|
||||
let mut img = ResourceImage::new(name);
|
||||
let search_storage = !(CommandLine::install_mode() || CommandLine::recovery_mode());
|
||||
if search_storage && ResourceImage::ensure_storage_mounted() {
|
||||
let path = PathBuf::from(format!("{}/{}.img", STORAGE_BASEDIR, name));
|
||||
if path.exists() {
|
||||
img.path.push(path);
|
||||
info!("Image found at {}", img.path.display());
|
||||
return Ok(img)
|
||||
}
|
||||
let filename = ResourceImage::image_filename(name);
|
||||
|
||||
let run_path = Path::new(RUN_DIRECTORY).join(&filename);
|
||||
|
||||
let channel = ResourceImage::read_rootfs_channel()?;
|
||||
let storage_path = Path::new(STORAGE_BASEDIR).join(channel).join(&filename);
|
||||
|
||||
if run_path.exists() {
|
||||
return ResourceImage::from_path(run_path);
|
||||
}
|
||||
|
||||
if img.search_boot_partitions() {
|
||||
Ok(img)
|
||||
if !ResourceImage::ensure_storage_mounted()? {
|
||||
bail!("Unable to mount /storage");
|
||||
}
|
||||
|
||||
if storage_path.exists() {
|
||||
ResourceImage::from_path(storage_path)
|
||||
} else {
|
||||
Err(format_err!("Failed to find resource image: {}", name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Locate and return a rootfs resource image.
|
||||
/// Only EFI boot partitions will be searched.
|
||||
/// Locate a rootfs image in /run/images and return it
|
||||
pub fn find_rootfs() -> Result<ResourceImage> {
|
||||
let mut img = ResourceImage::new("citadel-rootfs");
|
||||
if img.search_boot_partitions() {
|
||||
info!("Found rootfs image at {}", img.path.display());
|
||||
Ok(img)
|
||||
let rootfs_path = Path::new(RUN_DIRECTORY).join(ResourceImage::image_filename("rootfs"));
|
||||
if rootfs_path.exists() {
|
||||
info!("Found rootfs image at {}", rootfs_path.display());
|
||||
ResourceImage::from_path(rootfs_path)
|
||||
} else {
|
||||
Err(format_err!("Failed to find rootfs resource image"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<ResourceImage> {
|
||||
let header = ImageHeader::from_file(path.as_ref())?;
|
||||
if !header.is_magic_valid() {
|
||||
bail!("Image file {} does not have a valid header", path.as_ref().display());
|
||||
}
|
||||
let metainfo = header.metainfo()?;
|
||||
Ok(ResourceImage::new(path.as_ref(), header, metainfo))
|
||||
}
|
||||
|
||||
pub fn is_valid_image(&self) -> bool {
|
||||
self.header.is_magic_valid()
|
||||
}
|
||||
|
||||
/// Return path to the resource image file.
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn new(name: &str) -> ResourceImage {
|
||||
pub fn header(&self) -> &ImageHeader {
|
||||
&self.header
|
||||
}
|
||||
|
||||
pub fn metainfo(&self) -> &MetaInfo {
|
||||
&self.metainfo
|
||||
}
|
||||
|
||||
fn new(path: &Path, header: ImageHeader, metainfo: MetaInfo) -> ResourceImage {
|
||||
ResourceImage {
|
||||
name: name.to_owned(),
|
||||
path: PathBuf::new(),
|
||||
path: path.to_owned(),
|
||||
header, metainfo,
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,172 +113,157 @@ impl ResourceImage {
|
||||
self.process_manifest_file()
|
||||
}
|
||||
|
||||
fn mount_verity(&self, config: &Config) -> Result<()> {
|
||||
let hdr = ImageHeader::from_file(&self.path)?;
|
||||
let metainfo = hdr.verified_metainfo(config)?;
|
||||
|
||||
info!("Setting up dm-verity device for image");
|
||||
|
||||
if !hdr.has_flag(ImageHeader::FLAG_HASH_TREE) {
|
||||
self.generate_verity_hashtree(&hdr, &metainfo)?;
|
||||
pub fn is_compressed(&self) -> bool {
|
||||
self.header.has_flag(ImageHeader::FLAG_DATA_COMPRESSED)
|
||||
}
|
||||
|
||||
let devname = format!("verity-{}", self.name);
|
||||
pub fn has_verity_hashtree(&self) -> bool {
|
||||
self.header.has_flag(ImageHeader::FLAG_HASH_TREE)
|
||||
}
|
||||
|
||||
self.path.verity_setup(ImageHeader::HEADER_SIZE, metainfo.nblocks(), metainfo.verity_root(), &devname)?;
|
||||
pub fn decompress(&self) -> Result<()> {
|
||||
if !self.is_compressed() {
|
||||
return Ok(())
|
||||
}
|
||||
self.decompress_and_generate_hashtree(true)
|
||||
}
|
||||
|
||||
// Avoid copying the body twice in the common case that image is both
|
||||
// compressed and needs hashtree generated.
|
||||
fn decompress_and_generate_hashtree(&self, decompress_only: bool) -> Result<()> {
|
||||
assert!(self.is_compressed());
|
||||
let mut tmpfile = self.extract_body_to_tmpfile(Some("xz"))?;
|
||||
util::xz_decompress(&tmpfile)?;
|
||||
tmpfile.set_extension("");
|
||||
self.header.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED);
|
||||
|
||||
if !decompress_only && !self.has_verity_hashtree() {
|
||||
verity::generate_image_hashtree(&tmpfile, &self.metainfo)?;
|
||||
self.header.set_flag(ImageHeader::FLAG_HASH_TREE);
|
||||
}
|
||||
self.write_image_from_tmpfile(&tmpfile)
|
||||
}
|
||||
|
||||
fn extract_body_to_tmpfile(&self, extension: Option<&str>) -> Result<PathBuf> {
|
||||
let mut reader = File::open(&self.path)?;
|
||||
reader.seek(SeekFrom::Start(4096))?;
|
||||
fs::create_dir_all("/tmp/citadel-image-tmp")?;
|
||||
let mut path = Path::new("/tmp/citadel-image-tmp").join(format!("{}-tmp", &self.metainfo.image_type()));
|
||||
if let Some(ext) = extension {
|
||||
path.set_extension(ext);
|
||||
}
|
||||
let mut out = File::create(&path)?;
|
||||
io::copy(&mut reader, &mut out)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn write_to_partition(&self, partition: &Partition) -> Result<()> {
|
||||
if self.metainfo.image_type() != "rootfs" {
|
||||
bail!("Cannot write to partition, image type is not rootfs");
|
||||
}
|
||||
|
||||
if !self.has_verity_hashtree() {
|
||||
self.generate_verity_hashtree()?;
|
||||
}
|
||||
|
||||
info!("writing rootfs image to {}", partition.path().display());
|
||||
let args = format!("if={} of={} bs=4096 skip=1",
|
||||
self.path.display(), partition.path().display());
|
||||
util::exec_cmdline("/bin/dd", args)?;
|
||||
|
||||
self.header.set_status(ImageHeader::STATUS_NEW);
|
||||
self.header.write_partition(partition.path())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_image_from_tmpfile(&self, tmpfile: &Path) -> Result<()> {
|
||||
let mut reader = File::open(&tmpfile)?;
|
||||
let mut out = File::create(self.path())?;
|
||||
self.header.write_header(&mut out)?;
|
||||
io::copy(&mut reader, &mut out)?;
|
||||
fs::remove_file(tmpfile)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mount_verity(&self, config: &Config) -> Result<()> {
|
||||
let verity_dev = self.setup_verity_device(config)?;
|
||||
|
||||
info!("Mounting dm-verity device to {}", self.mount_path().display());
|
||||
|
||||
fs::create_dir_all(self.mount_path())?;
|
||||
Path::new(&format!("/dev/mapper/{}", devname)).mount(self.mount_path())
|
||||
|
||||
util::mount(&verity_dev.to_string_lossy(), self.mount_path(), None)
|
||||
|
||||
}
|
||||
|
||||
pub fn generate_verity_hashtree(&self, hdr: &ImageHeader, metainfo: &MetaInfo) -> Result<()> {
|
||||
info!("Generating dm-verity hash tree for image");
|
||||
if !hdr.has_flag(ImageHeader::FLAG_HASH_TREE) {
|
||||
let _ = self.path.verity_regenerate_hashtree(ImageHeader::HEADER_SIZE, metainfo.nblocks(), metainfo.verity_salt())?;
|
||||
hdr.set_flag(ImageHeader::FLAG_HASH_TREE);
|
||||
let w = fs::OpenOptions::new().write(true).open(&self.path)?;
|
||||
hdr.write_header(w)?;
|
||||
pub fn setup_verity_device(&self, config: &Config) -> Result<PathBuf> {
|
||||
if !CommandLine::nosignatures() {
|
||||
self.header.verify_signature(config)?;
|
||||
}
|
||||
Ok(())
|
||||
info!("Setting up dm-verity device for image");
|
||||
if !self.has_verity_hashtree() {
|
||||
self.generate_verity_hashtree()?;
|
||||
}
|
||||
verity::setup_image_device(self.path())
|
||||
}
|
||||
|
||||
pub fn generate_verity_hashtree(&self) -> Result<()> {
|
||||
if self.has_verity_hashtree() {
|
||||
return Ok(())
|
||||
}
|
||||
info!("Generating dm-verity hash tree for image {}", self.path.display());
|
||||
if self.is_compressed() {
|
||||
info!("Image is compressed, so need to decompress first");
|
||||
self.decompress_and_generate_hashtree(false)
|
||||
} else {
|
||||
let tmpfile = self.extract_body_to_tmpfile(None)?;
|
||||
verity::generate_image_hashtree(&tmpfile, &self.metainfo)?;
|
||||
self.header.set_flag(ImageHeader::FLAG_HASH_TREE);
|
||||
self.write_image_from_tmpfile(&tmpfile)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_verity(&self) -> Result<bool> {
|
||||
if !self.has_verity_hashtree() {
|
||||
self.generate_verity_hashtree()?;
|
||||
}
|
||||
verity::verify_image(self.path(), &self.metainfo)
|
||||
}
|
||||
|
||||
pub fn verify_shasum(&self) -> Result<bool> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// Mount the resource image but use a simple loop mount rather than setting up a dm-verity
|
||||
// device for the image.
|
||||
fn mount_noverity(&self) -> Result<()> {
|
||||
info!("loop mounting image to {} (noverity)", self.mount_path().display());
|
||||
fs::create_dir_all(self.mount_path())?;
|
||||
Path::new(&self.path).mount_with_args(self.mount_path(), "-oloop,ro,offset=4096")
|
||||
|
||||
if self.is_compressed() {
|
||||
self.decompress()?;
|
||||
}
|
||||
|
||||
// Copy resource image from /boot partition to /run/images and uncompress
|
||||
// with xz if it is a compressed file. Update `self.path` to refer to the
|
||||
// copy rather than the source file.
|
||||
fn copy_to_run(&mut self) -> bool {
|
||||
if let Err(err) = fs::create_dir_all(RUN_DIRECTORY) {
|
||||
warn!("Error creating {} directory: {}", RUN_DIRECTORY, err);
|
||||
return false;
|
||||
}
|
||||
let mut new_path = PathBuf::from(RUN_DIRECTORY);
|
||||
new_path.set_file_name(self.path.file_name().unwrap());
|
||||
if let Err(err) = fs::copy(&self.path, &new_path) {
|
||||
warn!("Error copying {} to {}: {}", self.path.display(), new_path.display(), err);
|
||||
return false;
|
||||
let mount_path = self.mount_path();
|
||||
let loopdev = self.create_loopdev()?;
|
||||
|
||||
info!("Loop device created: {}", loopdev.display());
|
||||
info!("Mounting to: {}", mount_path.display());
|
||||
|
||||
fs::create_dir_all(&mount_path)?;
|
||||
|
||||
util::mount(&loopdev.to_string_lossy(), mount_path, Some("-oro"))
|
||||
}
|
||||
|
||||
if new_path.extension().unwrap() == "xz" {
|
||||
if let Err(err) = new_path.xz_uncompress() {
|
||||
warn!("Error uncompressing {}: {}", new_path.display(), err);
|
||||
return false;
|
||||
}
|
||||
let stem = new_path.file_stem().unwrap().to_owned();
|
||||
new_path.set_file_name(stem);
|
||||
}
|
||||
self.path.push(new_path);
|
||||
|
||||
if let Err(err) = self.maybe_decompress_image() {
|
||||
warn!("Error decompressing image: {}", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn maybe_decompress_image(&self) -> Result<()> {
|
||||
let mut image = File::open(&self.path)?;
|
||||
let hdr = ImageHeader::from_reader(&mut image)?;
|
||||
if !hdr.has_flag(ImageHeader::FLAG_DATA_COMPRESSED) {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
info!("Decompressing internal image data");
|
||||
|
||||
let mut tempfile = self.write_compressed_tempfile(&mut image)?;
|
||||
|
||||
tempfile.xz_uncompress()?;
|
||||
tempfile.set_extension("");
|
||||
|
||||
self.write_uncompressed_image(&hdr, &tempfile)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_compressed_tempfile<R: Read>(&self, reader: &mut R) -> Result<PathBuf> {
|
||||
let mut tmp_path = Path::new(RUN_DIRECTORY).join(format!("{}-tmp", self.name));
|
||||
tmp_path.set_extension("xz");
|
||||
let mut tmp_out = File::create(&tmp_path)?;
|
||||
io::copy(reader, &mut tmp_out)?;
|
||||
Ok(tmp_path)
|
||||
}
|
||||
|
||||
fn write_uncompressed_image(&self, hdr: &ImageHeader, tempfile: &Path) -> Result<()> {
|
||||
let mut image_out = File::create(&self.path)?;
|
||||
hdr.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED);
|
||||
hdr.write_header(&mut image_out)?;
|
||||
|
||||
let mut tmp_in = File::open(&tempfile)?;
|
||||
io::copy(&mut tmp_in, &mut image_out)?;
|
||||
fs::remove_file(tempfile)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Search for resource image file on any currently mounted /boot
|
||||
// as well as on every UEFI ESP partition on the system.
|
||||
//
|
||||
// Return `true` if found
|
||||
fn search_boot_partitions(&mut self) -> bool {
|
||||
// Is /boot already mounted?
|
||||
if Path::new("/boot").is_mounted() {
|
||||
if self.search_current_boot_partition() && self.copy_to_run() {
|
||||
info!("Image found on currently mounted boot partition and copied to {}", self.path.display());
|
||||
return true;
|
||||
}
|
||||
let _ = Path::new("/boot").umount();
|
||||
}
|
||||
|
||||
let partitions = match DiskPartition::boot_partitions() {
|
||||
Ok(ps) => ps,
|
||||
Err(e) => {
|
||||
warn!("Error reading disk partition information: {}", e);
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
for part in partitions {
|
||||
if part.mount("/boot") {
|
||||
if self.search_current_boot_partition() && self.copy_to_run() {
|
||||
part.umount();
|
||||
info!("Image found on boot partition {} and copied to {}", part.path().display(), self.path.display());
|
||||
return true;
|
||||
}
|
||||
part.umount();
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// Search for resource file on currently mounted /boot partition
|
||||
// with both .img and .img.xz extensions.
|
||||
// Return `true` if found.
|
||||
fn search_current_boot_partition(&mut self) -> bool {
|
||||
let mut path = PathBuf::from(BOOT_BASEDIR);
|
||||
|
||||
for ext in ["img", "img.xz"].iter() {
|
||||
path.set_file_name(format!("{}.{}", self.name, ext));
|
||||
if path.exists() {
|
||||
self.path.push(path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
pub fn create_loopdev(&self) -> Result<PathBuf> {
|
||||
let args = format!("--offset 4096 -f --show {}", self.path.display());
|
||||
let output = util::exec_cmdline_with_output("/sbin/losetup", args)?;
|
||||
Ok(PathBuf::from(output))
|
||||
}
|
||||
|
||||
// Return the path at which to mount this resource image.
|
||||
fn mount_path(&self) -> PathBuf {
|
||||
PathBuf::from(format!("{}/{}.mountpoint", RUN_DIRECTORY, self.name))
|
||||
PathBuf::from(format!("{}/{}.mountpoint", RUN_DIRECTORY, self.metainfo.image_type()))
|
||||
}
|
||||
|
||||
// Read and process a manifest file in the root directory of a mounted resource image.
|
||||
@ -273,13 +272,14 @@ impl ResourceImage {
|
||||
let manifest = self.mount_path().join("manifest");
|
||||
if !manifest.exists() {
|
||||
warn!("No manifest file found for resource image: {}", self.path.display());
|
||||
} else {
|
||||
for line in manifest.read_as_lines()? {
|
||||
return Ok(())
|
||||
}
|
||||
let s = fs::read_to_string(manifest)?;
|
||||
for line in s.lines() {
|
||||
if let Err(e) = self.process_manifest_line(&line) {
|
||||
warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -305,29 +305,49 @@ impl ResourceImage {
|
||||
let to = Path::new("/sysroot").join(path_to);
|
||||
|
||||
info!("Bind mounting {} to {} from manifest", from.display(), to.display());
|
||||
|
||||
from.bind_mount(&to)
|
||||
util::mount(&from.to_string_lossy(), to, Some("--bind"))
|
||||
}
|
||||
|
||||
// If the /storage directory is not mounted, attempt to mount it.
|
||||
// Return true if already mounted or if the attempt to mount it succeeds.
|
||||
fn ensure_storage_mounted() -> bool {
|
||||
if Path::new("/sysroot/storage").is_mounted() {
|
||||
return true
|
||||
fn ensure_storage_mounted() -> Result<bool> {
|
||||
if Mount::is_path_mounted("/dev/mapper/citadel-storage")? {
|
||||
return Ok(true);
|
||||
}
|
||||
let path = Path::new("/dev/mapper/citadel-storage");
|
||||
if !path.exists() {
|
||||
return false
|
||||
return Ok(false);
|
||||
}
|
||||
info!("Mounting /sysroot/storage directory");
|
||||
const MOUNT_ARGS: &str = "-odefaults,nossd,noatime,commit=120";
|
||||
match path.mount_with_args("/sysroot/storage", MOUNT_ARGS) {
|
||||
Err(e) => {
|
||||
warn!("failed to mount /sysroot/storage: {}", e);
|
||||
false
|
||||
},
|
||||
Ok(()) => true,
|
||||
let res = util::mount(
|
||||
"/dev/mapper/citadel-storage",
|
||||
"/sysroot/storage",
|
||||
Some("-odefaults,nossd,noatime,commit=120")
|
||||
);
|
||||
if let Err(err) = res {
|
||||
warn!("failed to mount /sysroot/storage: {}", err);
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn read_rootfs_channel() -> Result<String> {
|
||||
let s = fs::read_to_string("/sysroot/etc/citadel-channel")
|
||||
.context("Failed to open /sysroot/etc/citadel-channel")?;
|
||||
match s.split_whitespace().next() {
|
||||
Some(s) => Ok(s.to_owned()),
|
||||
None => Err(format_err!("Failed to parse /sysroot/etc/citadel-channel contents")),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn image_filename(image_type: &str) -> String {
|
||||
if image_type == "modules" {
|
||||
let utsname = util::uname();
|
||||
let v = utsname.release().split("-").collect::<Vec<_>>();
|
||||
format!("citadel-modules-{}.img", v[0])
|
||||
} else {
|
||||
format!("citadel-{}.img", image_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
129
libcitadel/src/util.rs
Normal file
129
libcitadel/src/util.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use std::path::Path;
|
||||
use std::process::{Command,ExitStatus,Stdio};
|
||||
use std::mem;
|
||||
use libc::{self, c_char};
|
||||
use std::ffi::CStr;
|
||||
use std::str::from_utf8_unchecked;
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use Result;
|
||||
|
||||
pub fn ensure_command_exists(cmd_path: &str) -> Result<()> {
|
||||
if !Path::new(cmd_path).exists() {
|
||||
bail!("Cannot execute '{}': command does not exist");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_cmdline<S: AsRef<str>>(cmd_path: &str, args: S) -> Result<()> {
|
||||
ensure_command_exists(cmd_path)?;
|
||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
|
||||
let status = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.status()?;
|
||||
|
||||
check_cmd_status(cmd_path, &status)
|
||||
}
|
||||
|
||||
pub fn exec_cmdline_with_output<S: AsRef<str>>(cmd_path: &str, args: S) -> Result<String> {
|
||||
ensure_command_exists(cmd_path)?;
|
||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
|
||||
let res = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.context(format!("unable to execute {}", cmd_path))?;
|
||||
|
||||
check_cmd_status(cmd_path, &res.status)?;
|
||||
Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned())
|
||||
}
|
||||
|
||||
fn check_cmd_status(cmd_path: &str, status: &ExitStatus) -> Result<()> {
|
||||
if !status.success() {
|
||||
match status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
let output = exec_cmdline_with_output("/usr/bin/sha256sum", format!("{}", path.as_ref().display()))
|
||||
.context(format!("failed to calculate sha256 on {}", path.as_ref().display()))?;
|
||||
|
||||
let v: Vec<&str> = output.split_whitespace().collect();
|
||||
Ok(v[0].trim().to_owned())
|
||||
}
|
||||
|
||||
pub fn xz_compress<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
exec_cmdline("/usr/bin/xz", format!("-T0 {}", path.as_ref().display()))
|
||||
.context(format!("failed to compress {}", path.as_ref().display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn xz_decompress<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
exec_cmdline("/usr/bin/xz", format!("-d {}", path.as_ref().display()))
|
||||
.context(format!("failed to decompress {}", path.as_ref().display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mount<P: AsRef<Path>>(source: &str, target: P, options: Option<&str>) -> Result<()> {
|
||||
let paths = format!("{} {}", source, target.as_ref().display());
|
||||
let args = match options {
|
||||
Some(s) => format!("{} {}", s, paths),
|
||||
None => paths,
|
||||
};
|
||||
exec_cmdline("/usr/bin/mount", args)
|
||||
}
|
||||
|
||||
pub fn umount<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
let args = format!("{}", path.as_ref().display());
|
||||
exec_cmdline("/usr/bin/umount", args)
|
||||
}
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UtsName(libc::utsname);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl UtsName {
|
||||
pub fn sysname(&self) -> &str {
|
||||
to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn nodename(&self) -> &str {
|
||||
to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn release(&self) -> &str {
|
||||
to_str(&(&self.0.release as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &str {
|
||||
to_str(&(&self.0.version as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
|
||||
pub fn machine(&self) -> &str {
|
||||
to_str(&(&self.0.machine as *const c_char ) as *const *const c_char)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uname() -> UtsName {
|
||||
unsafe {
|
||||
let mut ret: UtsName = mem::uninitialized();
|
||||
libc::uname(&mut ret.0);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_str<'a>(s: *const *const c_char) -> &'a str {
|
||||
unsafe {
|
||||
let res = CStr::from_ptr(*s).to_bytes();
|
||||
from_utf8_unchecked(res)
|
||||
}
|
||||
}
|
124
libcitadel/src/verity.rs
Normal file
124
libcitadel/src/verity.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::collections::HashMap;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use failure::ResultExt;
|
||||
use {Result,ImageHeader,MetaInfo,Partition,util};
|
||||
|
||||
const VERITYSETUP: &str = "/sbin/veritysetup";
|
||||
const LOSETUP: &str = "/sbin/losetup";
|
||||
|
||||
/// Generate dm-verity hashtree for a disk image and store in external file.
|
||||
/// Parse output from veritysetup command and return as `VerityOutput`.
|
||||
pub fn generate_initial_hashtree<P: AsRef<Path>, Q:AsRef<Path>>(source: P, hashtree: Q) -> Result<VerityOutput> {
|
||||
let args = format!("format {} {}", source.as_ref().display(), hashtree.as_ref().display());
|
||||
let output = util::exec_cmdline_with_output(VERITYSETUP, args)
|
||||
.context("creating initial hashtree with veritysetup format failed")?;
|
||||
Ok(VerityOutput::parse(&output))
|
||||
}
|
||||
|
||||
pub fn generate_image_hashtree<P: AsRef<Path>>(image: P, metainfo: &MetaInfo) -> Result<VerityOutput> {
|
||||
let args = format!("--hash-offset={} --data-blocks={} --salt={} format {} {}",
|
||||
metainfo.nblocks() * 4096, metainfo.nblocks(), metainfo.verity_salt(),
|
||||
image.as_ref().display(), image.as_ref().display());
|
||||
|
||||
let output = util::exec_cmdline_with_output(VERITYSETUP, args)
|
||||
.context("Failed to generate hashtree with veritysetup")?;
|
||||
|
||||
// XXX check that root hash matches
|
||||
|
||||
Ok(VerityOutput::parse(&output))
|
||||
}
|
||||
|
||||
pub fn verify_image<P: AsRef<Path>>(image: P, metainfo: &MetaInfo) -> Result<bool> {
|
||||
let arg_offset = format!("--hash-offset={}", metainfo.nblocks() * 4096);
|
||||
let loopdev = create_image_loop_device(image.as_ref())?;
|
||||
|
||||
let status = Command::new(VERITYSETUP)
|
||||
.args(&[ arg_offset.as_str(), "verify", &loopdev, &loopdev, metainfo.verity_root()])
|
||||
.stderr(Stdio::inherit())
|
||||
.status()?;
|
||||
|
||||
util::exec_cmdline(LOSETUP, format!("-d {}", loopdev))
|
||||
.context("Error removing loop device created for verification")?;
|
||||
|
||||
Ok(status.success())
|
||||
}
|
||||
|
||||
pub fn setup_image_device<P: AsRef<Path>>(image: P) -> Result<PathBuf> {
|
||||
let header = ImageHeader::from_file(image.as_ref())?;
|
||||
let metainfo = header.metainfo()?;
|
||||
|
||||
let devname = if metainfo.image_type() == "rootfs" {
|
||||
String::from("rootfs")
|
||||
} else {
|
||||
format!("verity-{}", metainfo.image_type())
|
||||
};
|
||||
|
||||
let loopdev = create_image_loop_device(image.as_ref())?;
|
||||
|
||||
setup_device(&loopdev, &devname, metainfo.nblocks(), metainfo.verity_root())
|
||||
}
|
||||
|
||||
pub fn setup_partition_device(partition: &Partition) -> Result<PathBuf> {
|
||||
let metainfo = partition.header().metainfo()?;
|
||||
let srcdev = partition.path().to_str().unwrap();
|
||||
setup_device(srcdev, "rootfs", metainfo.nblocks(), metainfo.verity_root())
|
||||
}
|
||||
|
||||
fn setup_device(srcdev: &str, devname: &str, nblocks: usize, roothash: &str) -> Result<PathBuf> {
|
||||
let args = format!("--hash-offset={} --data-blocks={} create {} {} {} {}",
|
||||
nblocks * 4096, nblocks, devname, srcdev, srcdev, roothash);
|
||||
util::exec_cmdline(VERITYSETUP, args)
|
||||
.context("Failed to set up verity device")?;
|
||||
|
||||
Ok(PathBuf::from(format!("/dev/mapper/{}", devname)))
|
||||
}
|
||||
|
||||
fn create_image_loop_device(file: &Path) -> Result<String> {
|
||||
let args = format!("--offset 4096 -f --show {}", file.display());
|
||||
let output = util::exec_cmdline_with_output(LOSETUP, args)?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// The output from the `veritysetup format` command can be parsed as key/value
|
||||
/// pairs. This class parses the output and stores it in a map for querying.
|
||||
pub struct VerityOutput {
|
||||
output: String,
|
||||
map: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl VerityOutput {
|
||||
/// Parse the string `output` as standard output from the dm-verity
|
||||
/// `veritysetup format` command.
|
||||
fn parse(output: &str) -> VerityOutput {
|
||||
let mut vo = VerityOutput {
|
||||
output: output.to_owned(),
|
||||
map: HashMap::new(),
|
||||
};
|
||||
for line in output.lines() {
|
||||
vo.parse_line(line);
|
||||
}
|
||||
vo
|
||||
}
|
||||
|
||||
fn parse_line(&mut self, line: &str) {
|
||||
let v = line.split(':').map(|s| s.trim()).collect::<Vec<_>>();
|
||||
|
||||
if v.len() == 2 {
|
||||
self.map.insert(v[0].to_owned(), v[1].to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_hash(&self) -> Option<&str> {
|
||||
self.map.get("Root hash").map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn salt(&self) -> Option<&str> {
|
||||
self.map.get("Salt").map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn output(&self) -> &str {
|
||||
&self.output
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user