forked from brl/citadel-tools
migrate other citadel-tools to external repository
This commit is contained in:
parent
18ba0fc4a3
commit
109f007e33
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
/target
|
||||
**/target
|
||||
**/*.rs.bk
|
||||
|
479
citadel-desktopd/Cargo.lock
generated
Normal file
479
citadel-desktopd/Cargo.lock
generated
Normal file
@ -0,0 +1,479 @@
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "citadel-desktopd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"inotify 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termcolor 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"inotify-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synom"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"wincolor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
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.4"
|
||||
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-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"
|
||||
|
||||
[[package]]
|
||||
name = "wincolor"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
||||
"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859"
|
||||
"checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2"
|
||||
"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
|
||||
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
|
||||
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
|
||||
"checksum bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7db437d718977f6dc9b2e3fd6fc343c02ac6b899b73fdd2179163447bd9ce9"
|
||||
"checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0"
|
||||
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
|
||||
"checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9"
|
||||
"checksum env_logger 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f15f0b172cb4f52ed5dbf47f774a387cd2315d1bf7894ab5af9b083ae27efa5a"
|
||||
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
|
||||
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
|
||||
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
|
||||
"checksum inotify 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41aaf46578a4628ff6c17c30993aed7e5188fae0817c78c558d3b7baaba1ffe5"
|
||||
"checksum inotify-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7dceb94c43f70baf4c4cd6afbc1e9037d4161dbe68df8a2cd4351a23319ee4fb"
|
||||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
||||
"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121"
|
||||
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
|
||||
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
||||
"checksum nix 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fd5681d13fda646462cfbd4e5f2051279a89a544d50eb98c365b507246839f"
|
||||
"checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca"
|
||||
"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe"
|
||||
"checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593"
|
||||
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
|
||||
"checksum num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7de20f146db9d920c45ee8ed8f71681fd9ade71909b48c3acbd766aa504cf10"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
|
||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||
"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa"
|
||||
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
|
||||
"checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e"
|
||||
"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526"
|
||||
"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0"
|
||||
"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5"
|
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
||||
"checksum termcolor 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9065bced9c3e43453aa3d56f1e98590b8455b341d2fa191a1090c0dd0b242c75"
|
||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
||||
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
|
||||
"checksum toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7540f4ffc193e0d3c94121edb19b055670d369f77d5804db11ae053a45b6e7e"
|
||||
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
|
||||
"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"
|
||||
"checksum wincolor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0878187fa88838d2006c0a76f30d64797098426245b375383f60acb6aed8a203"
|
16
citadel-desktopd/Cargo.toml
Normal file
16
citadel-desktopd/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "citadel-desktopd"
|
||||
version = "0.1.0"
|
||||
authors = ["brl@subgraph.com"]
|
||||
homepage = "https://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1.1"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4.0"
|
||||
env_logger = "0.5.3"
|
||||
inotify = "0.5"
|
||||
toml = "0.4.5"
|
||||
serde_derive = "1.0.27"
|
||||
serde = "1.0.27"
|
||||
nix = "0.10.0"
|
6
citadel-desktopd/conf/citadel-desktopd.conf
Normal file
6
citadel-desktopd/conf/citadel-desktopd.conf
Normal file
@ -0,0 +1,6 @@
|
||||
[options]
|
||||
exec_prefix="/usr/bin/realms run --"
|
||||
target_directory="/home/citadel/.local/share/applications"
|
||||
|
||||
[[sources]]
|
||||
path="/run/realms/current.realm/rootfs/usr/share/applications"
|
5
citadel-desktopd/conf/citadel-desktopd.service
Normal file
5
citadel-desktopd/conf/citadel-desktopd.service
Normal file
@ -0,0 +1,5 @@
|
||||
[Unit]
|
||||
Description=Desktop Integration Manager
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/libexec/citadel-desktopd /usr/share/citadel/citadel-desktopd.conf
|
120
citadel-desktopd/src/config.rs
Normal file
120
citadel-desktopd/src/config.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use Result;
|
||||
use toml;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
exec_prefix: String,
|
||||
target_directory: PathBuf,
|
||||
source_paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
|
||||
impl Config {
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||
let toml = ConfigToml::from_path(path)?;
|
||||
let config = toml.to_config()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn exec_prefix(&self) -> &str {
|
||||
self.exec_prefix.as_ref()
|
||||
}
|
||||
|
||||
pub fn target_directory(&self) -> &Path {
|
||||
self.target_directory.as_ref()
|
||||
}
|
||||
|
||||
pub fn source_paths(&self) -> &Vec<PathBuf> {
|
||||
self.source_paths.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ConfigToml {
|
||||
options: Option<Options>,
|
||||
sources: Option<Vec<Source>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Options {
|
||||
exec_prefix: Option<String>,
|
||||
target_directory: Option<String>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn exec_prefix(&self) -> Result<&str> {
|
||||
match self.exec_prefix {
|
||||
Some(ref s) => Ok(s.as_str()),
|
||||
None => Err(format_err!("missing 'exec_prefix=' field")),
|
||||
}
|
||||
}
|
||||
|
||||
fn target_directory(&self) -> Result<&str> {
|
||||
match self.target_directory {
|
||||
Some(ref s) => Ok(s.as_str()),
|
||||
None => Err(format_err!("missing 'target_directory=' field")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Clone)]
|
||||
struct Source {
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
fn path(&self) -> Result<&str> {
|
||||
match self.path {
|
||||
Some(ref s) => Ok(s.as_str()),
|
||||
None => Err(format_err!("missing 'path=' field")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_as_string(path: &Path) -> Result<String> {
|
||||
let mut f = File::open(path)?;
|
||||
let mut buffer = String::new();
|
||||
f.read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
impl ConfigToml {
|
||||
fn from_path<P: AsRef<Path>>(path: P) -> Result<ConfigToml> {
|
||||
let s = load_as_string(path.as_ref())?;
|
||||
let config = toml::from_str::<ConfigToml>(&s)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn options(&self) -> Result<&Options> {
|
||||
self.options.as_ref()
|
||||
.ok_or(format_err!("missing '[options]' section"))
|
||||
}
|
||||
|
||||
fn sources(&self) -> Result<Vec<Source>> {
|
||||
match self.sources {
|
||||
Some(ref srcs) => Ok(srcs.clone()),
|
||||
None => Err(format_err!("missing '[[sources]]' section(s)")),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_config(&self) -> Result<Config> {
|
||||
let options = self.options()?;
|
||||
let exec_prefix = options.exec_prefix()?.to_string() + " ";
|
||||
let target_path = options.target_directory()?.to_string();
|
||||
let target_directory = PathBuf::from(target_path);
|
||||
|
||||
let mut source_paths = Vec::new();
|
||||
for src in self.sources()? {
|
||||
let path = src.path()?;
|
||||
source_paths.push(PathBuf::from(path));
|
||||
}
|
||||
Ok(Config{
|
||||
exec_prefix,
|
||||
target_directory,
|
||||
source_paths,
|
||||
})
|
||||
}
|
||||
}
|
188
citadel-desktopd/src/desktop.rs
Normal file
188
citadel-desktopd/src/desktop.rs
Normal file
@ -0,0 +1,188 @@
|
||||
use std::io::Write;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
use Result;
|
||||
|
||||
|
||||
pub struct DesktopFile {
|
||||
filename: String,
|
||||
lines: Vec<Line>,
|
||||
// map from key of key/value pair to index of line in lines vector
|
||||
main_map: HashMap<String, usize>,
|
||||
// map from group name to map of key/value -> index
|
||||
groups: HashMap<String,HashMap<String,usize>>,
|
||||
}
|
||||
|
||||
|
||||
impl DesktopFile {
|
||||
|
||||
pub fn write_to_dir<P: AsRef<Path>>(&self, directory: P) -> Result<()> {
|
||||
let mut path = directory.as_ref().to_path_buf();
|
||||
path.push(self.filename.as_str());
|
||||
let f = File::create(&path)?;
|
||||
self.write_to(f)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_to<W: Write>(&self, mut w: W) -> Result<()> {
|
||||
for line in &self.lines {
|
||||
line.write_to(&mut w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn filename(&self) -> &str {
|
||||
self.filename.as_ref()
|
||||
}
|
||||
|
||||
//
|
||||
// Conditions for translating a .desktop entry
|
||||
//
|
||||
// Type=Application // Mandatory 'Type' key must be Application
|
||||
// NotShowIn= // If 'NotShowIn' key is present, must not contain GNOME
|
||||
// OnlyShowIn= // If 'OnlyShowIn' key is present, must contain 'GNOME'
|
||||
// Terminal=false // If 'Terminal' key is present, must be false
|
||||
// Hidden=false // If 'Hidden' key is present, must be false
|
||||
//
|
||||
pub fn is_showable(&self) -> bool {
|
||||
self.is_application_type() && self.show_in_gnome() &&
|
||||
!(self.needs_terminal() || self.is_hidden())
|
||||
}
|
||||
|
||||
fn needs_terminal(&self) -> bool {
|
||||
self.key_exists_and_not_false("Terminal")
|
||||
}
|
||||
|
||||
fn is_application_type(&self) -> bool {
|
||||
if let Some(t) = self.get_key_val("Type") {
|
||||
return t == "Application"
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn show_in_gnome(&self) -> bool {
|
||||
if self.key_exists("NotShowIn") && self.key_value_contains("NotShowIn", "GNOME") {
|
||||
return false;
|
||||
}
|
||||
if self.key_exists("OnlyShowIn") && !self.key_value_contains("OnlyShowIn", "GNOME") {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn key_value_contains(&self, key: &str, s: &str) -> bool {
|
||||
match self.get_key_val(key) {
|
||||
Some(val) => val.contains(s),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn key_exists(&self, key: &str) -> bool {
|
||||
self.main_map.contains_key(key)
|
||||
}
|
||||
|
||||
fn is_hidden(&self) -> bool {
|
||||
self.key_exists_and_not_false("Hidden")
|
||||
}
|
||||
|
||||
fn key_exists_and_not_false(&self, key: &str) -> bool {
|
||||
if let Some(s) = self.get_key_val(key) {
|
||||
if s != "false" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_key_val(&self, key: &str) -> Option<&str> {
|
||||
if let Some(idx) = self.main_map.get(key) {
|
||||
match self.lines[*idx] {
|
||||
Line::KeyValue(_, ref v) => return Some(v),
|
||||
ref line => panic!("Key lookup on '{}' returned wrong line type: {:?}", key, line),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
pub fn new(filename: &str) -> DesktopFile {
|
||||
DesktopFile {
|
||||
filename: filename.to_string(),
|
||||
lines: Vec::new(),
|
||||
main_map: HashMap::new(),
|
||||
groups: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_line(&mut self, line: Line) {
|
||||
if line.is_key_value_type() {
|
||||
let idx = self.lines.len();
|
||||
self.main_map.insert(line.get_key_string(), idx);
|
||||
}
|
||||
self.lines.push(line);
|
||||
}
|
||||
|
||||
pub fn add_action_line(&mut self, action: &str, line: Line) {
|
||||
if line.is_key_value_type() {
|
||||
let idx = self.lines.len();
|
||||
let map = self.groups.entry(action.to_string()).or_insert(HashMap::new());
|
||||
map.insert(line.get_key_string(), idx);
|
||||
}
|
||||
self.lines.push(line);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Line {
|
||||
Empty,
|
||||
Comment(String),
|
||||
ExecLine(String),
|
||||
KeyValue(String,String),
|
||||
KeyLocaleValue(String,String,String),
|
||||
DesktopHeader,
|
||||
ActionHeader(String),
|
||||
GroupHeader(String)
|
||||
}
|
||||
|
||||
|
||||
impl Line {
|
||||
pub fn is_action_header(&self) -> bool {
|
||||
match *self {
|
||||
Line::ActionHeader(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_key_value_type(&self) -> bool {
|
||||
match *self {
|
||||
Line::KeyValue(..) | Line::KeyLocaleValue(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key_string(&self) -> String {
|
||||
match *self {
|
||||
Line::KeyValue(ref k, ..) => k.to_string(),
|
||||
Line::KeyLocaleValue(ref k, ref loc, ..) => format!("{}[{}]", k, loc),
|
||||
_ => panic!("get_key_string() called on Line item which is not a key/value type"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to<W: Write>(&self, mut w: W) -> Result<()> {
|
||||
match *self {
|
||||
Line::Empty => writeln!(w)?,
|
||||
Line::Comment(ref s) => writeln!(w, "#{}", s)?,
|
||||
Line::ExecLine(ref s) => writeln!(w, "Exec={}", s)?,
|
||||
Line::KeyValue(ref k, ref v) => writeln!(w, "{}={}", k, v)?,
|
||||
Line::KeyLocaleValue(ref k, ref loc, ref v) => writeln!(w, "{}[{}]={}", k, loc, v)?,
|
||||
Line::DesktopHeader => writeln!(w, "[Desktop Entry]")?,
|
||||
Line::ActionHeader(ref action) => writeln!(w, "[Desktop Action {}]", action)?,
|
||||
Line::GroupHeader(..) => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
120
citadel-desktopd/src/desktop_file_sync.rs
Normal file
120
citadel-desktopd/src/desktop_file_sync.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use std::sync::{Arc,Mutex};
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use monitor::{DirectoryMonitor,MonitorEventHandler};
|
||||
use parser::DesktopFileParser;
|
||||
use config::Config;
|
||||
use Result;
|
||||
|
||||
|
||||
pub struct DesktopFileSync {
|
||||
config: Config,
|
||||
monitor: DirectoryMonitor,
|
||||
}
|
||||
|
||||
impl DesktopFileSync {
|
||||
pub fn new(config: Config) -> DesktopFileSync {
|
||||
let handler = Arc::new(Mutex::new(SyncHandler::new(config.clone())));
|
||||
let monitor = DirectoryMonitor::new(handler.clone());
|
||||
DesktopFileSync {
|
||||
config, monitor
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_target_directory(&self) -> Result<()> {
|
||||
let entries = fs::read_dir(self.config.target_directory())?;
|
||||
|
||||
for entry in entries {
|
||||
let path = entry?.path();
|
||||
if is_desktop_file(&path) {
|
||||
fs::remove_file(&path).context(format!("remove_file({:?})", path))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sync_source(&mut self, src: &Path) -> Result<()> {
|
||||
self.clear_target_directory()?;
|
||||
self.sync_source_directory(src)?;
|
||||
self.monitor.set_monitor_sources(&[src.to_path_buf()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync_source_directory(&self, src: &Path) -> Result<()> {
|
||||
let entries = fs::read_dir(src)?;
|
||||
for entry in entries {
|
||||
let path = entry?.path();
|
||||
if is_desktop_file(path.as_path()) {
|
||||
if let Err(e) = sync_desktop_file(path.as_path(), &self.config) {
|
||||
info!("error syncing desktop file {:?}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct SyncHandler {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl SyncHandler {
|
||||
fn new(config: Config) -> SyncHandler {
|
||||
SyncHandler { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorEventHandler for SyncHandler {
|
||||
fn file_added(&self, path: &Path) -> Result<()> {
|
||||
info!("file_added: {:?}", path);
|
||||
if is_desktop_file(path) {
|
||||
sync_desktop_file(path, &self.config)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn file_removed(&self, path: &Path) -> Result<()> {
|
||||
info!("file_removed: {:?}", path);
|
||||
let filename = filename_from_path(path)?;
|
||||
let target_path = self.config.target_directory().join(filename);
|
||||
if target_path.exists() {
|
||||
fs::remove_file(target_path.as_path())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_desktop_file(source: &Path, config: &Config) -> Result<()> {
|
||||
if !is_desktop_file(source) {
|
||||
return Err(format_err!("source path [{:?}] is not desktop file", source));
|
||||
}
|
||||
let df = DesktopFileParser::parse_from_path(source, config.exec_prefix())?;
|
||||
if df.is_showable() {
|
||||
df.write_to_dir(config.target_directory())?;
|
||||
} else {
|
||||
info!("ignoring {} as not showable", df.filename());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_desktop_file(path: &Path) -> bool {
|
||||
if let Some(ext) = path.extension() {
|
||||
return ext == "desktop"
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn filename_from_path(path: &Path) -> Result<&str> {
|
||||
let filename = match path.file_name() {
|
||||
Some(name) => name,
|
||||
None => return Err(format_err!("Path {:?} has no filename component", path)),
|
||||
};
|
||||
match filename.to_str() {
|
||||
Some(s) => Ok(s),
|
||||
None => Err(format_err!("Filename has invalid utf8 encoding")),
|
||||
}
|
||||
}
|
||||
|
57
citadel-desktopd/src/main.rs
Normal file
57
citadel-desktopd/src/main.rs
Normal file
@ -0,0 +1,57 @@
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
extern crate env_logger;
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
extern crate inotify;
|
||||
extern crate nix;
|
||||
|
||||
mod desktop;
|
||||
mod parser;
|
||||
mod config;
|
||||
mod monitor;
|
||||
mod desktop_file_sync;
|
||||
|
||||
use std::result;
|
||||
use std::process;
|
||||
use failure::Error;
|
||||
use desktop_file_sync::DesktopFileSync;
|
||||
|
||||
use config::Config;
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
|
||||
fn main() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
env_logger::init();
|
||||
|
||||
let mut args = std::env::args();
|
||||
args.next();
|
||||
if args.len() != 1 {
|
||||
println!("expected config file argument");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let config_path = args.next().unwrap();
|
||||
let config = match Config::from_path(&config_path) {
|
||||
Err(e) => {
|
||||
warn!("Failed to load configuration file: {}", e);
|
||||
process::exit(1);
|
||||
},
|
||||
Ok(config) => config,
|
||||
};
|
||||
|
||||
let src = config.source_paths().first().unwrap().clone();
|
||||
|
||||
let mut dfs = DesktopFileSync::new(config.clone());
|
||||
if let Err(e) = dfs.sync_source(src.as_path()) {
|
||||
warn!("error calling sync_source: {}", e);
|
||||
}
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::new(120, 0));
|
||||
}
|
||||
|
||||
}
|
210
citadel-desktopd/src/monitor.rs
Normal file
210
citadel-desktopd/src/monitor.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::sync::{Arc,Mutex,Once,ONCE_INIT};
|
||||
use std::sync::atomic::{AtomicBool,Ordering};
|
||||
use std::thread::{self,JoinHandle};
|
||||
use std::os::unix::thread::JoinHandleExt;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use nix::libc;
|
||||
use nix::sys::signal;
|
||||
|
||||
use inotify::{Events,Inotify,EventMask,WatchMask,Event,WatchDescriptor};
|
||||
|
||||
use Result;
|
||||
|
||||
pub trait MonitorEventHandler: Send+Sync {
|
||||
fn file_added(&self, path: &Path) -> Result<()> { let _ = path; Ok(()) }
|
||||
fn file_removed(&self, path: &Path) -> Result<()> { let _ = path; Ok(()) }
|
||||
fn directory_added(&self, path: &Path) -> Result<()> { let _ = path; Ok(()) }
|
||||
fn directory_removed(&self, path: &Path) -> Result<()> { let _ = path; Ok(()) }
|
||||
}
|
||||
|
||||
pub struct DirectoryMonitor {
|
||||
event_handler: Arc<Mutex<MonitorEventHandler>>,
|
||||
worker_handle: Option<WorkerHandle>,
|
||||
}
|
||||
|
||||
impl DirectoryMonitor {
|
||||
pub fn new(handler: Arc<Mutex<MonitorEventHandler>>) -> DirectoryMonitor {
|
||||
initialize();
|
||||
DirectoryMonitor {
|
||||
event_handler: handler,
|
||||
worker_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_monitor_sources(&mut self, sources: &[PathBuf]) {
|
||||
if let Some(handle) = self.worker_handle.take() {
|
||||
handle.stop();
|
||||
handle.wait();
|
||||
}
|
||||
let sources = Vec::from(sources);
|
||||
let h = MonitorWorker::start_worker(sources, self.event_handler.clone());
|
||||
self.worker_handle = Some(h);
|
||||
}
|
||||
}
|
||||
|
||||
struct MonitorWorker {
|
||||
descriptors: HashMap<WatchDescriptor,PathBuf>,
|
||||
inotify: Inotify,
|
||||
exit_flag: Arc<AtomicBool>,
|
||||
watch_paths: Vec<PathBuf>,
|
||||
handler: Arc<Mutex<MonitorEventHandler>>,
|
||||
}
|
||||
|
||||
|
||||
impl MonitorWorker {
|
||||
fn start_worker(watch_paths: Vec<PathBuf>, handler: Arc<Mutex<MonitorEventHandler>>) -> WorkerHandle {
|
||||
let exit_flag = Arc::new(AtomicBool::new(false));
|
||||
let flag_clone = exit_flag.clone();
|
||||
let jhandle = thread::spawn(move || {
|
||||
|
||||
let mut worker = match MonitorWorker::new(watch_paths, flag_clone, handler) {
|
||||
Ok(worker) => worker,
|
||||
Err(e) => {
|
||||
info!("failed to initialize inotify handle: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Err(e) = worker.run() {
|
||||
info!("error returned from worker thread: {}", e);
|
||||
}
|
||||
});
|
||||
WorkerHandle::new(jhandle, exit_flag)
|
||||
}
|
||||
|
||||
fn new(watch_paths: Vec<PathBuf>, exit_flag: Arc<AtomicBool>, handler: Arc<Mutex<MonitorEventHandler>>) -> Result<MonitorWorker> {
|
||||
Ok(MonitorWorker {
|
||||
descriptors: HashMap::new(),
|
||||
inotify: Inotify::init()?,
|
||||
exit_flag,
|
||||
watch_paths,
|
||||
handler,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_watches(&mut self) -> Result<()> {
|
||||
let watch_flags = WatchMask::CREATE | WatchMask::DELETE | WatchMask::MOVED_TO |
|
||||
WatchMask::DONT_FOLLOW | WatchMask::ONLYDIR;
|
||||
for p in &self.watch_paths {
|
||||
let wd = self.inotify.add_watch(p, watch_flags)?;
|
||||
self.descriptors.insert(wd, p.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_events<'a>(&mut self, buffer: &'a mut [u8]) -> Result<Option<Events<'a>>> {
|
||||
if self.exit_flag.load(Ordering::Relaxed) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match self.inotify.read_events_blocking(buffer) {
|
||||
Ok(events) => Ok(Some(events)),
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::Interrupted {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_events(&self, events: Events) {
|
||||
for ev in events {
|
||||
if let Err(e) = self.handle_event(&ev) {
|
||||
info!("error handling inotify event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<()> {
|
||||
info!("running monitor event loop");
|
||||
self.add_watches()?;
|
||||
let mut buffer = [0u8; 4096];
|
||||
loop {
|
||||
match self.read_events(&mut buffer)? {
|
||||
Some(events) => self.process_events(events),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn full_event_path(&self, ev: &Event) -> Result<PathBuf> {
|
||||
let filename = ev.name
|
||||
.ok_or(format_err!("inotify event received without a filename"))?;
|
||||
let path = self.descriptors.get(&ev.wd)
|
||||
.ok_or(format_err!("Failed to find descriptor for received inotify event"))?;
|
||||
Ok(path.join(filename))
|
||||
}
|
||||
|
||||
fn handle_event(&self, ev: &Event) -> Result<()> {
|
||||
let handler = self.handler.lock().unwrap();
|
||||
let pb = self.full_event_path(ev)?;
|
||||
let path = pb.as_path();
|
||||
let is_create = ev.mask.intersects(EventMask::CREATE|EventMask::MOVED_TO);
|
||||
if !is_create && !ev.mask.contains(EventMask::DELETE) {
|
||||
return Err(format_err!("Unexpected mask value for inotify event: {:?}", ev.mask));
|
||||
}
|
||||
|
||||
if ev.mask.contains(EventMask::ISDIR) {
|
||||
if is_create {
|
||||
handler.directory_added(path)?;
|
||||
} else {
|
||||
handler.directory_removed(path)?;
|
||||
}
|
||||
|
||||
} else {
|
||||
if is_create {
|
||||
handler.file_added(path)?;
|
||||
} else {
|
||||
handler.file_removed(path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorkerHandle {
|
||||
join_handle: JoinHandle<()>,
|
||||
exit_flag: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl WorkerHandle {
|
||||
fn new(join_handle: JoinHandle<()>, exit_flag: Arc<AtomicBool>) -> WorkerHandle {
|
||||
WorkerHandle { join_handle, exit_flag }
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
info!("calling stop on monitor");
|
||||
let tid = self.join_handle.as_pthread_t();
|
||||
self.exit_flag.store(true, Ordering::Relaxed);
|
||||
unsafe {
|
||||
libc::pthread_kill(tid, signal::SIGUSR1 as libc::c_int);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait(self) {
|
||||
if let Err(e) = self.join_handle.join() {
|
||||
warn!("monitor thread panic with '{:?}'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static INITIALIZE_ONCE: Once = ONCE_INIT;
|
||||
|
||||
fn initialize() {
|
||||
INITIALIZE_ONCE.call_once(|| {
|
||||
let h = signal::SigHandler::Handler(sighandler);
|
||||
let sa = signal::SigAction::new(h, signal::SaFlags::empty(), signal::SigSet::empty());
|
||||
if let Err(e) = unsafe { signal::sigaction(signal::SIGUSR1, &sa) } {
|
||||
warn!("Error setting signal handler: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extern fn sighandler(_: libc::c_int) {
|
||||
// do nothing, signal is only used to EINTR blocking inotify call
|
||||
}
|
307
citadel-desktopd/src/parser.rs
Normal file
307
citadel-desktopd/src/parser.rs
Normal file
@ -0,0 +1,307 @@
|
||||
|
||||
use std::io::Read;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::collections::HashSet;
|
||||
use desktop::{DesktopFile,Line};
|
||||
use Result;
|
||||
|
||||
lazy_static! {
|
||||
// These are the keys which are copied into the translated .desktop files
|
||||
static ref KEY_WHITELIST: HashSet<&'static str> = [
|
||||
"Type", "Version", "Name", "GenericName", "NoDisplay", "Comment", "Icon", "Hidden",
|
||||
"OnlyShowIn", "NotShowIn", "Path", "Terminal", "Actions", "MimeType",
|
||||
"Categories", "Keywords", "StartupNotify", "StartupWMClass", "URL", "DocPath",
|
||||
"X-GNOME-FullName", "X-GNOME-Provides", "X-Desktop-File-Install-Version", "X-GNOME-UsesNotifications",
|
||||
"X-GNOME-DocPath", "X-Geoclue-Reason", "X-GNOME-SingleWindow", "X-GNOME-Gettext-Domain",
|
||||
"X-MultipleArgs",
|
||||
].iter().cloned().collect();
|
||||
|
||||
// These are keys which are recognized but deliberately ignored.
|
||||
static ref KEY_IGNORELIST: HashSet<&'static str> = [
|
||||
"DBusActivatable", "Implements", "TryExec", "InitialPreference", "Encoding", "X-KDE-Protocols", "X-GIO-NoFuse", "X-Gnome-Vfs-System",
|
||||
"X-GNOME-Autostart-Phase", "X-GNOME-Autostart-Notify", "X-GNOME-AutoRestart",
|
||||
"X-GNOME-Bugzilla-Bugzilla", "X-GNOME-Bugzilla-Product", "X-GNOME-Bugzilla-Component", "X-GNOME-Bugzilla-Version",
|
||||
"X-GNOME-Bugzilla-ExtraInfoScript", "X-GNOME-Bugzilla-OtherBinaries", "X-GNOME-Autostart-enabled",
|
||||
"X-AppInstall-Package", "X-KDE-SubstituteUID", "X-Ubuntu-Gettext-Domain", "X-AppInstall-Keywords",
|
||||
"X-Ayatana-Desktop-Shortcuts", "X-GNOME-Settings-Panel", "X-GNOME-WMSettingsModule", "X-GNOME-WMName",
|
||||
"X-GnomeWMSettingsLibrary",
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
fn is_whitelisted_key(key: &str) -> bool {
|
||||
KEY_WHITELIST.contains(key)
|
||||
}
|
||||
|
||||
fn filename_from_path(path: &Path) -> Result<&str> {
|
||||
let filename = match path.file_name() {
|
||||
Some(name) => name,
|
||||
None => return Err(format_err!("Path {:?} has no filename component", path)),
|
||||
};
|
||||
match filename.to_str() {
|
||||
Some(s) => Ok(s),
|
||||
None => Err(format_err!("Filename has invalid utf8 encoding")),
|
||||
}
|
||||
}
|
||||
pub struct DesktopFileParser {
|
||||
desktop_file: DesktopFile,
|
||||
exec_prefix: String,
|
||||
seen_header: bool,
|
||||
current_action: Option<String>,
|
||||
in_ignored_group: bool,
|
||||
known_actions: HashSet<String>,
|
||||
}
|
||||
|
||||
|
||||
impl DesktopFileParser {
|
||||
fn new(filename: &str, exec_prefix: &str) -> DesktopFileParser {
|
||||
DesktopFileParser {
|
||||
desktop_file: DesktopFile::new(filename),
|
||||
exec_prefix: exec_prefix.to_string(),
|
||||
seen_header: false,
|
||||
current_action: None,
|
||||
in_ignored_group: false,
|
||||
known_actions: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_from_path<P: AsRef<Path>>(path: P, exec_prefix: &str) -> Result<DesktopFile> {
|
||||
let filename = filename_from_path(path.as_ref())?;
|
||||
let f = File::open(path.as_ref())?;
|
||||
DesktopFileParser::parse_from_reader(f, filename, exec_prefix)
|
||||
}
|
||||
|
||||
fn parse_from_reader<T: Read>(mut r: T, filename: &str, exec_prefix: &str) -> Result<DesktopFile> {
|
||||
let mut buffer = String::new();
|
||||
r.read_to_string(&mut buffer)?;
|
||||
DesktopFileParser::parse_from_string(&buffer, filename, exec_prefix)
|
||||
}
|
||||
|
||||
fn parse_from_string(body: &str, filename: &str, exec_prefix: &str) -> Result<DesktopFile> {
|
||||
let mut parser = DesktopFileParser::new(filename, exec_prefix);
|
||||
for s in body.lines() {
|
||||
match LineParser::parse(s) {
|
||||
Some(line) => parser.process_line(line)?,
|
||||
None => return Err(format_err!("Failed to parse line: '{}'", s))
|
||||
}
|
||||
}
|
||||
Ok(parser.desktop_file)
|
||||
}
|
||||
|
||||
fn process_initial(&mut self, line: Line) -> Result<()> {
|
||||
match line {
|
||||
Line::Comment(_) | Line::Empty => {},
|
||||
Line::DesktopHeader => self.seen_header = true,
|
||||
_ => return Err(format_err!("Missing Desktop Entry header"))
|
||||
}
|
||||
self.desktop_file.add_line(line);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_line(&mut self, mut line: Line) -> Result<()> {
|
||||
if self.in_ignored_group && !line.is_action_header() {
|
||||
return Ok(())
|
||||
}
|
||||
if !self.seen_header {
|
||||
return self.process_initial(line)
|
||||
}
|
||||
|
||||
if let Line::KeyValue(ref k, ref value) = line {
|
||||
if k == "Actions" {
|
||||
for s in value.split_terminator(";") {
|
||||
self.known_actions.insert(s.trim().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
match line {
|
||||
Line::ExecLine(ref mut s) => {
|
||||
s.insert_str(0,self.exec_prefix.as_str())
|
||||
},
|
||||
Line::DesktopHeader => return Err(format_err!("Duplicate Desktop Entry header")),
|
||||
Line::ActionHeader(ref action) => {
|
||||
if self.known_actions.contains(action) {
|
||||
self.current_action = Some(action.to_string());
|
||||
self.in_ignored_group = false;
|
||||
} else {
|
||||
return Err(format_err!("Desktop Action header with undecleared action: {}", action))
|
||||
}
|
||||
},
|
||||
Line::GroupHeader(_) => {
|
||||
self.in_ignored_group = true;
|
||||
return Ok(())
|
||||
},
|
||||
Line::KeyLocaleValue(ref k,_,_) | Line::KeyValue(ref k,_) => {
|
||||
|
||||
if !is_whitelisted_key(k) {
|
||||
if !KEY_IGNORELIST.contains(k.as_str()) {
|
||||
info!("Unknown key in {}: {}", self.desktop_file.filename(), k);
|
||||
}
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
if let Some(ref action) = self.current_action {
|
||||
self.desktop_file.add_action_line(action, line)
|
||||
} else {
|
||||
self.desktop_file.add_line(line);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const DESKTOP_ACTION: &'static str = "Desktop Action ";
|
||||
|
||||
struct LineParser<'a> {
|
||||
s: &'a str,
|
||||
}
|
||||
|
||||
impl <'a> LineParser<'a> {
|
||||
fn new(s: &'a str) -> LineParser<'a> {
|
||||
LineParser {
|
||||
s,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(s: &'a str) -> Option<Line> {
|
||||
if let Some(line) = LineParser::new(s)._parse() {
|
||||
if validate_line(&line) {
|
||||
return Some(line)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn first(&self) -> Option<char> {
|
||||
self.s.chars().next()
|
||||
}
|
||||
|
||||
fn last(&self) -> Option<char> {
|
||||
self.s.chars().next_back()
|
||||
}
|
||||
|
||||
fn _parse(&mut self) -> Option<Line> {
|
||||
match self.first() {
|
||||
None => Some(Line::Empty),
|
||||
Some('#') => Some(Line::Comment(self.s[1..].to_string())),
|
||||
Some('[') => self.parse_header(),
|
||||
Some(_) => self.parse_keyval(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_header(&mut self) -> Option<Line> {
|
||||
if self.last().unwrap() != ']' {
|
||||
return None
|
||||
}
|
||||
let content = &self.s[1..self.s.len() - 1];
|
||||
if content.starts_with(DESKTOP_ACTION) {
|
||||
let action = &content[DESKTOP_ACTION.len()..];
|
||||
return Some(Line::ActionHeader(action.to_string()))
|
||||
} else if content == "Desktop Entry" {
|
||||
return Some(Line::DesktopHeader)
|
||||
}
|
||||
return Some(Line::GroupHeader(content.to_string()))
|
||||
}
|
||||
|
||||
fn parse_keyval(&self) -> Option<Line> {
|
||||
let parts: Vec<&str> = self.s.splitn(2, "=").collect();
|
||||
if parts.len() != 2 {
|
||||
return None
|
||||
}
|
||||
let key = parts[0].trim();
|
||||
let val = parts[1].trim();
|
||||
if !key.contains("[") {
|
||||
if key == "Exec" {
|
||||
return Some(Line::ExecLine(val.to_string()))
|
||||
}
|
||||
return Some(Line::KeyValue(key.to_string(), val.to_string()))
|
||||
}
|
||||
self.parse_locale(key).map(|(key,locale)| Line::KeyLocaleValue(key, locale, val.to_string()))
|
||||
}
|
||||
|
||||
fn parse_locale(&self, key: &str) -> Option<(String,String)> {
|
||||
let idx = key.find("[").unwrap();
|
||||
let (k,loc) = key.split_at(idx);
|
||||
let mut chars = loc.chars();
|
||||
if let Some(']') = chars.next_back() {
|
||||
chars.next();
|
||||
if k.trim() == "Exec" {
|
||||
// Exec key with locale not allowed
|
||||
return None;
|
||||
}
|
||||
return Some((k.trim().to_string(), chars.as_str().to_string()))
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_alphanum_or_dash(c: char) -> bool {
|
||||
is_ascii(c) && (c.is_alphanumeric() || c == '-')
|
||||
}
|
||||
|
||||
fn is_ascii(c: char) -> bool {
|
||||
c as u32 <= 0x7F
|
||||
}
|
||||
|
||||
fn is_first_char_alphabetic(s: &str) -> bool {
|
||||
if let Some(c) = s.chars().next() {
|
||||
return is_ascii(c) && c.is_alphabetic()
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_valid_key(key: &str) -> bool {
|
||||
if !is_first_char_alphabetic(key) {
|
||||
return false
|
||||
}
|
||||
key.chars().all(is_alphanum_or_dash)
|
||||
}
|
||||
|
||||
fn is_valid_locale(locale: &str) -> bool {
|
||||
!locale.is_empty() && locale.chars().all(|c| {
|
||||
is_alphanum_or_dash(c) || c == '_' || c == '.' || c == '@'
|
||||
})
|
||||
}
|
||||
|
||||
fn is_valid_value(value: &str) -> bool {
|
||||
value.chars().all(|c| {
|
||||
!(c.is_control() || c as u32 == 0 )
|
||||
})
|
||||
}
|
||||
|
||||
fn is_valid_action(action: &str) -> bool {
|
||||
is_first_char_alphabetic(action) && action.chars().all(is_alphanum_or_dash)
|
||||
}
|
||||
|
||||
fn is_valid_group(group: &str) -> bool {
|
||||
is_first_char_alphabetic(group) && group.chars().all(|c| {
|
||||
is_ascii(c) && !c.is_control()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_valid_exec(val: &str) -> bool {
|
||||
val.chars().all(|c| {
|
||||
is_ascii(c) && !(c.is_control() || c as u32 == 0)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_line(line: &Line) -> bool {
|
||||
match *line {
|
||||
Line::ExecLine(ref s) => is_valid_exec(s),
|
||||
Line::KeyValue(ref k, ref v) => is_valid_key(k) && is_valid_value(v),
|
||||
Line::KeyLocaleValue(ref k, ref l, ref v) => is_valid_key(k) && is_valid_locale(l) && is_valid_value(v),
|
||||
Line::ActionHeader(ref action) => is_valid_action(action),
|
||||
Line::GroupHeader(ref group) => is_valid_group(group),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
let tests = vec!["###", "", "# hello", "[Desktop Entry]", "[Desktop Action foo]", "Foo=Bar", "Foo[hehe]=Lol"];
|
||||
for t in tests {
|
||||
println!("{:?}", LineParser::parse(t));
|
||||
}
|
||||
}
|
305
citadel-mkimage/Cargo.lock
generated
305
citadel-mkimage/Cargo.lock
generated
@ -6,6 +6,11 @@ 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"
|
||||
@ -42,6 +47,25 @@ 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"
|
||||
@ -58,6 +82,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libcitadel 0.1.0",
|
||||
"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)",
|
||||
"toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -77,6 +102,56 @@ 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"
|
||||
@ -97,11 +172,72 @@ 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"
|
||||
source = "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 = "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)",
|
||||
"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)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.25 (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)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.24"
|
||||
@ -118,6 +254,91 @@ 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"
|
||||
@ -136,6 +357,32 @@ name = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
@ -151,11 +398,27 @@ 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"
|
||||
@ -203,6 +466,11 @@ 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"
|
||||
@ -218,6 +486,11 @@ name = "vec_map"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.6"
|
||||
@ -239,32 +512,64 @@ 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 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 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"
|
||||
"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"
|
||||
|
@ -5,6 +5,7 @@ authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
homepage = "https://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
libcitadel = { path = "../libcitadel" }
|
||||
clap = "2.32.0"
|
||||
failure = "0.1.3"
|
||||
serde_derive = "1.0.82"
|
||||
|
@ -5,8 +5,8 @@ use std::fs::{self,File};
|
||||
use std::io::{self,Write};
|
||||
|
||||
use failure::ResultExt;
|
||||
use libcitadel::Result;
|
||||
|
||||
use Result;
|
||||
use BuildConfig;
|
||||
use util;
|
||||
|
||||
|
@ -3,9 +3,11 @@ use std::path::{Path,PathBuf};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use Result;
|
||||
use toml;
|
||||
|
||||
use libcitadel::Result;
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BuildConfig {
|
||||
#[serde (rename="image-type")]
|
||||
|
@ -1,53 +1,19 @@
|
||||
#[macro_use] extern crate libcitadel;
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($e:expr) => { if $crate::verbose() { println!("[+] {}", $e);} };
|
||||
($fmt:expr, $($arg:tt)+) => { if $crate::verbose() { println!("[+] {}", format!($fmt, $($arg)+));} };
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($e:expr) => { println!("WARNING: {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("WARNING: {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! notify {
|
||||
($e:expr) => { println!("[+] {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("[+] {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static VERBOSE: RefCell<bool> = RefCell::new(false);
|
||||
}
|
||||
|
||||
pub fn verbose() -> bool {
|
||||
VERBOSE.with(|f| {
|
||||
*f.borrow()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_verbose(val: bool) {
|
||||
VERBOSE.with(|f| {
|
||||
*f.borrow_mut() = val
|
||||
});
|
||||
}
|
||||
|
||||
extern crate clap;
|
||||
extern crate toml;
|
||||
|
||||
use std::process::exit;
|
||||
use std::result;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use clap::{App,Arg,SubCommand,ArgMatches};
|
||||
use clap::AppSettings::*;
|
||||
use failure::Error;
|
||||
|
||||
pub type Result<T> = result::Result<T,Error>;
|
||||
use build::UpdateBuilder;
|
||||
use config::BuildConfig;
|
||||
use libcitadel::{Result,set_verbose};
|
||||
|
||||
mod build;
|
||||
mod config;
|
||||
@ -91,40 +57,6 @@ fn format_error(err: &Error) -> String {
|
||||
output
|
||||
}
|
||||
|
||||
/*
|
||||
fn find_source_image(update_type: &str, filename: &str) -> Result<PathBuf> {
|
||||
let mut path = PathBuf::from(filename);
|
||||
let meta = path.metadata()
|
||||
.context(format!("could not find source file or directory {}", filename))?;
|
||||
|
||||
if meta.file_type().is_file() {
|
||||
return Ok(path);
|
||||
}
|
||||
if !meta.file_type().is_dir() {
|
||||
bail!("source not found: {}", filename);
|
||||
}
|
||||
|
||||
path.push(format!("build/images/citadel-{}-image-intel-corei7-64.ext2", update_type));
|
||||
let meta = path.metadata()
|
||||
.context(format!("could not find source file {}", path.display()))?;
|
||||
|
||||
if !meta.file_type().is_file() {
|
||||
bail!("source {} exists but is not a file", path.display());
|
||||
}
|
||||
|
||||
let canonical = fs::canonicalize(&path)
|
||||
.context(format!("failed to resolve {} to absolute path", path.display()))?;
|
||||
Ok(canonical)
|
||||
}
|
||||
|
||||
fn parse_version(arg_matches: &ArgMatches) -> Result<usize> {
|
||||
let v = arg_matches.value_of("version")
|
||||
.unwrap_or("0")
|
||||
.parse::<usize>()
|
||||
.context("Unable to parse version argument")?;
|
||||
Ok(v)
|
||||
}
|
||||
*/
|
||||
|
||||
fn build_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let build_file = arg_matches.value_of("build-file").unwrap();
|
||||
@ -135,48 +67,3 @@ fn build_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
fn build_update(update_type: &str, arg_matches: &ArgMatches) -> Result<()> {
|
||||
let config = Config::load("/usr/share/citadel/citadel-image.conf")?;
|
||||
let channel = config.get_default_channel().unwrap(); // XXX unwrap()
|
||||
|
||||
let source_name = arg_matches.value_of("citadel-base").unwrap();
|
||||
let source = find_source_image(update_type, source_name)?;
|
||||
let version = parse_version(arg_matches)?;
|
||||
|
||||
info!("Building file {} as {} update image for channel {} with version {}", source.display(), update_type, channel.name(), version);
|
||||
let conf = BuildConfig::load("").unwrap();
|
||||
let mut builder = UpdateBuilder::new(conf)?;
|
||||
builder.build()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rootfs_update(arg_matches: &ArgMatches) -> Result<()> {
|
||||
build_update("rootfs", arg_matches)
|
||||
}
|
||||
|
||||
fn modules_update(arg_matches: &ArgMatches) -> Result<()> {
|
||||
build_update("modules", arg_matches)
|
||||
}
|
||||
|
||||
fn extra_update(arg_matches: &ArgMatches) -> Result<()> {
|
||||
build_update("extra", arg_matches)
|
||||
}
|
||||
|
||||
fn generate_verity(_arg_matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_keys() -> Result<()> {
|
||||
let keys = SigningKeys::generate()?;
|
||||
println!("pubkey = \"{}\"", keys.to_public_hex());
|
||||
println!("keypair = \"{}\"", keys.to_hex());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn image_info(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let image = arg_matches.value_of("image-file").unwrap();
|
||||
let config = Config::load("/usr/share/citadel/citadel-image.conf")?;
|
||||
info::show_info(image, &config)
|
||||
}
|
||||
*/
|
||||
|
@ -1,11 +1,9 @@
|
||||
|
||||
use std::process::{Command,Stdio};
|
||||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use Result;
|
||||
use libcitadel::Result;
|
||||
|
||||
|
||||
pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
|
484
citadel-mount/Cargo.lock
generated
Normal file
484
citadel-mount/Cargo.lock
generated
Normal file
@ -0,0 +1,484 @@
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.12"
|
||||
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)",
|
||||
"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)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
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-mount"
|
||||
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)",
|
||||
"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)",
|
||||
"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.22 (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"
|
||||
source = "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 = "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)",
|
||||
"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)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.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)",
|
||||
"void 1.0.2 (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 = "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 = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.82"
|
||||
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)",
|
||||
]
|
||||
|
||||
[[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"
|
||||
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.22 (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 = "toml"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
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-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 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 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 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 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 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"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
10
citadel-mount/Cargo.toml
Normal file
10
citadel-mount/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "citadel-mount"
|
||||
version = "0.1.0"
|
||||
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
homepage = "http://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
libcitadel = { path = "../libcitadel" }
|
||||
libc = "0.2"
|
||||
failure = "0.1.3"
|
142
citadel-mount/src/boot_select.rs
Normal file
142
citadel-mount/src/boot_select.rs
Normal file
@ -0,0 +1,142 @@
|
||||
|
||||
use libcitadel::{Config,Partition,Result,ImageHeader};
|
||||
|
||||
pub struct BootSelection {
|
||||
partitions: Vec<Partition>,
|
||||
}
|
||||
|
||||
impl BootSelection {
|
||||
pub fn choose_install_partition(config: &Config) -> Result<Partition> {
|
||||
let bs = BootSelection::load_partitions(config)?;
|
||||
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)?;
|
||||
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)
|
||||
.map_err(|e| format_err!("Could not load rootfs partition info: {}", e))?;
|
||||
|
||||
Ok(BootSelection {
|
||||
partitions
|
||||
})
|
||||
}
|
||||
|
||||
fn _choose_install_partition(&self) -> Option<&Partition> {
|
||||
self.choose(|p| {
|
||||
// first pass, if there is a partition which is not mounted and
|
||||
// not initialized use that one
|
||||
!p.is_mounted() && !p.is_initialized()
|
||||
}).or_else(|| self.choose(|p| {
|
||||
// second pass, just find one that's not mounted
|
||||
!p.is_mounted()
|
||||
}))
|
||||
}
|
||||
|
||||
fn choose<F>(&self, pred: F) -> Option<&Partition>
|
||||
where F: Sized + Fn(&&Partition) -> bool
|
||||
{
|
||||
self.partitions.iter().find(pred)
|
||||
}
|
||||
|
||||
/// Find the best rootfs partition to boot from
|
||||
fn _choose_boot_partition(&self) -> Option<&Partition> {
|
||||
let mut best: Option<&Partition> = None;
|
||||
|
||||
for p in &self.partitions {
|
||||
if is_better(&best, p) {
|
||||
best = Some(p);
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
|
||||
/// Perform checks for error states at boot time.
|
||||
pub fn scan_boot_partitions(&mut self, config: &Config) -> Result<()> {
|
||||
for mut p in &mut self.partitions {
|
||||
if let Err(e) = boot_scan_partition(&mut p, config) {
|
||||
warn!("error in bootscan of partition {}: {}", p.path().display(), e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Called at boot to perform various checks and possibly
|
||||
/// update the status field to an error state.
|
||||
///
|
||||
/// Mark `STATUS_TRY_BOOT` partition as `STATUS_FAILED`.
|
||||
///
|
||||
/// If metainfo cannot be parsed, mark as `STATUS_BAD_META`.
|
||||
///
|
||||
/// Verify metainfo signature and mark `STATUS_BAD_SIG` if
|
||||
/// signature verification fails.
|
||||
///
|
||||
fn boot_scan_partition(p: &mut Partition, config: &Config) -> Result<()> {
|
||||
if !p.is_initialized() {
|
||||
return Ok(())
|
||||
}
|
||||
if p.header().status() == ImageHeader::STATUS_TRY_BOOT {
|
||||
warn!("Partition {} has STATUS_TRY_BOOT, assuming it failed boot attempt and marking STATUS_FAILED", p.path().display());
|
||||
p.write_status(ImageHeader::STATUS_FAILED)?;
|
||||
}
|
||||
let signature = p.header().signature();
|
||||
p.metainfo().verify(config, &signature)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_better<'a>(current_best: &Option<&'a Partition>, other: &'a Partition) -> bool {
|
||||
|
||||
if !other.is_initialized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only consider partitions in state NEW or state GOOD
|
||||
if !other.is_good() && !other.is_new() {
|
||||
return false;
|
||||
}
|
||||
// If metainfo is broken, then no, it's not better
|
||||
//if !other.metainfo().is_ok() {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
let best = match *current_best {
|
||||
Some(p) => p,
|
||||
// No current 'best', so 'other' is better, whatever it is.
|
||||
None => return true,
|
||||
};
|
||||
|
||||
// First parition with PREFER flag trumps everything else
|
||||
if best.is_preferred() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let best_version = best.metainfo().version();
|
||||
let other_version = other.metainfo().version();
|
||||
|
||||
if best_version > other_version {
|
||||
return false;
|
||||
}
|
||||
|
||||
if other_version > best_version {
|
||||
return true;
|
||||
}
|
||||
|
||||
// choose NEW over GOOD if versions are the same
|
||||
if other.is_new() && best.is_good() {
|
||||
return true;
|
||||
}
|
||||
// ... but if all things otherwise match, return first match
|
||||
false
|
||||
}
|
82
citadel-mount/src/main.rs
Normal file
82
citadel-mount/src/main.rs
Normal file
@ -0,0 +1,82 @@
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate libcitadel;
|
||||
|
||||
extern crate libc;
|
||||
|
||||
use std::env;
|
||||
use std::process::exit;
|
||||
|
||||
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
|
||||
///
|
||||
/// citadel-mount rootfs
|
||||
/// citadel-mount modules
|
||||
/// citadel-mount extra
|
||||
///
|
||||
/// '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
|
||||
///
|
||||
///
|
||||
|
||||
fn main() {
|
||||
|
||||
|
||||
if CommandLine::verbose() {
|
||||
set_verbose(true);
|
||||
}
|
||||
|
||||
let config = match Config::load_default() {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
let mut args = env::args();
|
||||
args.next();
|
||||
let result = match args.next() {
|
||||
Some(ref s) if s == "rootfs" => mount_rootfs(config),
|
||||
Some(ref s) if s == "modules" => mount_modules(config),
|
||||
Some(ref s) if s == "extra" => mount_extra(config),
|
||||
_ => Err(format_err!("Bad or missing argument")),
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
warn!("Failed: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn mount_rootfs(config: Config) -> Result<()> {
|
||||
info!("citadel-mount rootfs");
|
||||
let rootfs = Rootfs::new(config);
|
||||
rootfs.setup()
|
||||
}
|
||||
|
||||
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)?;
|
||||
image.mount(&config)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mount_extra(config: Config) -> Result<()> {
|
||||
info!("citadel-mount extra");
|
||||
let mut image = ResourceImage::find("citadel-extra")?;
|
||||
image.mount(&config)?;
|
||||
Ok(())
|
||||
}
|
110
citadel-mount/src/rootfs.rs
Normal file
110
citadel-mount/src/rootfs.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use std::process::Command;
|
||||
|
||||
use libcitadel::{BlockDev,Result,Partition,CommandLine,Config,ImageHeader,MetaInfo,PathExt};
|
||||
use BootSelection;
|
||||
use ResourceImage;
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
|
||||
|
||||
pub struct Rootfs {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Rootfs {
|
||||
pub fn new(config: Config) -> Rootfs {
|
||||
Rootfs { config }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
self.setup_rootfs_resource()
|
||||
}
|
||||
|
||||
fn allow_resource(&self) -> bool {
|
||||
CommandLine::install_mode() || CommandLine::recovery_mode()
|
||||
}
|
||||
|
||||
fn setup_partition(&self, partition: Partition) -> Result<()> {
|
||||
if CommandLine::noverity() {
|
||||
self.setup_partition_unverified(&partition)
|
||||
} else {
|
||||
self.setup_partition_verified(&partition)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
self.setup_resource_verified(&img, &hdr, &metainfo)
|
||||
}
|
||||
}
|
||||
|
||||
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_verified(&self, img: &ResourceImage, hdr: &ImageHeader, metainfo: &MetaInfo) -> Result<()> {
|
||||
if !hdr.has_flag(ImageHeader::FLAG_HASH_TREE) {
|
||||
img.generate_verity_hashtree(&hdr, &metainfo)?;
|
||||
}
|
||||
img.path().verity_setup(ImageHeader::HEADER_SIZE, metainfo.nblocks(), metainfo.verity_root(), "rootfs")
|
||||
}
|
||||
|
||||
fn setup_partition_unverified(&self, partition: &Partition) -> Result<()> {
|
||||
info!("Creating /dev/mapper/rootfs device with linear device mapping of partition (no verity)");
|
||||
self.setup_linear_mapping(partition.path())
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
fn setup_linear_mapping(&self, blockdev: &Path) -> Result<()> {
|
||||
let dev = BlockDev::open_ro(blockdev)?;
|
||||
let table = format!("0 {} linear {} 0",
|
||||
dev.nsectors()?,
|
||||
blockdev.pathstr());
|
||||
|
||||
info!("/usr/sbin/dmsetup create rootfs --table '{}'", table);
|
||||
|
||||
let ok = Command::new("/usr/sbin/dmsetup")
|
||||
.args(&["create", "rootfs", "--table", &table])
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.expect("unable to execute /usr/sbin/dmsetup")
|
||||
.success();
|
||||
|
||||
if !ok {
|
||||
bail!("Failed to set up linear identity mapping with /usr/sbin/dmsetup");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
48
citadel-mount/src/uname.rs
Normal file
48
citadel-mount/src/uname.rs
Normal file
@ -0,0 +1,48 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
350
citadel-realms/Cargo.lock
generated
Normal file
350
citadel-realms/Cargo.lock
generated
Normal file
@ -0,0 +1,350 @@
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "citadel-realms"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termcolor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.2.3"
|
||||
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.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive_internals 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.12.14 (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 = "syn"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.12.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.4.2 (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 = "synom"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.0.4"
|
||||
source = "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 = "vec_map"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.4"
|
||||
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-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"
|
||||
|
||||
[[package]]
|
||||
name = "wincolor"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.4 (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 atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
|
||||
"checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2"
|
||||
"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
|
||||
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
|
||||
"checksum cc 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fedf677519ac9e865c4ff43ef8f930773b37ed6e6ea61b6b83b400a7b5787f49"
|
||||
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
|
||||
"checksum clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dc18f6f4005132120d9711636b32c46a233fad94df6217fa1d81c5e97a9f200"
|
||||
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
|
||||
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
|
||||
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
|
||||
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
|
||||
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
|
||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||
"checksum rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11fb43a206a04116ffd7cfcf9bcb941f8eb6cc7ff667272246b0a1c74259a3cb"
|
||||
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
|
||||
"checksum serde 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "1f4d6340aa5fcdac490a1aa3511ff079b1cdaa45ffb766b2fd83395dae085cd5"
|
||||
"checksum serde_derive 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "a5c7e2a9833bb397a3284b55e208b895e2486b8e6c6682a428e309204cd9d75a"
|
||||
"checksum serde_derive_internals 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc848d073be32cd982380c06587ea1d433bc1a4c4a111de07ec2286a3ddade8"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
"checksum syn 0.12.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8c5bc2d6ff27891209efa5f63e9de78648d7801f085e4653701a692ce938d6fd"
|
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
||||
"checksum termcolor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "56c456352e44f9f91f774ddeeed27c1ec60a2455ed66d692059acfb1d731bda1"
|
||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
|
||||
"checksum toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7540f4ffc193e0d3c94121edb19b055670d369f77d5804db11ae053a45b6e7e"
|
||||
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
||||
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
||||
"checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369"
|
||||
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
|
||||
"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"
|
||||
"checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767"
|
15
citadel-realms/Cargo.toml
Normal file
15
citadel-realms/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "citadel-realms"
|
||||
version = "0.1.0"
|
||||
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
homepage = "http://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
clap = "2.30.0"
|
||||
failure = "0.1.1"
|
||||
toml = "0.4.5"
|
||||
serde_derive = "1.0.27"
|
||||
serde = "1.0.27"
|
||||
termcolor = "0.3"
|
||||
walkdir = "2"
|
43
citadel-realms/src/appimg.rs
Normal file
43
citadel-realms/src/appimg.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use Realm;
|
||||
use Result;
|
||||
|
||||
const BASE_APPIMG_PATH: &str = "/storage/appimg/base.appimg";
|
||||
const BTRFS_COMMAND: &str = "/usr/bin/btrfs";
|
||||
|
||||
pub fn clone_base_appimg(target_realm: &Realm) -> Result<()> {
|
||||
if !Path::new(BASE_APPIMG_PATH).exists() {
|
||||
bail!("base appimg does not exist at {}", BASE_APPIMG_PATH);
|
||||
}
|
||||
let target = format!("/realms/realm-{}/rootfs", target_realm.name());
|
||||
let target_path = Path::new(&target);
|
||||
|
||||
if target_path.exists() {
|
||||
bail!("cannot create clone of base appimg for realm '{}' because rootfs directory already exists at {}",
|
||||
target_realm.name(), target);
|
||||
}
|
||||
|
||||
if !target_path.parent().unwrap().exists() {
|
||||
bail!("cannot create clone of base appimg for realm '{}' because realm directory /realms/realm-{} does not exist.",
|
||||
target_realm.name(), target_realm.name());
|
||||
}
|
||||
|
||||
Command::new(BTRFS_COMMAND)
|
||||
.args(&["subvolume", "snapshot", BASE_APPIMG_PATH, &target ])
|
||||
.status()
|
||||
.map_err(|e| format_err!("failed to execute {}: {}", BTRFS_COMMAND, e))?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
pub fn delete_rootfs_subvolume(realm: &Realm) -> Result<()> {
|
||||
let path = realm.base_path().join("rootfs");
|
||||
Command::new(BTRFS_COMMAND)
|
||||
.args(&["subvolume", "delete", path.to_str().unwrap() ])
|
||||
.status()
|
||||
.map_err(|e| format_err!("failed to execute {}: {}", BTRFS_COMMAND, e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
112
citadel-realms/src/config.rs
Normal file
112
citadel-realms/src/config.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use toml;
|
||||
use Result;
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_zone() -> String {
|
||||
"clear".to_owned()
|
||||
}
|
||||
|
||||
#[derive (Deserialize,Clone)]
|
||||
pub struct RealmConfig {
|
||||
#[serde(default = "default_true", rename="use-shared-dir")]
|
||||
use_shared_dir: bool,
|
||||
|
||||
#[serde(default, rename="use-ephemeral-home")]
|
||||
use_ephemeral_home: bool,
|
||||
|
||||
#[serde(default = "default_true", rename="use-sound")]
|
||||
use_sound: bool,
|
||||
|
||||
#[serde(default = "default_true", rename="use-x11")]
|
||||
use_x11: bool,
|
||||
|
||||
#[serde(default = "default_true", rename="use-wayland")]
|
||||
use_wayland: bool,
|
||||
|
||||
#[serde(default, rename="use-kvm")]
|
||||
use_kvm: bool,
|
||||
|
||||
#[serde(default,rename="use-gpu")]
|
||||
use_gpu: bool,
|
||||
|
||||
#[serde(default = "default_true", rename="use-network")]
|
||||
use_network: bool,
|
||||
|
||||
#[serde(default = "default_zone", rename="network-zone")]
|
||||
network_zone: String,
|
||||
}
|
||||
|
||||
impl RealmConfig {
|
||||
pub fn load_or_default(path: &Path) -> Result<RealmConfig> {
|
||||
if path.exists() {
|
||||
let s = load_as_string(&path)?;
|
||||
let config = toml::from_str::<RealmConfig>(&s)?;
|
||||
Ok(config)
|
||||
} else {
|
||||
Ok(RealmConfig::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default() -> RealmConfig {
|
||||
RealmConfig {
|
||||
use_shared_dir: true,
|
||||
use_ephemeral_home: false,
|
||||
use_sound: true,
|
||||
use_x11: true,
|
||||
use_wayland: true,
|
||||
use_kvm: false,
|
||||
use_gpu: false,
|
||||
use_network: true,
|
||||
network_zone: default_zone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kvm(&self) -> bool {
|
||||
self.use_kvm
|
||||
}
|
||||
|
||||
pub fn gpu(&self) -> bool {
|
||||
self.use_gpu
|
||||
}
|
||||
|
||||
pub fn shared_dir(&self) -> bool {
|
||||
self.use_shared_dir
|
||||
}
|
||||
|
||||
pub fn emphemeral_home(&self) -> bool {
|
||||
self.use_ephemeral_home
|
||||
}
|
||||
|
||||
pub fn sound(&self) -> bool {
|
||||
self.use_sound
|
||||
}
|
||||
|
||||
pub fn x11(&self) -> bool {
|
||||
self.use_x11
|
||||
}
|
||||
|
||||
pub fn wayland(&self) -> bool {
|
||||
self.use_wayland
|
||||
}
|
||||
|
||||
pub fn network(&self) -> bool {
|
||||
self.use_network
|
||||
}
|
||||
|
||||
pub fn network_zone(&self) -> &str {
|
||||
&self.network_zone
|
||||
}
|
||||
}
|
||||
|
||||
fn load_as_string(path: &Path) -> Result<String> {
|
||||
let mut f = File::open(path)?;
|
||||
let mut buffer = String::new();
|
||||
f.read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
307
citadel-realms/src/main.rs
Normal file
307
citadel-realms/src/main.rs
Normal file
@ -0,0 +1,307 @@
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
extern crate libc;
|
||||
extern crate clap;
|
||||
extern crate toml;
|
||||
extern crate termcolor;
|
||||
extern crate walkdir;
|
||||
|
||||
use failure::Error;
|
||||
use clap::{App,Arg,ArgMatches,SubCommand};
|
||||
use clap::AppSettings::*;
|
||||
use std::process::exit;
|
||||
use std::cell::RefCell;
|
||||
use std::result;
|
||||
|
||||
pub type Result<T> = result::Result<T,Error>;
|
||||
|
||||
thread_local! {
|
||||
pub static VERBOSE: RefCell<bool> = RefCell::new(true);
|
||||
}
|
||||
pub fn verbose() -> bool {
|
||||
VERBOSE.with(|f| *f.borrow())
|
||||
}
|
||||
|
||||
macro_rules! warn {
|
||||
($e:expr) => { println!("[!]: {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("[!]: {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
macro_rules! info {
|
||||
($e:expr) => { if ::verbose() { println!("[+]: {}", $e); } };
|
||||
($fmt:expr, $($arg:tt)+) => { if ::verbose() { println!("[+]: {}", format!($fmt, $($arg)+)); } };
|
||||
}
|
||||
|
||||
mod manager;
|
||||
mod realm;
|
||||
mod util;
|
||||
mod systemd;
|
||||
mod config;
|
||||
mod network;
|
||||
mod appimg;
|
||||
|
||||
use realm::{Realm,RealmSymlinks};
|
||||
use manager::RealmManager;
|
||||
use config::RealmConfig;
|
||||
use systemd::Systemd;
|
||||
use network::NetworkConfig;
|
||||
|
||||
fn main() {
|
||||
let app = App::new("citadel-realms")
|
||||
.about("Subgraph Citadel realm management")
|
||||
.after_help("'realms help <command>' to display help for an individual subcommand\n")
|
||||
.global_settings(&[ColoredHelp, DisableVersion, DeriveDisplayOrder, VersionlessSubcommands ])
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.arg(Arg::with_name("quiet")
|
||||
.long("quiet")
|
||||
.help("Don't display extra output"))
|
||||
|
||||
.subcommand(SubCommand::with_name("list")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Display list of all realms"))
|
||||
|
||||
.subcommand(SubCommand::with_name("shell")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Open shell in current or named realm")
|
||||
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.help("Name of a realm to open shell in. Use current realm if omitted."))
|
||||
|
||||
.arg(Arg::with_name("root-shell")
|
||||
.long("root")
|
||||
.help("Open shell as root instead of user account.")))
|
||||
|
||||
.subcommand(SubCommand::with_name("terminal")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Launch terminal in current or named realm")
|
||||
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.help("Name of realm to open terminal in. Use current realm if omitted.")))
|
||||
|
||||
.subcommand(SubCommand::with_name("start")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Start named realm or default realm")
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.help("Name of realm to start. Use default realm if omitted.")))
|
||||
|
||||
.subcommand(SubCommand::with_name("stop")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Stop a running realm by name")
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.required(true)
|
||||
.help("Name of realm to stop.")))
|
||||
|
||||
.subcommand(SubCommand::with_name("default")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Choose a realm to start automatically on boot")
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.help("Name of a realm to set as default. Display current default realm if omitted.")))
|
||||
|
||||
.subcommand(SubCommand::with_name("current")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Choose a realm to set as 'current' realm")
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.help("Name of a realm to set as current, will start if necessary. Display current realm name if omitted.")))
|
||||
|
||||
.subcommand(SubCommand::with_name("run")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Execute a command in named realm or current realm")
|
||||
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.help("Name of realm to run command in, start if necessary. Use current realm if omitted."))
|
||||
.arg(Arg::with_name("args")
|
||||
.required(true)
|
||||
.last(true)
|
||||
.allow_hyphen_values(true)
|
||||
.multiple(true)))
|
||||
|
||||
.subcommand(SubCommand::with_name("update-appimg")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Launch shell to update application image")
|
||||
|
||||
.arg(Arg::with_name("appimg-name")
|
||||
.long("appimg")
|
||||
.help("Name of application image in /storage/appimg directory. Default is to use base.appimg")
|
||||
.takes_value(true)))
|
||||
|
||||
|
||||
.subcommand(SubCommand::with_name("new")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Create a new realm with the name provided")
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.required(true)
|
||||
.help("Name to assign to newly created realm")))
|
||||
|
||||
.subcommand(SubCommand::with_name("remove")
|
||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||
.about("Remove realm by name")
|
||||
|
||||
.arg(Arg::with_name("no-confirm")
|
||||
.long("no-confirm")
|
||||
.help("Do not prompt for confirmation."))
|
||||
.arg(Arg::with_name("remove-home")
|
||||
.long("remove-home")
|
||||
.help("Also remove home directory with --no-confirm rather than moving it to /realms/removed-homes"))
|
||||
|
||||
.arg(Arg::with_name("realm-name")
|
||||
.help("Name of realm to remove")
|
||||
.required(true)));
|
||||
|
||||
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
if matches.is_present("quiet") {
|
||||
VERBOSE.with(|f| *f.borrow_mut() = false);
|
||||
}
|
||||
|
||||
let result = match matches.subcommand() {
|
||||
("list", _) => do_list(),
|
||||
("start", Some(m)) => do_start(m),
|
||||
("stop", Some(m)) => do_stop(m),
|
||||
("default", Some(m)) => do_default(m),
|
||||
("current", Some(m)) => do_current(m),
|
||||
("run", Some(m)) => do_run(m),
|
||||
("shell", Some(m)) => do_shell(m),
|
||||
("terminal", Some(m)) => do_terminal(m),
|
||||
("new", Some(m)) => do_new(m),
|
||||
("remove", Some(m)) => do_remove(m),
|
||||
("update-appimg", _) => do_update_appimg(),
|
||||
_ => {
|
||||
let _ = do_list();
|
||||
exit(0);
|
||||
},
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
warn!("{}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_root() -> bool {
|
||||
unsafe {
|
||||
libc::geteuid() == 0
|
||||
}
|
||||
}
|
||||
|
||||
fn require_root() -> Result<()> {
|
||||
if !is_root() {
|
||||
bail!("You need to do that as root")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_list() -> Result<()> {
|
||||
let manager = RealmManager::load()?;
|
||||
println!();
|
||||
manager.list()?;
|
||||
println!("\n 'realms help' for list of commands\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_start(matches: &ArgMatches) -> Result<()> {
|
||||
require_root()?;
|
||||
let mut manager = RealmManager::load()?;
|
||||
match matches.value_of("realm-name") {
|
||||
Some(name) => manager.start_named_realm(name)?,
|
||||
None => manager.start_default()?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_stop(matches: &ArgMatches) -> Result<()> {
|
||||
require_root()?;
|
||||
let name = matches.value_of("realm-name").unwrap();
|
||||
let mut manager = RealmManager::load()?;
|
||||
manager.stop_realm(name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_default(matches: &ArgMatches) -> Result<()> {
|
||||
let manager = RealmManager::load()?;
|
||||
|
||||
match matches.value_of("realm-name") {
|
||||
Some(name) => {
|
||||
require_root()?;
|
||||
manager.set_default_by_name(name)?;
|
||||
},
|
||||
None => {
|
||||
if let Some(name) = manager.default_realm_name() {
|
||||
println!("Default Realm: {}", name);
|
||||
} else {
|
||||
println!("No default realm.");
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_current(matches: &ArgMatches) -> Result<()> {
|
||||
let manager = RealmManager::load()?;
|
||||
|
||||
match matches.value_of("realm-name") {
|
||||
Some(name) => {
|
||||
require_root()?;
|
||||
manager.set_current_by_name(name)?;
|
||||
},
|
||||
None => {
|
||||
if let Some(name) = manager.current_realm_name() {
|
||||
println!("Current Realm: {}", name);
|
||||
} else {
|
||||
println!("No current realm.");
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn do_run(matches: &ArgMatches) -> Result<()> {
|
||||
let args: Vec<&str> = matches.values_of("args").unwrap().collect();
|
||||
let mut v = Vec::new();
|
||||
for arg in args {
|
||||
v.push(arg.to_string());
|
||||
}
|
||||
let manager = RealmManager::load()?;
|
||||
manager.run_in_realm(matches.value_of("realm-name"), &v, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_shell(matches: &ArgMatches) -> Result<()> {
|
||||
let manager = RealmManager::load()?;
|
||||
let root = matches.is_present("root-shell");
|
||||
manager.launch_shell(matches.value_of("realm-name"), root)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_terminal(matches: &ArgMatches) -> Result<()> {
|
||||
let manager = RealmManager::load()?;
|
||||
manager.launch_terminal(matches.value_of("realm-name"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_new(matches: &ArgMatches) -> Result<()> {
|
||||
require_root()?;
|
||||
let name = matches.value_of("realm-name").unwrap();
|
||||
let mut manager = RealmManager::load()?;
|
||||
manager.new_realm(name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_remove(matches: &ArgMatches) -> Result<()> {
|
||||
require_root()?;
|
||||
let confirm = !matches.is_present("no-confirm");
|
||||
let save_home = !matches.is_present("remove-home");
|
||||
let name = matches.value_of("realm-name").unwrap();
|
||||
let mut manager = RealmManager::load()?;
|
||||
manager.remove_realm(name, confirm, save_home)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_update_appimg() -> Result<()> {
|
||||
require_root()?;
|
||||
let manager = RealmManager::load()?;
|
||||
manager.base_appimg_update()
|
||||
}
|
399
citadel-realms/src/manager.rs
Normal file
399
citadel-realms/src/manager.rs
Normal file
@ -0,0 +1,399 @@
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
|
||||
|
||||
use Realm;
|
||||
use Result;
|
||||
use Systemd;
|
||||
use RealmSymlinks;
|
||||
use NetworkConfig;
|
||||
use util::*;
|
||||
|
||||
const REALMS_BASE_PATH: &str = "/realms";
|
||||
|
||||
pub struct RealmManager {
|
||||
/// Map from realm name -> realm
|
||||
realm_map: HashMap<String, Realm>,
|
||||
|
||||
/// Sorted for 'list'
|
||||
realm_list: Vec<Realm>,
|
||||
|
||||
/// track status of 'current' and 'default' symlinks
|
||||
symlinks: Rc<RefCell<RealmSymlinks>>,
|
||||
|
||||
/// finds free ip addresses to use
|
||||
network: Rc<RefCell<NetworkConfig>>,
|
||||
|
||||
/// interface to systemd
|
||||
systemd: Systemd,
|
||||
}
|
||||
|
||||
|
||||
impl RealmManager {
|
||||
fn new() -> Result<RealmManager> {
|
||||
let network = RealmManager::create_network_config()?;
|
||||
|
||||
Ok(RealmManager {
|
||||
realm_map: HashMap::new(),
|
||||
realm_list: Vec::new(),
|
||||
symlinks: Rc::new(RefCell::new(RealmSymlinks::new())),
|
||||
network: network.clone(),
|
||||
systemd: Systemd::new(network),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_network_config() -> Result<Rc<RefCell<NetworkConfig>>> {
|
||||
let mut network = NetworkConfig::new();
|
||||
network.add_bridge("clear", "172.17.0.0/24")?;
|
||||
Ok(Rc::new(RefCell::new(network)))
|
||||
}
|
||||
|
||||
pub fn load() -> Result<RealmManager> {
|
||||
let mut manager = RealmManager::new()?;
|
||||
manager.symlinks.borrow_mut().load_symlinks()?;
|
||||
if ! PathBuf::from(REALMS_BASE_PATH).exists() {
|
||||
bail!("realms base directory {} does not exist", REALMS_BASE_PATH);
|
||||
}
|
||||
for dent in fs::read_dir(REALMS_BASE_PATH)? {
|
||||
let path = dent?.path();
|
||||
manager.process_realm_path(&path)
|
||||
.map_err(|e| format_err!("error processing entry {} in realm base dir: {}", path.display(), e))?;
|
||||
}
|
||||
manager.realm_list.sort_unstable();
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
///
|
||||
/// Process `path` as an entry from the base realms directory and
|
||||
/// if `path` is a directory, and directory name has prefix "realm-"
|
||||
/// extract chars after prefix as realm name and add a new `Realm`
|
||||
/// instance
|
||||
///
|
||||
fn process_realm_path(&mut self, path: &Path) -> Result<()> {
|
||||
let meta = path.symlink_metadata()?;
|
||||
if !meta.is_dir() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let fname = path_filename(path);
|
||||
if !fname.starts_with("realm-") {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let (_, realm_name) = fname.split_at(6);
|
||||
if !is_valid_realm_name(realm_name) {
|
||||
warn!("ignoring directory in realm storage which has invalid realm name: {}", realm_name);
|
||||
return Ok(())
|
||||
}
|
||||
let rootfs = path.join("rootfs");
|
||||
if !rootfs.exists() {
|
||||
warn!("realm directory {} does not have a rootfs, ignoring", path.display());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
match Realm::new(realm_name, self.symlinks.clone(), self.network.clone()) {
|
||||
Ok(realm) => { self.add_realm_entry(realm);} ,
|
||||
Err(e) => warn!("Ignoring '{}': {}", realm_name, e),
|
||||
};
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
fn add_realm_entry(&mut self, realm: Realm) -> &Realm {
|
||||
self.realm_map.insert(realm.name().to_owned(), realm.clone());
|
||||
self.realm_list.push(realm.clone());
|
||||
self.realm_map.get(realm.name()).expect("cannot find realm we just added to map")
|
||||
}
|
||||
|
||||
fn remove_realm_entry(&mut self, name: &str) -> Result<()> {
|
||||
self.realm_map.remove(name);
|
||||
let list = self.realm_list.clone();
|
||||
let mut have_default = false;
|
||||
self.realm_list.clear();
|
||||
for realm in list {
|
||||
if realm.name() != name {
|
||||
if realm.is_default() {
|
||||
have_default = true;
|
||||
}
|
||||
self.realm_list.push(realm);
|
||||
}
|
||||
}
|
||||
if !have_default && !self.realm_list.is_empty() {
|
||||
self.symlinks.borrow_mut().set_default_symlink(self.realm_list[0].name())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn current_realm_name(&self) -> Option<String> {
|
||||
self.symlinks.borrow().current()
|
||||
}
|
||||
|
||||
pub fn default_realm_name(&self) -> Option<String> {
|
||||
self.symlinks.borrow().default()
|
||||
}
|
||||
|
||||
///
|
||||
/// Execute shell in a realm. If `realm_name` is `None` then exec
|
||||
/// shell in current realm, otherwise look up realm by name.
|
||||
///
|
||||
/// If `root_shell` is true, open a root shell, otherwise open
|
||||
/// a user (uid = 1000) shell.
|
||||
///
|
||||
pub fn launch_shell(&self, realm_name: Option<&str>, root_shell: bool) -> Result<()> {
|
||||
let run_shell = |realm: &Realm| {
|
||||
info!("opening shell in realm '{}'", realm.name());
|
||||
realm.exec_shell(root_shell)?;
|
||||
info!("exiting shell in realm '{}'", realm.name());
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if let Some(name) = realm_name {
|
||||
self.with_named_realm(name, true, run_shell)
|
||||
} else {
|
||||
self.with_current_realm(run_shell)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn launch_terminal(&self, name: Option<&str>) -> Result<()> {
|
||||
let run_terminal = |realm: &Realm| {
|
||||
info!("opening terminal in realm '{}'", realm.name());
|
||||
let title_arg = format!("Realm: {}", realm.name());
|
||||
realm.run(&["/usr/bin/gnome-terminal".to_owned(), "--title".to_owned(), title_arg], true)
|
||||
};
|
||||
|
||||
if let Some(name) = name {
|
||||
self.with_named_realm(name, true, run_terminal)
|
||||
} else {
|
||||
self.with_current_realm(run_terminal)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn run_in_realm(&self, realm_name: Option<&str>, args: &[String], use_launcher: bool) -> Result<()> {
|
||||
|
||||
if let Some(name) = realm_name {
|
||||
self.with_named_realm(name, true, |realm| realm.run(args, use_launcher))
|
||||
} else {
|
||||
self.with_current_realm(|realm| realm.run(args, use_launcher))
|
||||
}
|
||||
}
|
||||
|
||||
fn with_current_realm<F: Fn(&Realm)->Result<()>>(&self, f: F) -> Result<()> {
|
||||
match self.symlinks.borrow().current() {
|
||||
Some(ref name) => {
|
||||
self.with_named_realm(name, false, f)?;
|
||||
},
|
||||
None => {
|
||||
warn!("No current realm instance to run command in");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn with_named_realm<F: Fn(&Realm)->Result<()>>(&self, name: &str, want_start: bool, f: F) -> Result<()> {
|
||||
match self.realm(name) {
|
||||
Some(realm) => {
|
||||
if want_start && !realm.is_running()? {
|
||||
info!("realm '{}' is not running, starting it.", realm.name());
|
||||
self.start_realm(realm)?;
|
||||
}
|
||||
f(realm)
|
||||
},
|
||||
None => bail!("no realm with name '{}' exists", name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list(&self) -> Result<()> {
|
||||
let mut out = ColoredOutput::new();
|
||||
self.print_realm_header(&mut out);
|
||||
for realm in &self.realm_list {
|
||||
self.print_realm(realm, &mut out)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_realm_header(&self, out: &mut ColoredOutput) {
|
||||
out.write(" REALMS ").bold("bold").write(": current, ").bright("colored")
|
||||
.write(": running, (default) starts on boot\n").write(" ------\n\n");
|
||||
}
|
||||
|
||||
fn print_realm(&self, realm: &Realm, out: &mut ColoredOutput) -> Result<()> {
|
||||
let name = format!("{:12}", realm.name());
|
||||
if realm.is_current() {
|
||||
out.write(" > ").bold(&name);
|
||||
} else if realm.is_running()? {
|
||||
out.write(" ").bright(&name);
|
||||
} else {
|
||||
out.write(" ").dim(&name);
|
||||
}
|
||||
|
||||
if realm.is_default() {
|
||||
out.write(" (default)");
|
||||
}
|
||||
out.write("\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_default(&mut self) -> Result<()> {
|
||||
let default = self.symlinks.borrow().default();
|
||||
if let Some(ref realm_name) = default {
|
||||
self.start_named_realm(realm_name)?;
|
||||
return Ok(());
|
||||
}
|
||||
bail!("No default realm to start");
|
||||
}
|
||||
|
||||
pub fn start_named_realm(&mut self, realm_name: &str) -> Result<()> {
|
||||
info!("starting realm '{}'", realm_name);
|
||||
self.with_named_realm(realm_name, false, |realm| self.start_realm(realm))
|
||||
}
|
||||
|
||||
fn start_realm(&self, realm: &Realm) -> Result<()> {
|
||||
let mut symlinks = self.symlinks.borrow_mut();
|
||||
let no_current_realm = symlinks.current().is_none();
|
||||
// no realm is current, so make this realm the current one
|
||||
// service file for realm will also start desktopd, so this symlink
|
||||
// must be created before launching realm.
|
||||
if no_current_realm {
|
||||
symlinks.set_current_symlink(Some(realm.name()))?;
|
||||
}
|
||||
if let Err(e) = realm.start() {
|
||||
if no_current_realm {
|
||||
// oops realm failed to start, need to reset symlink we changed
|
||||
symlinks.set_current_symlink(None)?;
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn stop_realm(&mut self, name: &str) -> Result<()> {
|
||||
match self.realm_map.get(name) {
|
||||
Some(realm) => {
|
||||
realm.stop()?;
|
||||
self.set_current_if_none()?;
|
||||
},
|
||||
None => {
|
||||
warn!("Cannot stop '{}'. Realm does not exist", name);
|
||||
return Ok(())
|
||||
},
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_current_if_none(&self) -> Result<()> {
|
||||
let mut symlinks = self.symlinks.borrow_mut();
|
||||
if symlinks.current().is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(ref name) = self.find_running_realm_name()? {
|
||||
symlinks.set_current_symlink(Some(name))?;
|
||||
self.systemd.restart_desktopd()?;
|
||||
} else {
|
||||
self.systemd.stop_desktopd()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_running_realm_name(&self) -> Result<Option<String>> {
|
||||
for realm in self.realm_map.values() {
|
||||
if realm.is_running()? {
|
||||
return Ok(Some(realm.name().to_string()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn set_current_by_name(&self, realm_name: &str) -> Result<()> {
|
||||
self.with_named_realm(realm_name, false, |realm| realm.set_current())
|
||||
}
|
||||
|
||||
pub fn set_default_by_name(&self, realm_name: &str) -> Result<()> {
|
||||
self.with_named_realm(realm_name, false, |realm| realm.set_default())
|
||||
}
|
||||
pub fn realm_name_exists(&self, name: &str) -> bool {
|
||||
self.realm_map.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn realm(&self, name: &str) -> Option<&Realm> {
|
||||
self.realm_map.get(name)
|
||||
}
|
||||
|
||||
pub fn new_realm(&mut self, name: &str) -> Result<&Realm> {
|
||||
if !is_valid_realm_name(name) {
|
||||
bail!("'{}' is not a valid realm name. Only letters, numbers and dash '-' symbol allowed in name. First character must be a letter", name);
|
||||
} else if self.realm_name_exists(name) {
|
||||
bail!("A realm with name '{}' already exists", name);
|
||||
}
|
||||
|
||||
let realm = Realm::new(name, self.symlinks.clone(), self.network.clone())?;
|
||||
|
||||
match realm.create_realm_directory() {
|
||||
Ok(()) => Ok(self.add_realm_entry(realm)),
|
||||
Err(e) => {
|
||||
fs::remove_dir_all(realm.base_path())?;
|
||||
Err(e)
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn remove_realm(&mut self, realm_name: &str, confirm: bool, save_home: bool) -> Result<()> {
|
||||
self.with_named_realm(realm_name, false, |realm| {
|
||||
if realm.base_path().join(".realmlock").exists() {
|
||||
warn!("Realm '{}' has .realmlock file in base directory to protect it from deletion.", realm.name());
|
||||
warn!("Remove this file from {} before running 'realms remove {}' if you really want to delete it", realm.base_path().display(), realm.name());
|
||||
return Ok(());
|
||||
}
|
||||
let mut save_home = save_home;
|
||||
if confirm {
|
||||
if !RealmManager::confirm_delete(realm.name(), &mut save_home)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
realm.delete_realm(save_home)?;
|
||||
self.set_current_if_none()
|
||||
})?;
|
||||
|
||||
self.remove_realm_entry(realm_name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn confirm_delete(realm_name: &str, save_home: &mut bool) -> Result<bool> {
|
||||
let you_sure = RealmManager::prompt_user(&format!("Are you sure you want to remove realm '{}'?", realm_name), false)?;
|
||||
if !you_sure {
|
||||
info!("Ok, not removing");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
println!("\nThe home directory for this realm can be saved in /realms/removed/home-{}\n", realm_name);
|
||||
*save_home = RealmManager::prompt_user("Would you like to save the home directory?", true)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn prompt_user(prompt: &str, default_y: bool) -> Result<bool> {
|
||||
let yn = if default_y { "(Y/n)" } else { "(y/N)" };
|
||||
use std::io::{stdin,stdout};
|
||||
print!("{} {} : ", prompt, yn);
|
||||
stdout().flush()?;
|
||||
let mut line = String::new();
|
||||
stdin().read_line(&mut line)?;
|
||||
|
||||
let yes = match line.trim().chars().next() {
|
||||
Some(c) => c == 'Y' || c == 'y',
|
||||
None => default_y,
|
||||
};
|
||||
Ok(yes)
|
||||
}
|
||||
|
||||
pub fn base_appimg_update(&self) -> Result<()> {
|
||||
info!("Entering root shell on base appimg");
|
||||
self.systemd.base_image_update_shell()
|
||||
}
|
||||
}
|
210
citadel-realms/src/network.rs
Normal file
210
citadel-realms/src/network.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use std::path::PathBuf;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::collections::{HashSet,HashMap};
|
||||
use std::io::{BufReader,BufRead,Write};
|
||||
use std::fs::{self,File};
|
||||
|
||||
use Result;
|
||||
|
||||
const MIN_MASK: usize = 16;
|
||||
const MAX_MASK: usize = 24;
|
||||
const RESERVED_OCTET: u32 = 213;
|
||||
|
||||
/// Manage ip address assignment for bridges
|
||||
pub struct NetworkConfig {
|
||||
allocators: HashMap<String, BridgeAllocator>,
|
||||
}
|
||||
|
||||
impl NetworkConfig {
|
||||
pub fn new() -> NetworkConfig {
|
||||
NetworkConfig {
|
||||
allocators: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_bridge(&mut self, name: &str, network: &str) -> Result<()> {
|
||||
let allocator = BridgeAllocator::for_bridge(name, network)
|
||||
.map_err(|e| format_err!("Failed to create bridge allocator: {}", e))?;
|
||||
self.allocators.insert(name.to_owned(), allocator);
|
||||
Ok(())
|
||||
}
|
||||
pub fn gateway(&self, bridge: &str) -> Result<String> {
|
||||
match self.allocators.get(bridge) {
|
||||
Some(allocator) => Ok(allocator.gateway()),
|
||||
None => bail!("Failed to return gateway address for bridge {} because it does not exist", bridge),
|
||||
}
|
||||
}
|
||||
pub fn allocate_address_for(&mut self, bridge: &str, realm_name: &str) -> Result<String> {
|
||||
match self.allocators.get_mut(bridge) {
|
||||
Some(allocator) => allocator.allocate_address_for(realm_name),
|
||||
None => bail!("Failed to allocate address for bridge {} because it does not exist", bridge),
|
||||
}
|
||||
}
|
||||
pub fn free_allocation_for(&mut self, bridge: &str, realm_name: &str) -> Result<()> {
|
||||
match self.allocators.get_mut(bridge) {
|
||||
Some(allocator) => allocator.free_allocation_for(realm_name),
|
||||
None => bail!("Failed to free address on bridge {} because it does not exist", bridge),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserved(&self, bridge: &str) -> Result<String> {
|
||||
match self.allocators.get(bridge) {
|
||||
Some(allocator) => Ok(allocator.reserved()),
|
||||
None => bail!("Failed to return reserved address for bridge {} because it does not exist", bridge),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Allocates IP addresses for a bridge shared by multiple realms.
|
||||
///
|
||||
/// State information is stored in /run/realms/network-$bridge as
|
||||
/// colon ':' separated pairs of realm name and allocated ip address
|
||||
///
|
||||
/// realm-a:172.17.0.2
|
||||
/// realm-b:172.17.0.3
|
||||
///
|
||||
struct BridgeAllocator {
|
||||
bridge: String,
|
||||
network: Ipv4Addr,
|
||||
mask_size: usize,
|
||||
allocated: HashSet<Ipv4Addr>,
|
||||
allocations: HashMap<String, Ipv4Addr>,
|
||||
}
|
||||
|
||||
impl BridgeAllocator {
|
||||
pub fn for_bridge(bridge: &str, network: &str) -> Result<BridgeAllocator> {
|
||||
let (addr_str, mask_size) = match network.find('/') {
|
||||
Some(idx) => {
|
||||
let (net,bits) = network.split_at(idx);
|
||||
(net.to_owned(), bits[1..].parse()?)
|
||||
},
|
||||
None => (network.to_owned(), 24),
|
||||
};
|
||||
if mask_size > MAX_MASK || mask_size < MIN_MASK {
|
||||
bail!("Unsupported network mask size of {}", mask_size);
|
||||
}
|
||||
|
||||
let mask = (1u32 << (32 - mask_size)) - 1;
|
||||
let ip = addr_str.parse::<Ipv4Addr>()?;
|
||||
|
||||
if (u32::from(ip) & mask) != 0 {
|
||||
bail!("network {} has masked bits with netmask /{}", addr_str, mask_size);
|
||||
}
|
||||
|
||||
let mut conf = BridgeAllocator::new(bridge, ip, mask_size);
|
||||
conf.load_state()?;
|
||||
Ok(conf)
|
||||
}
|
||||
|
||||
fn new(bridge: &str, network: Ipv4Addr, mask_size: usize) -> BridgeAllocator {
|
||||
let mut allocator = BridgeAllocator {
|
||||
bridge: bridge.to_owned(),
|
||||
allocated: HashSet::new(),
|
||||
allocations: HashMap::new(),
|
||||
network, mask_size,
|
||||
};
|
||||
let rsv = u32::from(network) | RESERVED_OCTET;
|
||||
allocator.allocated.insert(Ipv4Addr::from(rsv));
|
||||
allocator
|
||||
}
|
||||
|
||||
fn allocate_address_for(&mut self, realm_name: &str) -> Result<String> {
|
||||
match self.find_free_address() {
|
||||
Some(addr) => {
|
||||
self.allocated.insert(addr.clone());
|
||||
if let Some(old) = self.allocations.insert(realm_name.to_owned(), addr.clone()) {
|
||||
self.allocated.remove(&old);
|
||||
}
|
||||
self.write_state()?;
|
||||
return Ok(format!("{}/{}", addr, self.mask_size));
|
||||
},
|
||||
None => bail!("No free IP address could be found to assign to {}", realm_name),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn find_free_address(&self) -> Option<Ipv4Addr> {
|
||||
let mask = (1u32 << (32 - self.mask_size)) - 1;
|
||||
let net = u32::from(self.network);
|
||||
for i in 2..mask {
|
||||
let addr = Ipv4Addr::from(net + i);
|
||||
if !self.allocated.contains(&addr) {
|
||||
return Some(addr);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn gateway(&self) -> String {
|
||||
let gw = u32::from(self.network) + 1;
|
||||
let addr = Ipv4Addr::from(gw);
|
||||
addr.to_string()
|
||||
}
|
||||
|
||||
fn reserved(&self) -> String {
|
||||
let rsv = u32::from(self.network) | RESERVED_OCTET;
|
||||
let addr = Ipv4Addr::from(rsv);
|
||||
format!("{}/{}", addr, self.mask_size)
|
||||
}
|
||||
|
||||
fn free_allocation_for(&mut self, realm_name: &str) -> Result<()> {
|
||||
match self.allocations.remove(realm_name) {
|
||||
Some(ip) => {
|
||||
self.allocated.remove(&ip);
|
||||
self.write_state()?;
|
||||
}
|
||||
None => warn!("No address allocation found for realm {}", realm_name),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state_file_path(&self) -> PathBuf {
|
||||
PathBuf::from(format!("/run/realms/network-{}", self.bridge))
|
||||
}
|
||||
|
||||
|
||||
fn load_state(&mut self) -> Result<()> {
|
||||
let path = self.state_file_path();
|
||||
if !path.exists() {
|
||||
return Ok(())
|
||||
}
|
||||
let f = File::open(path)?;
|
||||
let reader = BufReader::new(f);
|
||||
for line in reader.lines() {
|
||||
let line = &line?;
|
||||
self.parse_state_line(line)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_state_line(&mut self, line: &str) -> Result<()> {
|
||||
match line.find(":") {
|
||||
Some(idx) => {
|
||||
let (name,addr) = line.split_at(idx);
|
||||
let ip = addr[1..].parse::<Ipv4Addr>()?;
|
||||
self.allocated.insert(ip.clone());
|
||||
self.allocations.insert(name.to_owned(), ip);
|
||||
},
|
||||
None => bail!("Could not parse line from network state file: {}", line),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_state(&mut self) -> Result<()> {
|
||||
let path = self.state_file_path();
|
||||
let dir = path.parent().unwrap();
|
||||
if !dir.exists() {
|
||||
fs::create_dir_all(dir)
|
||||
.map_err(|e| format_err!("failed to create directory {} for network allocation state file: {}", dir.display(), e))?;
|
||||
}
|
||||
let mut f = File::create(&path)
|
||||
.map_err(|e| format_err!("failed to open network state file {} for writing: {}", path.display(), e))?;
|
||||
|
||||
for (realm,addr) in &self.allocations {
|
||||
writeln!(f, "{}:{}", realm, addr)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
375
citadel-realms/src/realm.rs
Normal file
375
citadel-realms/src/realm.rs
Normal file
@ -0,0 +1,375 @@
|
||||
use std::path::{PathBuf,Path};
|
||||
use std::rc::Rc;
|
||||
use std::cmp::Ordering;
|
||||
use std::cell::{RefCell,Cell};
|
||||
use std::fs::{self,File};
|
||||
use std::os::unix::fs::{symlink,MetadataExt};
|
||||
|
||||
use {RealmConfig,Result,Systemd,NetworkConfig};
|
||||
use util::*;
|
||||
use appimg::*;
|
||||
|
||||
const REALMS_BASE_PATH: &str = "/realms";
|
||||
const REALMS_RUN_PATH: &str = "/run/realms";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Realm {
|
||||
/// The realm name. Corresponds to a directory with path /realms/realm-$name/
|
||||
name: String,
|
||||
|
||||
/// modify time of timestamp file which is updated when realm is set to current.
|
||||
ts: Cell<i64>,
|
||||
|
||||
/// Configuration options, either default values or values read from file /realms/realm-$name/config
|
||||
config: RealmConfig,
|
||||
|
||||
/// wrapper around various calls to systemd utilities
|
||||
systemd: Systemd,
|
||||
|
||||
/// reads and manages 'current' and 'default' symlinks, shared between all instances
|
||||
symlinks: Rc<RefCell<RealmSymlinks>>,
|
||||
}
|
||||
|
||||
impl Realm {
|
||||
pub fn new(name: &str, symlinks: Rc<RefCell<RealmSymlinks>>, network: Rc<RefCell<NetworkConfig>>) -> Result<Realm> {
|
||||
let mut realm = Realm {
|
||||
name: name.to_string(),
|
||||
ts: Cell::new(0),
|
||||
systemd: Systemd::new(network),
|
||||
config: RealmConfig::default(), symlinks,
|
||||
};
|
||||
realm.load_config()?;
|
||||
realm.load_timestamp()?;
|
||||
Ok(realm)
|
||||
}
|
||||
|
||||
fn load_config(&mut self) -> Result<()> {
|
||||
let path = self.base_path().join("config");
|
||||
self.config = RealmConfig::load_or_default(&path)
|
||||
.map_err(|e| format_err!("failed to load realm config file {}: {}", path.display(), e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &RealmConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
pub fn base_path(&self) -> PathBuf {
|
||||
PathBuf::from(REALMS_BASE_PATH).join(format!("realm-{}", self.name))
|
||||
}
|
||||
|
||||
pub fn set_default(&self) -> Result<()> {
|
||||
if self.is_default() {
|
||||
info!("Realm '{}' is already default realm", self.name());
|
||||
return Ok(())
|
||||
}
|
||||
self.symlinks.borrow_mut().set_default_symlink(&self.name)?;
|
||||
info!("Realm '{}' set as default realm", self.name());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_current(&self) -> Result<()> {
|
||||
if self.is_current() {
|
||||
info!("Realm '{}' is already current realm", self.name());
|
||||
return Ok(())
|
||||
}
|
||||
if !self.is_running()? {
|
||||
self.start()?;
|
||||
}
|
||||
self.symlinks.borrow_mut().set_current_symlink(Some(&self.name))?;
|
||||
self.systemd.restart_desktopd()?;
|
||||
self.update_timestamp()?;
|
||||
info!("Realm '{}' set as current realm", self.name());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_default(&self) -> bool {
|
||||
self.symlinks.borrow().is_name_default(&self.name)
|
||||
}
|
||||
|
||||
pub fn is_current(&self) -> bool {
|
||||
self.symlinks.borrow().is_name_current(&self.name)
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> Result<bool> {
|
||||
self.systemd.realm_is_active(self)
|
||||
}
|
||||
|
||||
pub fn run(&self, args: &[String], use_launcher: bool) -> Result<()> {
|
||||
self.systemd.machinectl_shell(self, args, use_launcher)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_shell(&self, as_root: bool) -> Result<()> {
|
||||
self.systemd.machinectl_exec_shell(self, as_root)
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<()> {
|
||||
self.systemd.start_realm(self)?;
|
||||
info!("Started realm '{}'", self.name());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Result<()> {
|
||||
self.systemd.stop_realm(self)?;
|
||||
if self.is_current() {
|
||||
self.symlinks.borrow_mut().set_current_symlink(None)?;
|
||||
}
|
||||
info!("Stopped realm '{}'", self.name());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn load_timestamp(&self) -> Result<()> {
|
||||
let tstamp = self.base_path().join(".tstamp");
|
||||
if tstamp.exists() {
|
||||
let meta = tstamp.metadata()?;
|
||||
self.ts.set(meta.mtime());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// create an empty file which is used to track the time at which
|
||||
/// this realm was last made 'current'. These times are used
|
||||
/// to order the output when listing realms.
|
||||
fn update_timestamp(&self) -> Result<()> {
|
||||
let tstamp = self.base_path().join(".tstamp");
|
||||
if tstamp.exists() {
|
||||
fs::remove_file(&tstamp)?;
|
||||
}
|
||||
File::create(&tstamp)
|
||||
.map_err(|e| format_err!("failed to create timestamp file {}: {}", tstamp.display(), e))?;
|
||||
// also load the new value
|
||||
self.load_timestamp()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_realm_directory(&self) -> Result<()> {
|
||||
if self.base_path().exists() {
|
||||
bail!("realm base directory {} already exists, cannot create", self.base_path().display());
|
||||
}
|
||||
|
||||
fs::create_dir(self.base_path())
|
||||
.map_err(|e| format_err!("failed to create realm base directory {}: {}", self.base_path().display(), e))?;
|
||||
|
||||
self.create_home_directory()
|
||||
.map_err(|e| format_err!("failed to create realm home directory {}: {}", self.base_path().join("home").display(), e))?;
|
||||
|
||||
// This must be last step because if an error is returned caller assumes that subvolume was
|
||||
// never created and does not need to be removed.
|
||||
clone_base_appimg(self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_home_directory(&self) -> Result<()> {
|
||||
let home = self.base_path().join("home");
|
||||
fs::create_dir(&home)?;
|
||||
chown(&home, 1000, 1000)?;
|
||||
let skel = PathBuf::from(REALMS_BASE_PATH).join("skel");
|
||||
if skel.exists() {
|
||||
info!("Populating realm home directory with files from {}", skel.display());
|
||||
copy_tree(&skel, &home)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_realm(&self, save_home: bool) -> Result<()> {
|
||||
if save_home {
|
||||
self.save_home_for_delete()?;
|
||||
}
|
||||
if self.is_running()? {
|
||||
self.stop()?;
|
||||
}
|
||||
info!("removing rootfs subvolume for '{}'", self.name());
|
||||
delete_rootfs_subvolume(self)?;
|
||||
|
||||
info!("removing realm directory {}", self.base_path().display());
|
||||
fs::remove_dir_all(self.base_path())?;
|
||||
|
||||
info!("realm '{}' has been removed", self.name());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_home_for_delete(&self) -> Result<()> {
|
||||
let target = PathBuf::from(&format!("/realms/removed/home-{}", self.name()));
|
||||
if !Path::new("/realms/removed").exists() {
|
||||
fs::create_dir("/realms/removed")?;
|
||||
}
|
||||
|
||||
fs::rename(self.base_path().join("home"), &target)
|
||||
.map_err(|e| format_err!("unable to move realm home directory to {}: {}", target.display(), e))?;
|
||||
info!("home directory been moved to /realms/removed/home-{}, delete it at your leisure", self.name());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Ord for Realm {
|
||||
fn cmp(&self, other: &Realm) -> Ordering {
|
||||
let self_run = self.is_running().unwrap_or(false);
|
||||
let other_run = other.is_running().unwrap_or(false);
|
||||
|
||||
if self_run && !other_run {
|
||||
Ordering::Less
|
||||
} else if !self_run && other_run {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
let self_ts = self.ts.get();
|
||||
let other_ts = other.ts.get();
|
||||
other_ts.cmp(&self_ts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Realm {
|
||||
fn partial_cmp(&self, other: &Realm) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Realm {
|
||||
fn eq(&self, other: &Realm) -> bool {
|
||||
self.cmp(other) == Ordering::Equal
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Realm {}
|
||||
|
||||
pub struct RealmSymlinks {
|
||||
current_name: Option<String>,
|
||||
default_name: Option<String>,
|
||||
}
|
||||
|
||||
impl RealmSymlinks {
|
||||
pub fn new() -> RealmSymlinks {
|
||||
RealmSymlinks {
|
||||
current_name: None,
|
||||
default_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_symlinks(&mut self) -> Result<()> {
|
||||
self.current_name = self.resolve_realm_name(&PathBuf::from(REALMS_RUN_PATH).join("current.realm"))?;
|
||||
self.default_name = self.resolve_realm_name(&PathBuf::from(REALMS_BASE_PATH).join("default.realm"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_name_default(&self, name: &str) -> bool {
|
||||
match self.default() {
|
||||
Some(dname) => dname == name,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_name_current(&self, name: &str) -> bool {
|
||||
match self.current() {
|
||||
Some(cname) => cname == name,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Option<String> {
|
||||
self.current_name.clone()
|
||||
}
|
||||
|
||||
pub fn default(&self) -> Option<String> {
|
||||
self.default_name.clone()
|
||||
}
|
||||
|
||||
|
||||
pub fn set_current_symlink(&mut self, name: Option<&str>) -> Result<()> {
|
||||
let runpath = Path::new(REALMS_RUN_PATH);
|
||||
if !runpath.exists() {
|
||||
fs::create_dir_all(runpath)
|
||||
.map_err(|e| format_err!("failed to create realm runtime directory {}: {}", runpath.display(), e))?;
|
||||
}
|
||||
|
||||
let path = runpath.join("current.realm");
|
||||
if let Some(n) = name {
|
||||
let tmp = Path::new("/run/current.realm.tmp");
|
||||
let target = PathBuf::from(REALMS_BASE_PATH).join(format!("realm-{}", n));
|
||||
symlink(&target, tmp)
|
||||
.map_err(|e| format_err!("failed to create symlink from {} to {}: {}", tmp.display(), target.display(), e))?;
|
||||
|
||||
fs::rename(tmp, &path)
|
||||
.map_err(|e| format_err!("failed to rename symlink from {} to {}: {}", tmp.display(), path.display(), e))?;
|
||||
|
||||
self.current_name = Some(n.to_owned());
|
||||
} else {
|
||||
if path.exists() {
|
||||
fs::remove_file(&path)
|
||||
.map_err(|e| format_err!("failed to remove current symlink {}: {}", path.display(), e))?;
|
||||
}
|
||||
self.current_name = None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_default_symlink(&mut self, name: &str) -> Result<()> {
|
||||
let path = PathBuf::from(REALMS_BASE_PATH).join("default.realm");
|
||||
let tmp = Path::new("/realms/default.realm.tmp");
|
||||
let target = format!("realm-{}", name);
|
||||
symlink(&target, tmp)
|
||||
.map_err(|e| format_err!("failed to create symlink from {} to {}: {}", tmp.display(), target, e))?;
|
||||
fs::rename(tmp, &path)
|
||||
.map_err(|e| format_err!("failed to rename symlink from {} to {}: {}", tmp.display(), path.display(), e))?;
|
||||
|
||||
self.default_name = Some(name.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_realm_name(&self, path: &Path) -> Result<Option<String>> {
|
||||
if !path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
let meta = path.symlink_metadata()?;
|
||||
if !meta.file_type().is_symlink() {
|
||||
bail!("{} exists but it is not a symlink", path.display());
|
||||
}
|
||||
let target = RealmSymlinks::absolute_target(path)?;
|
||||
RealmSymlinks::ensure_subdir_of_base(path, &target)?;
|
||||
if !target.is_dir() {
|
||||
bail!("target of symlink {} is not a directory", path.display());
|
||||
}
|
||||
let filename = path_filename(&target);
|
||||
if !filename.starts_with("realm-") {
|
||||
bail!("target of symlink {} is not a realm directory", path.display());
|
||||
}
|
||||
Ok(Some(filename[6..].to_string()))
|
||||
}
|
||||
|
||||
/// Read target of symlink `path` and resolve it to an absolute
|
||||
/// path
|
||||
fn absolute_target(path: &Path) -> Result<PathBuf> {
|
||||
let target = fs::read_link(path)?;
|
||||
if target.is_absolute() {
|
||||
Ok(target)
|
||||
} else if target.components().count() == 1 {
|
||||
match path.parent() {
|
||||
Some(parent) => return Ok(parent.join(target)),
|
||||
None => bail!("Cannot resolve absolute path of symlink target because symlink path has no parent"),
|
||||
}
|
||||
} else {
|
||||
bail!("symlink target has invalid value: {}", target.display())
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_subdir_of_base(path: &Path, target: &Path) -> Result<()> {
|
||||
let realms_base = PathBuf::from(REALMS_BASE_PATH);
|
||||
match target.parent() {
|
||||
Some(parent) => {
|
||||
if parent != realms_base.as_path() {
|
||||
bail!("target of symlink {} points outside of {} directory: {}", path.display(), REALMS_BASE_PATH, target.display());
|
||||
}
|
||||
},
|
||||
None => bail!("target of symlink {} has invalid value (no parent): {}", path.display(), target.display()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
383
citadel-realms/src/systemd.rs
Normal file
383
citadel-realms/src/systemd.rs
Normal file
@ -0,0 +1,383 @@
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::process::Command;
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::{self,File};
|
||||
use std::fmt::Write;
|
||||
use std::io::Write as IoWrite;
|
||||
use std::env;
|
||||
|
||||
const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl";
|
||||
const MACHINECTL_PATH: &str = "/usr/bin/machinectl";
|
||||
const SYSTEMD_NSPAWN_PATH: &str = "/run/systemd/nspawn";
|
||||
const SYSTEMD_UNIT_PATH: &str = "/run/systemd/system";
|
||||
|
||||
const DESKTOPD_SERVICE: &str = "citadel-desktopd.service";
|
||||
|
||||
use Realm;
|
||||
use NetworkConfig;
|
||||
use Result;
|
||||
use util::{path_filename,is_first_char_alphabetic};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Systemd {
|
||||
network: Rc<RefCell<NetworkConfig>>,
|
||||
}
|
||||
|
||||
impl Systemd {
|
||||
|
||||
pub fn new(network: Rc<RefCell<NetworkConfig>>) -> Systemd {
|
||||
Systemd { network }
|
||||
}
|
||||
|
||||
pub fn realm_is_active(&self, realm: &Realm) -> Result<bool> {
|
||||
let active = self.is_active(&self.realm_service_name(realm))?;
|
||||
let has_config = self.realm_config_exists(realm);
|
||||
if active && !has_config {
|
||||
bail!("Realm {} is running, but config files are missing", realm.name());
|
||||
}
|
||||
if !active && has_config {
|
||||
bail!("Realm {} is not running, but config files are present", realm.name());
|
||||
}
|
||||
Ok(active)
|
||||
}
|
||||
|
||||
pub fn start_realm(&self, realm: &Realm) -> Result<()> {
|
||||
if self.realm_is_active(realm)? {
|
||||
warn!("Realm {} is already running", realm.name());
|
||||
return Ok(())
|
||||
}
|
||||
self.write_realm_launch_config(realm)?;
|
||||
self.systemctl_start(&self.realm_service_name(realm))?;
|
||||
if realm.config().emphemeral_home() {
|
||||
self.setup_ephemeral_home(realm)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
pub fn base_image_update_shell(&self) -> Result<()> {
|
||||
let netconf = self.network.borrow_mut();
|
||||
let gw = netconf.gateway("clear")?;
|
||||
let addr = netconf.reserved("clear")?;
|
||||
let gw_env = format!("--setenv=IFCONFIG_GW={}", gw);
|
||||
let addr_env = format!("--setenv=IFCONFIG_IP={}", addr);
|
||||
|
||||
Command::new("/usr/bin/systemd-nspawn")
|
||||
.args(&[
|
||||
&addr_env, &gw_env,
|
||||
"--quiet",
|
||||
"--machine=base-appimg-update",
|
||||
"--directory=/storage/appimg/base.appimg",
|
||||
"--network-zone=clear",
|
||||
"/bin/bash", "-c", "/usr/libexec/configure-host0.sh && exec /bin/bash"
|
||||
]).status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_ephemeral_home(&self, realm: &Realm) -> Result<()> {
|
||||
|
||||
// 1) if exists: machinectl copy-to /realms/skel /home/user
|
||||
if Path::new("/realms/skel").exists() {
|
||||
self.machinectl_copy_to(realm, "/realms/skel", "/home/user")?;
|
||||
}
|
||||
|
||||
// 2) if exists: machinectl copy-to /realms/realm-$name /home/user
|
||||
let realm_skel = realm.base_path().join("skel");
|
||||
if realm_skel.exists() {
|
||||
self.machinectl_copy_to(realm, realm_skel.to_str().unwrap(), "/home/user")?;
|
||||
}
|
||||
|
||||
let home = realm.base_path().join("home");
|
||||
if !home.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 3) for each : machinectl bind /realms/realm-$name/home/$dir /home/user/$dir
|
||||
for dent in fs::read_dir(home)? {
|
||||
let path = dent?.path();
|
||||
self.bind_mount_home_subdir(realm, &path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bind_mount_home_subdir(&self, realm: &Realm, path: &Path) -> Result<()> {
|
||||
let path = path.canonicalize()?;
|
||||
if !path.is_dir() {
|
||||
return Ok(());
|
||||
}
|
||||
let fname = path_filename(&path);
|
||||
if !is_first_char_alphabetic(fname) {
|
||||
return Ok(());
|
||||
}
|
||||
let from = format!("/realms/realm-{}/home/{}", realm.name(), fname);
|
||||
let to = format!("/home/user/{}", fname);
|
||||
self.machinectl_bind(realm, &from, &to)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop_realm(&self, realm: &Realm) -> Result<()> {
|
||||
if !self.realm_is_active(realm)? {
|
||||
warn!("Realm {} is not running", realm.name());
|
||||
return Ok(());
|
||||
}
|
||||
self.systemctl_stop(&self.realm_service_name(realm))?;
|
||||
self.remove_realm_launch_config(realm)?;
|
||||
self.network.borrow_mut().free_allocation_for(realm.config().network_zone(), realm.name())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restart_desktopd(&self) -> Result<bool> {
|
||||
self.systemctl_restart(DESKTOPD_SERVICE)
|
||||
}
|
||||
pub fn stop_desktopd(&self) -> Result<bool> {
|
||||
self.systemctl_stop(DESKTOPD_SERVICE)
|
||||
}
|
||||
|
||||
fn realm_service_name(&self, realm: &Realm) -> String {
|
||||
format!("realm-{}.service", realm.name())
|
||||
}
|
||||
|
||||
fn is_active(&self, name: &str) -> Result<bool> {
|
||||
Command::new(SYSTEMCTL_PATH)
|
||||
.args(&["--quiet", "is-active", name])
|
||||
.status()
|
||||
.map(|status| status.success())
|
||||
.map_err(|e| format_err!("failed to execute{}: {}", MACHINECTL_PATH, e))
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn systemctl_restart(&self, name: &str) -> Result<bool> {
|
||||
self.run_systemctl("restart", name)
|
||||
}
|
||||
|
||||
fn systemctl_start(&self, name: &str) -> Result<bool> {
|
||||
self.run_systemctl("start", name)
|
||||
}
|
||||
|
||||
fn systemctl_stop(&self, name: &str) -> Result<bool> {
|
||||
self.run_systemctl("stop", name)
|
||||
}
|
||||
|
||||
fn run_systemctl(&self, op: &str, name: &str) -> Result<bool> {
|
||||
Command::new(SYSTEMCTL_PATH)
|
||||
.arg(op)
|
||||
.arg(name)
|
||||
.status()
|
||||
.map(|status| status.success())
|
||||
.map_err(|e| format_err!("failed to execute {}: {}", MACHINECTL_PATH, e))
|
||||
}
|
||||
|
||||
fn machinectl_copy_to(&self, realm: &Realm, from: &str, to: &str) -> Result<()> {
|
||||
Command::new(MACHINECTL_PATH)
|
||||
.args(&["copy-to", realm.name(), from, to ])
|
||||
.status()
|
||||
.map_err(|e| format_err!("failed to machinectl copy-to {} {} {}: {}", realm.name(), from, to, e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn machinectl_bind(&self, realm: &Realm, from: &str, to: &str) -> Result<()> {
|
||||
Command::new(MACHINECTL_PATH)
|
||||
.args(&["--mkdir", "bind", realm.name(), from, to ])
|
||||
.status()
|
||||
.map_err(|e| format_err!("failed to machinectl bind {} {} {}: {}", realm.name(), from, to, e))?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
pub fn machinectl_exec_shell(&self, realm: &Realm, as_root: bool) -> Result<()> {
|
||||
let namevar = format!("--setenv=REALM_NAME={}", realm.name());
|
||||
let user = if as_root { "root" } else { "user" };
|
||||
let user_at_host = format!("{}@{}", user, realm.name());
|
||||
Command::new(MACHINECTL_PATH)
|
||||
.args(&[ &namevar, "--quiet", "shell", &user_at_host, "/bin/bash"])
|
||||
.status()
|
||||
.map_err(|e| format_err!("failed to execute{}: {}", MACHINECTL_PATH, e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn machinectl_shell(&self, realm: &Realm, args: &[String], launcher: bool) -> Result<()> {
|
||||
let namevar = format!("--setenv=REALM_NAME={}", realm.name());
|
||||
let mut cmd = Command::new(MACHINECTL_PATH);
|
||||
cmd.arg("--quiet");
|
||||
match env::var("DESKTOP_STARTUP_ID") {
|
||||
Ok(val) => {
|
||||
cmd.arg("-E");
|
||||
cmd.arg(&format!("DESKTOP_STARTUP_ID={}", val));
|
||||
},
|
||||
Err(_) => {},
|
||||
};
|
||||
cmd.arg(&namevar);
|
||||
cmd.arg("shell");
|
||||
cmd.arg(format!("user@{}", realm.name()));
|
||||
|
||||
if launcher {
|
||||
cmd.arg("/usr/libexec/launch");
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
cmd.arg(&arg);
|
||||
}
|
||||
cmd.status().map_err(|e| format_err!("failed to execute{}: {}", MACHINECTL_PATH, e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn realm_service_path(&self, realm: &Realm) -> PathBuf {
|
||||
PathBuf::from(SYSTEMD_UNIT_PATH).join(self.realm_service_name(realm))
|
||||
}
|
||||
|
||||
fn realm_nspawn_path(&self, realm: &Realm) -> PathBuf {
|
||||
PathBuf::from(SYSTEMD_NSPAWN_PATH).join(format!("{}.nspawn", realm.name()))
|
||||
}
|
||||
|
||||
fn realm_config_exists(&self, realm: &Realm) -> bool {
|
||||
self.realm_service_path(realm).exists() || self.realm_nspawn_path(realm).exists()
|
||||
}
|
||||
|
||||
fn remove_realm_launch_config(&self, realm: &Realm) -> Result<()> {
|
||||
let nspawn_path = self.realm_nspawn_path(realm);
|
||||
if nspawn_path.exists() {
|
||||
fs::remove_file(&nspawn_path)?;
|
||||
}
|
||||
let service_path = self.realm_service_path(realm);
|
||||
if service_path.exists() {
|
||||
fs::remove_file(&service_path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_realm_launch_config(&self, realm: &Realm) -> Result<()> {
|
||||
let nspawn_path = self.realm_nspawn_path(realm);
|
||||
let nspawn_content = self.generate_nspawn_file(realm)?;
|
||||
self.write_launch_config_file(&nspawn_path, &nspawn_content)
|
||||
.map_err(|e| format_err!("failed to write nspawn config file {}: {}", nspawn_path.display(), e))?;
|
||||
|
||||
let service_path = self.realm_service_path(realm);
|
||||
let service_content = self.generate_service_file(realm);
|
||||
self.write_launch_config_file(&service_path, &service_content)
|
||||
.map_err(|e| format_err!("failed to write service config file {}: {}", service_path.display(), e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the string `content` to file `path`. If the directory does
|
||||
/// not already exist, create it.
|
||||
fn write_launch_config_file(&self, path: &Path, content: &str) -> Result<()> {
|
||||
match path.parent() {
|
||||
Some(parent) => {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
},
|
||||
None => bail!("config file path {} has no parent?", path.display()),
|
||||
};
|
||||
let mut f = File::create(path)?;
|
||||
f.write_all(content.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_nspawn_file(&self, realm: &Realm) -> Result<String> {
|
||||
Ok(NSPAWN_FILE_TEMPLATE
|
||||
.replace("$EXTRA_BIND_MOUNTS", &self.generate_extra_bind_mounts(realm)?)
|
||||
|
||||
.replace("$NETWORK_CONFIG", &self.generate_network_config(realm)?))
|
||||
}
|
||||
|
||||
fn generate_extra_bind_mounts(&self, realm: &Realm) -> Result<String> {
|
||||
let config = realm.config();
|
||||
let mut s = String::new();
|
||||
|
||||
if config.emphemeral_home() {
|
||||
writeln!(s, "TemporaryFileSystem=/home/user:mode=755,uid=1000,gid=1000")?;
|
||||
} else {
|
||||
writeln!(s, "Bind={}/home:/home/user", realm.base_path().display())?;
|
||||
}
|
||||
|
||||
if config.shared_dir() && Path::new("/realms/Shared").exists() {
|
||||
writeln!(s, "Bind=/realms/Shared:/home/user/Shared")?;
|
||||
}
|
||||
|
||||
if config.kvm() {
|
||||
writeln!(s, "Bind=/dev/kvm")?;
|
||||
}
|
||||
|
||||
if config.gpu() {
|
||||
writeln!(s, "Bind=/dev/dri/renderD128")?;
|
||||
}
|
||||
|
||||
if config.sound() {
|
||||
writeln!(s, "Bind=/dev/snd")?;
|
||||
writeln!(s, "Bind=/dev/shm")?;
|
||||
writeln!(s, "BindReadOnly=/run/user/1000/pulse:/run/user/host/pulse")?;
|
||||
}
|
||||
|
||||
if config.x11() {
|
||||
writeln!(s, "BindReadOnly=/tmp/.X11-unix")?;
|
||||
}
|
||||
|
||||
if config.wayland() {
|
||||
writeln!(s, "BindReadOnly=/run/user/1000/wayland-0:/run/user/host/wayland-0")?;
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn generate_network_config(&self, realm: &Realm) -> Result<String> {
|
||||
let mut s = String::new();
|
||||
if realm.config().network() {
|
||||
let mut netconf = self.network.borrow_mut();
|
||||
let zone = realm.config().network_zone();
|
||||
let addr = netconf.allocate_address_for(zone, realm.name())?;
|
||||
let gw = netconf.gateway(zone)?;
|
||||
writeln!(s, "Environment=IFCONFIG_IP={}", addr)?;
|
||||
writeln!(s, "Environment=IFCONFIG_GW={}", gw)?;
|
||||
writeln!(s, "[Network]")?;
|
||||
writeln!(s, "Zone=clear")?;
|
||||
} else {
|
||||
writeln!(s, "[Network]")?;
|
||||
writeln!(s, "Private=true")?;
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn generate_service_file(&self, realm: &Realm) -> String {
|
||||
let rootfs = format!("/realms/realm-{}/rootfs", realm.name());
|
||||
REALM_SERVICE_TEMPLATE.replace("$REALM_NAME", realm.name()).replace("$ROOTFS", &rootfs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub const NSPAWN_FILE_TEMPLATE: &str = r###"
|
||||
[Exec]
|
||||
Boot=true
|
||||
$NETWORK_CONFIG
|
||||
|
||||
[Files]
|
||||
BindReadOnly=/usr/share/themes
|
||||
BindReadOnly=/usr/share/icons/Paper
|
||||
|
||||
BindReadOnly=/storage/citadel-state/resolv.conf:/etc/resolv.conf
|
||||
|
||||
$EXTRA_BIND_MOUNTS
|
||||
|
||||
"###;
|
||||
|
||||
pub const REALM_SERVICE_TEMPLATE: &str = r###"
|
||||
[Unit]
|
||||
Description=Application Image $REALM_NAME instance
|
||||
Wants=citadel-desktopd.service
|
||||
|
||||
[Service]
|
||||
Environment=SYSTEMD_NSPAWN_SHARE_NS_IPC=1
|
||||
ExecStart=/usr/bin/systemd-nspawn --quiet --notify-ready=yes --keep-unit --machine=$REALM_NAME --link-journal=try-guest --directory=$ROOTFS
|
||||
|
||||
KillMode=mixed
|
||||
Type=notify
|
||||
RestartForceExitStatus=133
|
||||
SuccessExitStatus=133
|
||||
"###;
|
137
citadel-realms/src/util.rs
Normal file
137
citadel-realms/src/util.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::ffi::CString;
|
||||
use std::io::{self,Write};
|
||||
|
||||
use libc;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use Result;
|
||||
|
||||
|
||||
pub fn path_filename(path: &Path) -> &str {
|
||||
if let Some(osstr) = path.file_name() {
|
||||
if let Some(name) = osstr.to_str() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
fn is_alphanum_or_dash(c: char) -> bool {
|
||||
is_ascii(c) && (c.is_alphanumeric() || c == '-')
|
||||
}
|
||||
|
||||
fn is_ascii(c: char) -> bool {
|
||||
c as u32 <= 0x7F
|
||||
}
|
||||
|
||||
pub fn is_first_char_alphabetic(s: &str) -> bool {
|
||||
if let Some(c) = s.chars().next() {
|
||||
return is_ascii(c) && c.is_alphabetic()
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
const MAX_REALM_NAME_LEN:usize = 128;
|
||||
|
||||
/// Valid realm names:
|
||||
/// * must start with an alphabetic ascii letter character
|
||||
/// * may only contain ascii characters which are letters, numbers, or the dash '-' symbol
|
||||
/// * must not be empty or have a length exceeding 128 characters
|
||||
pub fn is_valid_realm_name(name: &str) -> bool {
|
||||
name.len() <= MAX_REALM_NAME_LEN &&
|
||||
// Also false on empty string
|
||||
is_first_char_alphabetic(name) &&
|
||||
name.chars().all(is_alphanum_or_dash)
|
||||
}
|
||||
|
||||
pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
|
||||
let cstr = CString::new(path.as_os_str().as_bytes())?;
|
||||
unsafe {
|
||||
if libc::chown(cstr.as_ptr(), uid, gid) == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_path(from: &Path, to: &Path) -> Result<()> {
|
||||
if to.exists() {
|
||||
bail!("destination path {} already exists which is not expected", to.display());
|
||||
}
|
||||
|
||||
let meta = from.metadata()?;
|
||||
|
||||
if from.is_dir() {
|
||||
fs::create_dir(to)?;
|
||||
} else {
|
||||
fs::copy(&from, &to)?;
|
||||
}
|
||||
|
||||
chown(to, meta.uid(), meta.gid())?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
pub fn copy_tree(from_base: &Path, to_base: &Path) -> Result<()> {
|
||||
for entry in WalkDir::new(from_base) {
|
||||
let path = entry?.path().to_owned();
|
||||
let to = to_base.join(path.strip_prefix(from_base)?);
|
||||
if &to != to_base {
|
||||
copy_path(&path, &to)
|
||||
.map_err(|e| format_err!("failed to copy {} to {}: {}", path.display(), to.display(), e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
use termcolor::{ColorChoice,Color,ColorSpec,WriteColor,StandardStream};
|
||||
|
||||
pub struct ColoredOutput {
|
||||
color_bright: ColorSpec,
|
||||
color_bold: ColorSpec,
|
||||
color_dim: ColorSpec,
|
||||
stream: StandardStream,
|
||||
}
|
||||
|
||||
|
||||
impl ColoredOutput {
|
||||
pub fn new() -> ColoredOutput {
|
||||
ColoredOutput::new_with_colors(Color::Rgb(0, 110, 180), Color::Rgb(100, 100, 80))
|
||||
}
|
||||
|
||||
pub fn new_with_colors(bright: Color, dim: Color) -> ColoredOutput {
|
||||
let mut out = ColoredOutput {
|
||||
color_bright: ColorSpec::new(),
|
||||
color_bold: ColorSpec::new(),
|
||||
color_dim: ColorSpec::new(),
|
||||
stream: StandardStream::stdout(ColorChoice::AlwaysAnsi),
|
||||
};
|
||||
out.color_bright.set_fg(Some(bright.clone()));
|
||||
out.color_bold.set_fg(Some(bright)).set_bold(true);
|
||||
out.color_dim.set_fg(Some(dim));
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn write(&mut self, s: &str) -> &mut Self {
|
||||
write!(&mut self.stream, "{}", s).unwrap();
|
||||
self.stream.reset().unwrap();
|
||||
self
|
||||
}
|
||||
pub fn bright(&mut self, s: &str) -> &mut Self {
|
||||
self.stream.set_color(&self.color_bright).unwrap();
|
||||
self.write(s)
|
||||
}
|
||||
pub fn bold(&mut self, s: &str) -> &mut Self {
|
||||
self.stream.set_color(&self.color_bold).unwrap();
|
||||
self.write(s)
|
||||
}
|
||||
pub fn dim(&mut self, s: &str) -> &mut Self {
|
||||
self.stream.set_color(&self.color_dim).unwrap();
|
||||
self.write(s)
|
||||
}
|
||||
|
||||
}
|
475
libcitadel/Cargo.lock
generated
Normal file
475
libcitadel/Cargo.lock
generated
Normal file
@ -0,0 +1,475 @@
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.12"
|
||||
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)",
|
||||
"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)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
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 = "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)",
|
||||
"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.22 (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"
|
||||
source = "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 = "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)",
|
||||
"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)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.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)",
|
||||
"void 1.0.2 (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 = "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 = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.82"
|
||||
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)",
|
||||
]
|
||||
|
||||
[[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"
|
||||
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.22 (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 = "toml"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
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-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 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 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 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 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 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"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
18
libcitadel/Cargo.toml
Normal file
18
libcitadel/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "libcitadel"
|
||||
version = "0.1.0"
|
||||
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
nix = "0.12.0"
|
||||
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"
|
||||
rustc-serialize = "0.3.24"
|
||||
lazy_static = "1.2.0"
|
||||
|
170
libcitadel/src/blockdev.rs
Normal file
170
libcitadel/src/blockdev.rs
Normal file
@ -0,0 +1,170 @@
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::{Read,Write,Seek,SeekFrom};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use libc;
|
||||
|
||||
use Result;
|
||||
|
||||
// IO on block devices requires 4096 byte aligned buffer
|
||||
const REQUIRED_ALIGNMENT: usize = 4096;
|
||||
const DEFAULT_ALIGNMENT: usize = REQUIRED_ALIGNMENT;
|
||||
|
||||
/// A byte buffer from which references can be acquired that have correctly
|
||||
/// aligned physical base address for block device I/O operations.
|
||||
pub struct AlignedBuffer {
|
||||
buffer: Vec<u8>,
|
||||
alignment: usize,
|
||||
size: usize,
|
||||
align_offset: usize,
|
||||
}
|
||||
|
||||
impl AlignedBuffer {
|
||||
|
||||
pub fn new(size: usize) -> AlignedBuffer {
|
||||
AlignedBuffer::new_with_alignment(size, DEFAULT_ALIGNMENT)
|
||||
}
|
||||
|
||||
pub fn from_slice(bytes: &[u8]) -> AlignedBuffer {
|
||||
AlignedBuffer::from_slice_with_alignment(bytes, DEFAULT_ALIGNMENT)
|
||||
}
|
||||
|
||||
pub fn new_with_alignment(size: usize, alignment: usize) -> AlignedBuffer {
|
||||
AlignedBuffer {
|
||||
alignment, size,
|
||||
buffer: vec![0u8; size + alignment],
|
||||
align_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_slice_with_alignment(bytes: &[u8], alignment: usize) -> AlignedBuffer {
|
||||
let mut ab = AlignedBuffer::new_with_alignment(bytes.len(), alignment);
|
||||
ab.as_mut().copy_from_slice(bytes);
|
||||
ab
|
||||
}
|
||||
|
||||
//
|
||||
// Calculates an offset into `self.buffer` array that is physically
|
||||
// located at a 4096 byte alignment boundary and returns slice at
|
||||
// this offset. `self.align_offset` is set so that access functions
|
||||
// will use the right offset.
|
||||
//
|
||||
// I/O on block devices must use 4k aligned memory:
|
||||
//
|
||||
// https://people.redhat.com/msnitzer/docs/io-limits.txt
|
||||
//
|
||||
// Or maybe just 512 byte aligned memory:
|
||||
//
|
||||
// https://www.quora.com/Why-does-O_DIRECT-require-I-O-to-be-512-byte-aligned
|
||||
//
|
||||
fn align_buffer(&mut self) {
|
||||
let addr = self.buffer.as_ptr() as usize;
|
||||
let offset = self.alignment - (addr & (self.alignment - 1));
|
||||
self.align_offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for AlignedBuffer {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
let start = self.align_offset;
|
||||
let end = start + self.size;
|
||||
&(self.buffer.as_slice())[start..end]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for AlignedBuffer {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.align_buffer();
|
||||
let start = self.align_offset;
|
||||
let end = start + self.size;
|
||||
&mut self.buffer.as_mut_slice()[start..end]
|
||||
}
|
||||
}
|
||||
|
||||
pub const SECTOR_SIZE: usize = 512;
|
||||
pub const ALIGNMENT_MASK: usize = 4095;
|
||||
|
||||
ioctl_read!(blk_getsize64, 0x12, 114, u64);
|
||||
|
||||
/// A block device which is open for reading or writing.
|
||||
pub struct BlockDev {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl BlockDev {
|
||||
/// Open a block device for read-only operations.
|
||||
pub fn open_ro<P: AsRef<Path>>(path: P) -> Result<BlockDev> {
|
||||
BlockDev::open(path.as_ref(), false)
|
||||
}
|
||||
|
||||
/// Open a block device for read-write operations.
|
||||
pub fn open_rw<P: AsRef<Path>>(path: P) -> Result<BlockDev> {
|
||||
BlockDev::open(path.as_ref(), true)
|
||||
}
|
||||
|
||||
fn open(path: &Path, write: bool) -> Result<BlockDev> {
|
||||
let mut oo = OpenOptions::new();
|
||||
oo.read(true);
|
||||
oo.custom_flags(libc::O_DIRECT | libc::O_SYNC);
|
||||
if write {
|
||||
oo.write(true);
|
||||
}
|
||||
let file = oo.open(path)
|
||||
.map_err(|e| format_err!("Failed to open block device {}: {}", path.display(), e))?;
|
||||
Ok(BlockDev{file})
|
||||
}
|
||||
|
||||
/// Returns the size of this block device in bytes.
|
||||
pub fn size(&self) -> Result<u64> {
|
||||
let mut sz = 0u64;
|
||||
unsafe {
|
||||
blk_getsize64(self.file.as_raw_fd(), &mut sz)
|
||||
.map_err(|e| format_err!("Error calling getsize ioctl on block device: {}", e))?;
|
||||
}
|
||||
Ok(sz)
|
||||
}
|
||||
|
||||
/// Return the number of 512 byte sectors on this block device.
|
||||
pub fn nsectors(&self) -> Result<usize> {
|
||||
Ok((self.size()? as usize) >> 9)
|
||||
}
|
||||
|
||||
// Validate that `buffer` address is properly aligned and that the size of the
|
||||
// buffer is multiple of sector size and that the offset and buffer size do
|
||||
// not exceed size of device. Then `seek` the device to the correct location
|
||||
// for the read or write operation.
|
||||
fn setup_io(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
|
||||
let addr = buffer.as_ptr() as usize;
|
||||
if addr & ALIGNMENT_MASK != 0 {
|
||||
bail!("block device i/o attempted with incorrectly aligned buffer: {:p}", buffer);
|
||||
}
|
||||
if buffer.len() % SECTOR_SIZE != 0 {
|
||||
bail!("buffer length {} is not a multiple of sector size", buffer.len());
|
||||
}
|
||||
let count = buffer.len() / SECTOR_SIZE;
|
||||
if offset + count > self.nsectors()? {
|
||||
bail!("sector_io({}, {}) is past end of device", offset, buffer.len());
|
||||
}
|
||||
self.file.seek(SeekFrom::Start((offset * SECTOR_SIZE) as u64))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read sectors from device at sector `offset` into `buffer`.
|
||||
/// The buffer must be a multiple of sector size (512 bytes).
|
||||
pub fn read_sectors(&mut self, offset: usize, buffer: &mut [u8]) -> Result<()> {
|
||||
self.setup_io(offset, buffer)?;
|
||||
self.file.read_exact(buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write sectors from `buffer` to device starting at sector `offset`.
|
||||
/// The buffer must be a multiple of sector size (512 bytes).
|
||||
pub fn write_sectors(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
|
||||
self.setup_io(offset, buffer)?;
|
||||
self.file.write_all(buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
247
libcitadel/src/cmdline.rs
Normal file
247
libcitadel/src/cmdline.rs
Normal file
@ -0,0 +1,247 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use {Result,PathExt};
|
||||
|
||||
lazy_static! {
|
||||
static ref CMDLINE: CommandLine = match CommandLine::load() {
|
||||
Ok(cl) => cl,
|
||||
Err(err) => {
|
||||
warn!("Failed to load kernel command line: {}", err);
|
||||
CommandLine::new()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Kernel command line parsed from /proc/cmdline into a map
|
||||
/// of Key / Value pairs. The value is optional since some
|
||||
/// variables are flags and do not have a value.
|
||||
///
|
||||
/// This class is a lazy constructed singleton.
|
||||
#[derive(Clone)]
|
||||
pub struct CommandLine {
|
||||
varmap: HashMap<String,Option<String>>,
|
||||
}
|
||||
|
||||
impl CommandLine {
|
||||
|
||||
/// Returns true if the variable `name` is present on the kernel command line.
|
||||
pub fn var_exists(name: &str) -> bool {
|
||||
CMDLINE._var_exists(name)
|
||||
}
|
||||
|
||||
/// Return a value for the variable `name` if a value is present on the kernel command line for this variable.
|
||||
/// Will return `None` if variable does not exist or if variable is present but does not have a value.
|
||||
pub fn get_value(name: &str) -> Option<&str> {
|
||||
CMDLINE._get_value(name)
|
||||
}
|
||||
|
||||
/// Return `true` if variable citadel.noverity is present on kernel command line.
|
||||
pub fn noverity() -> bool {
|
||||
CommandLine::var_exists("citadel.noverity")
|
||||
}
|
||||
|
||||
/// 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.recovery is present on kernel command line.
|
||||
pub fn recovery_mode() -> bool {
|
||||
CommandLine::var_exists("citadel.recovery")
|
||||
}
|
||||
|
||||
pub fn verbose() -> bool {
|
||||
CommandLine::var_exists("citadel.verbose")
|
||||
}
|
||||
|
||||
|
||||
fn new() -> CommandLine {
|
||||
CommandLine{ varmap: HashMap::new() }
|
||||
}
|
||||
|
||||
fn load() -> Result<CommandLine> {
|
||||
let s = Path::new("/proc/cmdline").read_as_string()?;
|
||||
let varmap = CommandLineParser::new(s).parse();
|
||||
Ok(CommandLine{varmap})
|
||||
}
|
||||
|
||||
fn _var_exists(&self, name: &str) -> bool {
|
||||
self.varmap.contains_key(name)
|
||||
}
|
||||
|
||||
fn _get_value(&self, name: &str) -> Option<&str> {
|
||||
if let Some(val) = self.varmap.get(name) {
|
||||
// 'name' exists
|
||||
if let Some(ref v) = *val {
|
||||
// has an associated value (name=value)
|
||||
return Some(v)
|
||||
}
|
||||
}
|
||||
// otherwise None
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ParseState {
|
||||
// In whitespace between options
|
||||
Whitespace,
|
||||
// In option name, preceeding '=' char
|
||||
Name(String),
|
||||
// In value after '=' char
|
||||
Value(String,String),
|
||||
// First char was a '-', expecting double '--'
|
||||
InDash,
|
||||
// In quoted value, whitespace allowed
|
||||
InQuoted(String, String),
|
||||
// Last char was closing '"' char, expect only whitespace next
|
||||
QuotedEnd(String, String),
|
||||
// Failed to parse an option, remain in state BAD until whitespace
|
||||
Bad,
|
||||
}
|
||||
|
||||
// Parser for kernel command line
|
||||
struct CommandLineParser {
|
||||
cmdline: String,
|
||||
varmap: HashMap<String, Option<String>>,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl CommandLineParser {
|
||||
fn new(cmdline: String) -> CommandLineParser {
|
||||
CommandLineParser {
|
||||
cmdline,
|
||||
varmap: HashMap::new(),
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(mut self) -> HashMap<String, Option<String>> {
|
||||
// Append a space to cause final item to be processed
|
||||
let cmdline = self.cmdline.clone() + " ";
|
||||
let mut state = ParseState::Whitespace;
|
||||
for c in cmdline.chars() {
|
||||
state = match state {
|
||||
ParseState::Whitespace => self.parse_whitespace(c),
|
||||
ParseState::Name(name) => self.parse_name(c, name),
|
||||
ParseState::Value(name, value) => self.parse_value(c, name, value),
|
||||
ParseState::InDash => self.parse_in_dash(c),
|
||||
ParseState::InQuoted(name, value) => self.parse_in_quoted(c, name, value),
|
||||
ParseState::QuotedEnd(name, value) => self.parse_quoted_end(c, name, value),
|
||||
ParseState::Bad => self.parse_bad(c),
|
||||
};
|
||||
self.pos += 1;
|
||||
}
|
||||
self.varmap
|
||||
}
|
||||
|
||||
fn parse_whitespace(&mut self, c: char) -> ParseState {
|
||||
match c {
|
||||
ch if ch.is_whitespace() => ParseState::Whitespace,
|
||||
ch if ch.is_ascii_alphanumeric() => ParseState::Name(ch.to_string()),
|
||||
_ => {
|
||||
self.unexpected_char(c, "as initial character of option name")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_name(&mut self, c: char, mut name: String) -> ParseState {
|
||||
match c {
|
||||
|
||||
'_' | '-' => {
|
||||
name.push('-');
|
||||
ParseState::Name(name)
|
||||
},
|
||||
|
||||
'=' => ParseState::Value(name, String::new()),
|
||||
|
||||
ch if ch.is_whitespace() => {
|
||||
self.varmap.insert(name, None);
|
||||
ParseState::Whitespace
|
||||
},
|
||||
|
||||
ch if ch.is_ascii_alphanumeric() || ch == '.' => {
|
||||
name.push(ch);
|
||||
ParseState::Name(name)
|
||||
},
|
||||
|
||||
_ => {
|
||||
self.unexpected_char(c, "parsing option name")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value(&mut self, c: char, name: String, mut value: String) -> ParseState {
|
||||
match c {
|
||||
|
||||
'"' if value.is_empty() => ParseState::InQuoted(name, value),
|
||||
|
||||
ch if ch.is_whitespace() => {
|
||||
self.varmap.insert(name, Some(value));
|
||||
ParseState::Whitespace
|
||||
},
|
||||
|
||||
ch if ch.is_ascii() => {
|
||||
value.push(ch);
|
||||
ParseState::Value(name, value)
|
||||
},
|
||||
|
||||
_ => {
|
||||
self.unexpected_char(c, "parsing option value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_in_dash(&mut self, c: char) -> ParseState {
|
||||
// Only supposed to be double dash, but we'll accept any number of consecutive dashes
|
||||
if c.is_whitespace() {
|
||||
ParseState::Whitespace
|
||||
} else if c == '-' {
|
||||
ParseState::InDash
|
||||
} else {
|
||||
self.unexpected_char(c, "after initial dash character")
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_in_quoted(&mut self, c: char, name: String, mut value: String) -> ParseState {
|
||||
if c == '"' {
|
||||
ParseState::QuotedEnd(name, value)
|
||||
} else {
|
||||
value.push(c);
|
||||
ParseState::InQuoted(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_quoted_end(&mut self, c: char, name: String, value: String) -> ParseState {
|
||||
if c.is_whitespace() {
|
||||
self.varmap.insert(name, Some(value));
|
||||
return ParseState::Whitespace
|
||||
}
|
||||
self.unexpected_char(c, "after closing quote character")
|
||||
}
|
||||
|
||||
fn parse_bad(&mut self, c: char) -> ParseState {
|
||||
if c.is_whitespace() {
|
||||
ParseState::Whitespace
|
||||
} else {
|
||||
ParseState::Bad
|
||||
}
|
||||
}
|
||||
|
||||
fn unexpected_char(&self, c: char, msg: &str) -> ParseState {
|
||||
warn!("Parsing kernel commandline: {}", self.cmdline);
|
||||
warn!("Unexpected char '{}' at position {} {}", c, self.pos, msg);
|
||||
ParseState::Bad
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foo() {
|
||||
let cline = CommandLine::load().unwrap();
|
||||
println!("hello");
|
||||
println!("cline: {:?}", cline.varmap);
|
||||
|
||||
}
|
||||
|
||||
|
127
libcitadel/src/config.rs
Normal file
127
libcitadel/src/config.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ed25519_dalek::{Signature,PublicKey,Keypair};
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use sha2::Sha512;
|
||||
use toml;
|
||||
|
||||
use {Result,PathExt};
|
||||
|
||||
|
||||
const DEFAULT_CONFIG_PATH: &str = "/usr/share/citadel/citadel-image.conf";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
|
||||
#[serde (rename="default-channel")]
|
||||
default_channel: Option<String>,
|
||||
|
||||
#[serde (rename="default-citadel-base")]
|
||||
default_citadel_base: Option<String>,
|
||||
|
||||
channel: HashMap<String, Channel>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
||||
pub fn load_default() -> Result<Config> {
|
||||
Config::load(DEFAULT_CONFIG_PATH)
|
||||
}
|
||||
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||
let config = match Config::from_path(path.as_ref()) {
|
||||
Ok(config) => config,
|
||||
Err(e) => bail!("Failed to load config file {}: {}", path.as_ref().display(), e),
|
||||
};
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn from_path(path: &Path) -> Result<Config> {
|
||||
let s = path.read_as_string()?;
|
||||
let mut config = toml::from_str::<Config>(&s)?;
|
||||
for (k,v) in config.channel.iter_mut() {
|
||||
v.name = k.to_string();
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
|
||||
pub fn get_default_citadel_base(&self) -> Option<PathBuf> {
|
||||
match self.default_citadel_base {
|
||||
Some(ref base) => Some(PathBuf::from(base)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_default_channel(&self) -> Option<Channel> {
|
||||
|
||||
if let Some(ref name) = self.default_channel {
|
||||
if let Some(c) = self.channel(name) {
|
||||
return Some(c);
|
||||
}
|
||||
}
|
||||
|
||||
if self.channel.len() == 1 {
|
||||
return self.channel.values().next().map(|c| c.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn channel(&self, name: &str) -> Option<Channel> {
|
||||
self.channel.get(name).map(|c| c.clone() )
|
||||
}
|
||||
|
||||
pub fn get_private_key(&self, channel: &str) -> Option<String> {
|
||||
if let Some(channel_config) = self.channel.get(channel) {
|
||||
if let Some(ref key) = channel_config.keypair {
|
||||
return Some(key.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_public_key(&self, channel: &str) -> Option<String> {
|
||||
if let Some(channel_config) = self.channel.get(channel) {
|
||||
return Some(channel_config.pubkey.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Clone)]
|
||||
pub struct Channel {
|
||||
update_server: Option<String>,
|
||||
pubkey: String,
|
||||
keypair: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn sign(&self, data: &[u8]) -> Result<Signature> {
|
||||
let keybytes = match self.keypair {
|
||||
Some(ref hex) => hex.from_hex()?,
|
||||
None => bail!("No private signing key available for channel {}", self.name),
|
||||
};
|
||||
let privkey = Keypair::from_bytes(&keybytes)?;
|
||||
let sig = privkey.sign::<Sha512>(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)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
99
libcitadel/src/disks.rs
Normal file
99
libcitadel/src/disks.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
|
||||
|
||||
use {Result,PathExt};
|
||||
|
||||
// Partition Type GUID of UEFI boot (ESP) partition
|
||||
const ESP_GUID: &str = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b";
|
||||
|
||||
///
|
||||
/// Represents a disk partition device on the system
|
||||
///
|
||||
/// A wrapper around the fields from a line in /proc/partitions
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct DiskPartition {
|
||||
path: PathBuf,
|
||||
major: u8,
|
||||
minor: u8,
|
||||
blocks: usize,
|
||||
}
|
||||
|
||||
impl DiskPartition {
|
||||
/// Return list of all UEFI ESP partitions on the system as a `Vec<DiskPartition>`
|
||||
pub fn boot_partitions() -> Result<Vec<DiskPartition>> {
|
||||
let mut v = Vec::new();
|
||||
for line in Path::new("/proc/partitions").read_as_lines()?.iter().skip(2) {
|
||||
let part = DiskPartition::from_proc_line(&line)
|
||||
.map_err(|e| format_err!("Failed to parse line '{}': {}", line, e))?;
|
||||
if part.is_esp() {
|
||||
v.push(part);
|
||||
}
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
// Parse a single line from /proc/partitions
|
||||
//
|
||||
// Example line:
|
||||
//
|
||||
// 8 1 523264 sda1
|
||||
//
|
||||
fn from_proc_line(line: &str) -> Result<DiskPartition> {
|
||||
let v = line.split_whitespace().collect::<Vec<_>>();
|
||||
if v.len() != 4 {
|
||||
bail!("could not parse");
|
||||
}
|
||||
Ok(DiskPartition::from_line_components(
|
||||
v[0].parse::<u8>()?, // Major
|
||||
v[1].parse::<u8>()?, // Minor
|
||||
v[2].parse::<usize>()?, // number of blocks
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 umount(&self) -> bool {
|
||||
match self.path.umount() {
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
false
|
||||
},
|
||||
Ok(()) => true,
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
353
libcitadel/src/header.rs
Normal file
353
libcitadel/src/header.rs
Normal file
@ -0,0 +1,353 @@
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::{Write,Read};
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use toml;
|
||||
|
||||
use {Channel,Config,Result,BlockDev};
|
||||
use blockdev::AlignedBuffer;
|
||||
|
||||
/// Expected magic value in header
|
||||
const MAGIC: &[u8] = b"SGOS";
|
||||
|
||||
/// Offset into header of the start of the metainfo document
|
||||
const METAINFO_OFFSET: usize = 8;
|
||||
|
||||
/// Signature is 64 bytes long
|
||||
const SIGNATURE_LENGTH: usize = 64;
|
||||
|
||||
/// Maximum amount of space in block for metainfo document
|
||||
const MAX_METAINFO_LEN: usize = (ImageHeader::HEADER_SIZE - (METAINFO_OFFSET + SIGNATURE_LENGTH));
|
||||
|
||||
fn is_valid_status_code(code: u8) -> bool {
|
||||
code <= ImageHeader::STATUS_BAD_META
|
||||
}
|
||||
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// magic : Must match ascii bytes 'SGOS' for the header to be considered valid
|
||||
///
|
||||
/// status : One of the `STATUS` constants defined below
|
||||
///
|
||||
/// flags : May contain 'FLAG' values defined below.
|
||||
///
|
||||
/// length : The size of the metainfo field in bytes as a 16-bit Big Endian value
|
||||
///
|
||||
/// metainfo : A utf-8 encoded TOML document with various fields describing the image
|
||||
///
|
||||
/// signature : ed25519 signature over the bytes of the metainfo field
|
||||
///
|
||||
|
||||
#[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"];
|
||||
|
||||
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.
|
||||
|
||||
pub const STATUS_INVALID : u8 = 0; // Set on partition before writing a new rootfs disk image
|
||||
pub const STATUS_NEW : u8 = 1; // Set on partition after write of new rootfs disk image completes successfully
|
||||
pub const STATUS_TRY_BOOT : u8 = 2; // Set on boot selected partition if in `STATUS_NEW` state.
|
||||
pub const STATUS_GOOD : u8 = 3; // Set on boot when a `STATUS_TRY_BOOT` partition successfully launches desktop
|
||||
pub const STATUS_FAILED : u8 = 4; // Set on boot for any partition in state `STATUS_TRY_BOOT`
|
||||
pub const STATUS_BAD_SIG : u8 = 5; // Set on boot selected partition when signature fails to verify
|
||||
pub const STATUS_BAD_META : u8 = 6; // Set on partition when metainfo cannot be parsed
|
||||
|
||||
/// 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));
|
||||
header.write_bytes(0, MAGIC);
|
||||
header
|
||||
}
|
||||
|
||||
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> {
|
||||
let mut v = vec![0u8; ImageHeader::HEADER_SIZE];
|
||||
r.read_exact(&mut v)?;
|
||||
Ok(ImageHeader(RefCell::new(v)))
|
||||
}
|
||||
|
||||
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);
|
||||
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()));
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
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);
|
||||
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> {
|
||||
let mlen = self.metainfo_len();
|
||||
if mlen == 0 || mlen > MAX_METAINFO_LEN {
|
||||
bail!("Invalid metainfo-len field: {}", mlen);
|
||||
}
|
||||
let mbytes = self.metainfo_bytes();
|
||||
let mut metainfo = MetaInfo::new(mbytes);
|
||||
metainfo.parse_toml()?;
|
||||
metainfo.verify(config, &self.signature())?;
|
||||
Ok(metainfo)
|
||||
}
|
||||
|
||||
pub fn is_magic_valid(&self) -> bool {
|
||||
self.read_bytes(0, 4) == MAGIC
|
||||
}
|
||||
|
||||
pub fn status(&self) -> u8 {
|
||||
self.read_u8(4)
|
||||
}
|
||||
|
||||
pub fn set_status(&self, status: u8) {
|
||||
self.write_u8(4, status);
|
||||
}
|
||||
|
||||
pub fn status_code_label(&self) -> String {
|
||||
let code = self.status();
|
||||
|
||||
if is_valid_status_code(code) {
|
||||
CODE_TO_LABEL[code as usize].to_string()
|
||||
} else {
|
||||
format!("Invalid status code: {}", code)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u8 { self.read_u8(5) }
|
||||
|
||||
pub fn has_flag(&self, flag: u8) -> bool {
|
||||
(self.flags() & flag) == flag
|
||||
}
|
||||
|
||||
/// Return `true` if flag value changed
|
||||
pub fn set_flag(&self, flag: u8) -> bool {
|
||||
self.change_flag(flag, true)
|
||||
}
|
||||
|
||||
pub fn clear_flag(&self, flag: u8) -> bool {
|
||||
self.change_flag(flag, false)
|
||||
}
|
||||
|
||||
fn change_flag(&self, flag: u8, set: bool) -> bool {
|
||||
let old = self.flags();
|
||||
let new = if set { old | flag } else { old & !flag };
|
||||
self.write_u8(5, new);
|
||||
old == new
|
||||
}
|
||||
|
||||
pub fn metainfo_len(&self) -> usize {
|
||||
self.read_u16(6) as usize
|
||||
}
|
||||
|
||||
pub fn set_metainfo_len(&self, len: usize) {
|
||||
self.write_u16(6, len as u16);
|
||||
}
|
||||
|
||||
pub fn set_metainfo_bytes(&self, bytes: &[u8]) {
|
||||
self.set_metainfo_len(bytes.len());
|
||||
self.write_bytes(8, bytes);
|
||||
}
|
||||
|
||||
pub fn metainfo_bytes(&self) -> Vec<u8> {
|
||||
let mlen = self.metainfo_len();
|
||||
assert!(mlen > 0 && mlen < MAX_METAINFO_LEN);
|
||||
self.read_bytes(METAINFO_OFFSET, mlen)
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> Vec<u8> {
|
||||
let mlen = self.metainfo_len();
|
||||
assert!(mlen > 0 && mlen < MAX_METAINFO_LEN);
|
||||
self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH)
|
||||
}
|
||||
|
||||
pub fn sign_metainfo(&self, channel: &Channel) -> Result<()> {
|
||||
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());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_header<W: Write>(&self, mut writer: W) -> Result<()> {
|
||||
writer.write_all(&self.0.borrow())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
for b in &mut self.0.borrow_mut()[..] {
|
||||
*b = 0;
|
||||
}
|
||||
self.write_bytes(0, MAGIC);
|
||||
}
|
||||
|
||||
fn read_u8(&self, idx: usize) -> u8 {
|
||||
self.0.borrow()[idx]
|
||||
}
|
||||
|
||||
fn read_u16(&self, idx: usize) -> u16 {
|
||||
let hi = self.read_u8(idx) as u16;
|
||||
let lo = self.read_u8(idx + 1) as u16;
|
||||
(hi << 8) | lo
|
||||
}
|
||||
|
||||
fn write_u8(&self, idx: usize, val: u8) {
|
||||
self.0.borrow_mut()[idx] = val;
|
||||
}
|
||||
|
||||
fn write_u16(&self, idx: usize, val: u16) {
|
||||
let hi = (val >> 8) as u8;
|
||||
let lo = val as u8;
|
||||
self.write_u8(idx, hi);
|
||||
self.write_u8(idx + 1, lo);
|
||||
}
|
||||
|
||||
fn write_bytes(&self, offset: usize, data: &[u8]) {
|
||||
self.0.borrow_mut()[offset..offset+data.len()].copy_from_slice(data)
|
||||
}
|
||||
|
||||
fn read_bytes(&self, offset: usize, len: usize) -> Vec<u8> {
|
||||
Vec::from(&self.0.borrow()[offset..offset+len])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MetaInfo {
|
||||
bytes: Vec<u8>,
|
||||
is_parsed: bool,
|
||||
is_valid: bool,
|
||||
toml: Option<MetaInfoToml>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize,Serialize,Clone)]
|
||||
struct MetaInfoToml {
|
||||
channel: String,
|
||||
version: u32,
|
||||
#[serde(rename="base-version")]
|
||||
base_version: Option<u32>,
|
||||
date: Option<String>,
|
||||
gitrev: Option<String>,
|
||||
nblocks: u32,
|
||||
shasum: String,
|
||||
#[serde(rename="verity-salt")]
|
||||
verity_salt: String,
|
||||
#[serde(rename="verity-root")]
|
||||
verity_root: String,
|
||||
}
|
||||
|
||||
impl MetaInfo {
|
||||
fn new(bytes: Vec<u8>) -> MetaInfo {
|
||||
MetaInfo {
|
||||
bytes,
|
||||
is_parsed: false,
|
||||
is_valid: false,
|
||||
toml: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.is_valid
|
||||
}
|
||||
|
||||
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")?;
|
||||
self.toml = Some(toml);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify(&self, config: &Config, signature: &[u8]) -> Result<()> {
|
||||
let channel = match config.channel(self.channel()) {
|
||||
Some(channel) => channel,
|
||||
None => bail!("Channel '{}' not found in config file", self.channel()),
|
||||
};
|
||||
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 channel(&self) -> &str {
|
||||
self.toml().channel.as_str()
|
||||
}
|
||||
|
||||
pub fn version(&self) -> u32 {
|
||||
self.toml().version
|
||||
}
|
||||
|
||||
pub fn date(&self) -> Option<&str> {
|
||||
self.toml().date.as_ref().map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn gitrev(&self) -> Option<&str> {
|
||||
self.toml().gitrev.as_ref().map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn nblocks(&self) -> usize {
|
||||
self.toml().nblocks as usize
|
||||
}
|
||||
|
||||
pub fn shasum(&self) -> &str {
|
||||
&self.toml().shasum
|
||||
}
|
||||
|
||||
pub fn verity_root(&self) -> &str {
|
||||
&self.toml().verity_root
|
||||
}
|
||||
|
||||
pub fn verity_salt(&self) -> &str {
|
||||
&self.toml().verity_salt
|
||||
}
|
||||
}
|
111
libcitadel/src/keys.rs
Normal file
111
libcitadel/src/keys.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use Result;
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha512;
|
||||
use ed25519_dalek::{self,PublicKey,Keypair,Signature};
|
||||
use rustc_serialize::hex::{ToHex,FromHex};
|
||||
|
||||
pub const SIGNATURE_LENGTH: usize = ed25519_dalek::SIGNATURE_LENGTH;
|
||||
|
||||
///
|
||||
/// Keys for signing or verifying signatures. Small convenience
|
||||
/// wrapper around `ed25519_dalek`.
|
||||
///
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pubkey(&self) -> &PublicKey {
|
||||
match *self {
|
||||
KEYPAIR(ref keypair) => &keypair.public,
|
||||
PUBLIC(ref public) => &public,
|
||||
}
|
||||
}
|
||||
}
|
70
libcitadel/src/lib.rs
Normal file
70
libcitadel/src/lib.rs
Normal file
@ -0,0 +1,70 @@
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate nix;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($e:expr) => { if $crate::verbose() { println!("[+] {}", $e);} };
|
||||
($fmt:expr, $($arg:tt)+) => { if $crate::verbose() { println!("[+] {}", format!($fmt, $($arg)+));} };
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($e:expr) => { println!("WARNING: {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("WARNING: {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! notify {
|
||||
($e:expr) => { println!("[+] {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("[+] {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
extern crate libc;
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
extern crate ed25519_dalek;
|
||||
extern crate sha2;
|
||||
extern crate rand;
|
||||
extern crate rustc_serialize;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::result;
|
||||
|
||||
use failure::Error;
|
||||
|
||||
thread_local! {
|
||||
pub static VERBOSE: RefCell<bool> = RefCell::new(false);
|
||||
}
|
||||
|
||||
pub fn verbose() -> bool {
|
||||
VERBOSE.with(|f| { *f.borrow() })
|
||||
}
|
||||
|
||||
pub fn set_verbose(val: bool) {
|
||||
VERBOSE.with(|f| { *f.borrow_mut() = val });
|
||||
}
|
||||
|
||||
mod blockdev;
|
||||
mod config;
|
||||
mod keys;
|
||||
mod disks;
|
||||
mod cmdline;
|
||||
mod header;
|
||||
mod partition;
|
||||
mod resource;
|
||||
mod path_ext;
|
||||
|
||||
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 type Result<T> = result::Result<T,Error>;
|
||||
|
||||
pub const BLOCK_SIZE: usize = 4096;
|
163
libcitadel/src/partition.rs
Normal file
163
libcitadel/src/partition.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs;
|
||||
use {Config,Result,ImageHeader,MetaInfo,PathExt};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Partition {
|
||||
path: PathBuf,
|
||||
hinfo: Option<HeaderInfo>,
|
||||
is_mounted: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HeaderInfo {
|
||||
header: ImageHeader,
|
||||
metainfo: MetaInfo,
|
||||
}
|
||||
|
||||
impl Partition {
|
||||
pub fn rootfs_partitions(config: &Config) -> Result<Vec<Partition>> {
|
||||
let mut v = Vec::new();
|
||||
for path in rootfs_partition_paths()? {
|
||||
let partition = Partition::load(&path, config)?;
|
||||
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)?;
|
||||
Ok(Partition::new(dev, header, is_mounted))
|
||||
}
|
||||
|
||||
fn load_header(dev: &Path, config: &Config) -> Result<Option<HeaderInfo>> {
|
||||
let header = ImageHeader::from_partition(dev)?;
|
||||
if !header.is_magic_valid() {
|
||||
return Ok(None);
|
||||
}
|
||||
let metainfo = match header.verified_metainfo(config) {
|
||||
Ok(metainfo) => metainfo,
|
||||
Err(e) => {
|
||||
warn!("Reading partition {}: {}", dev.display(), e);
|
||||
return Ok(None);
|
||||
},
|
||||
};
|
||||
Ok(Some(HeaderInfo {
|
||||
header, metainfo
|
||||
}))
|
||||
}
|
||||
|
||||
fn new(path: &Path, hinfo: Option<HeaderInfo>, is_mounted: bool) -> Partition {
|
||||
Partition {
|
||||
path: path.to_owned(),
|
||||
hinfo, is_mounted,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn is_mounted(&self) -> bool {
|
||||
self.is_mounted
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.hinfo.is_some()
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &ImageHeader {
|
||||
assert!(self.is_initialized());
|
||||
&self.hinfo.as_ref().unwrap().header
|
||||
}
|
||||
|
||||
fn header_mut(&mut self) -> &mut ImageHeader {
|
||||
assert!(self.is_initialized());
|
||||
&mut self.hinfo.as_mut().unwrap().header
|
||||
}
|
||||
|
||||
pub fn metainfo(&self) -> &MetaInfo {
|
||||
assert!(self.is_initialized());
|
||||
&self.hinfo.as_ref().unwrap().metainfo
|
||||
}
|
||||
|
||||
pub fn is_new(&self) -> bool {
|
||||
self.header().status() == ImageHeader::STATUS_NEW
|
||||
}
|
||||
|
||||
pub fn is_good(&self) -> bool {
|
||||
self.header().status() == ImageHeader::STATUS_GOOD
|
||||
}
|
||||
|
||||
pub fn is_preferred(&self) -> bool {
|
||||
self.header().has_flag(ImageHeader::FLAG_PREFER_BOOT)
|
||||
}
|
||||
|
||||
pub fn write_status(&mut self, status: u8) -> Result<()> {
|
||||
self.header_mut().set_status(status);
|
||||
self.header().write_partition(&self.path)
|
||||
}
|
||||
|
||||
/// Called at boot to perform various checks and possibly
|
||||
/// update the status field to an error state.
|
||||
///
|
||||
/// Mark `STATUS_TRY_BOOT` partition as `STATUS_FAILED`.
|
||||
///
|
||||
/// If metainfo cannot be parsed, mark as `STATUS_BAD_META`.
|
||||
///
|
||||
/// Verify metainfo signature and mark `STATUS_BAD_SIG` if
|
||||
/// signature verification fails.
|
||||
///
|
||||
pub fn boot_scan(&mut self, config: &Config) -> Result<()> {
|
||||
if !self.is_initialized() {
|
||||
return Ok(())
|
||||
}
|
||||
if self.header().status() == ImageHeader::STATUS_TRY_BOOT {
|
||||
self.write_status(ImageHeader::STATUS_FAILED)?;
|
||||
}
|
||||
// XXX verify signature
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn rootfs_partition_paths() -> Result<Vec<PathBuf>> {
|
||||
let mut rootfs_paths = Vec::new();
|
||||
for dent in fs::read_dir("/dev/mapper")? {
|
||||
let path = dent?.path();
|
||||
if is_path_rootfs(&path) {
|
||||
rootfs_paths.push(path);
|
||||
}
|
||||
}
|
||||
Ok(rootfs_paths)
|
||||
}
|
||||
|
||||
fn is_path_rootfs(path: &Path) -> bool {
|
||||
path_filename(path).starts_with("citadel-rootfs")
|
||||
}
|
||||
|
||||
fn path_filename(path: &Path) -> &str {
|
||||
if let Some(osstr) = path.file_name() {
|
||||
if let Some(name) = osstr.to_str() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
///
|
||||
/// 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)
|
||||
}
|
354
libcitadel/src/path_ext.rs
Normal file
354
libcitadel/src/path_ext.rs
Normal file
@ -0,0 +1,354 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
333
libcitadel/src/resource.rs
Normal file
333
libcitadel/src/resource.rs
Normal file
@ -0,0 +1,333 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::{self,File};
|
||||
use std::io::{self,Read};
|
||||
|
||||
use disks::DiskPartition;
|
||||
use Result;
|
||||
use CommandLine;
|
||||
use PathExt;
|
||||
use ImageHeader;
|
||||
use Config;
|
||||
use MetaInfo;
|
||||
|
||||
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
|
||||
/// loop mounted, optionally secured with dm-verity. The root directory
|
||||
/// of the mounted image may contain a file called `manifest` which
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
pub struct ResourceImage {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
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.
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if img.search_boot_partitions() {
|
||||
Ok(img)
|
||||
} else {
|
||||
Err(format_err!("Failed to find resource image: {}", name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Locate and return a rootfs resource image.
|
||||
/// Only EFI boot partitions will be searched.
|
||||
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)
|
||||
} else {
|
||||
Err(format_err!("Failed to find rootfs resource image"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return path to the resource image file.
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn new(name: &str) -> ResourceImage {
|
||||
ResourceImage {
|
||||
name: name.to_owned(),
|
||||
path: PathBuf::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mount(&mut self, config: &Config) -> Result<()> {
|
||||
if CommandLine::noverity() {
|
||||
self.mount_noverity()?;
|
||||
} else {
|
||||
self.mount_verity(config)?;
|
||||
}
|
||||
|
||||
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)?;
|
||||
}
|
||||
|
||||
let devname = format!("verity-{}", self.name);
|
||||
|
||||
self.path.verity_setup(ImageHeader::HEADER_SIZE, metainfo.nblocks(), metainfo.verity_root(), &devname)?;
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Return the path at which to mount this resource image.
|
||||
fn mount_path(&self) -> PathBuf {
|
||||
PathBuf::from(format!("{}/{}.mountpoint", RUN_DIRECTORY, self.name))
|
||||
}
|
||||
|
||||
// Read and process a manifest file in the root directory of a mounted resource image.
|
||||
fn process_manifest_file(&self) -> Result<()> {
|
||||
info!("Processing manifest file for {}", self.path.display());
|
||||
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()? {
|
||||
if let Err(e) = self.process_manifest_line(&line) {
|
||||
warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Process a single line from the resource image manifest file.
|
||||
// Each line describes a bind mount from the resource image root to the system root fs.
|
||||
// The line may contain either a single path or a pair of source and target paths separated by the colon (':') character.
|
||||
// If no colon character is present then the source and target paths are the same.
|
||||
// The source path from the mounted resource image will be bind mounted to the target path on the system rootfs.
|
||||
fn process_manifest_line(&self, line: &str) -> Result<()> {
|
||||
let line = line.trim_left_matches('/');
|
||||
|
||||
let (path_from, path_to) = if line.contains(":") {
|
||||
let v = line.split(":").collect::<Vec<_>>();
|
||||
if v.len() != 2 {
|
||||
bail!("badly formed line '{}'", line);
|
||||
}
|
||||
(v[0], v[1].trim_left_matches('/'))
|
||||
} else {
|
||||
(line, line)
|
||||
};
|
||||
|
||||
let from = self.mount_path().join(path_from);
|
||||
let to = Path::new("/sysroot").join(path_to);
|
||||
|
||||
info!("Bind mounting {} to {} from manifest", from.display(), to.display());
|
||||
|
||||
from.bind_mount(&to)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
let path = Path::new("/dev/mapper/citadel-storage");
|
||||
if !path.exists() {
|
||||
return 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user