diff --git a/.gitignore b/.gitignore index 53eaa21..c8f7745 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/target +**/target **/*.rs.bk diff --git a/citadel-desktopd/Cargo.lock b/citadel-desktopd/Cargo.lock new file mode 100644 index 0000000..f948c68 --- /dev/null +++ b/citadel-desktopd/Cargo.lock @@ -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" diff --git a/citadel-desktopd/Cargo.toml b/citadel-desktopd/Cargo.toml new file mode 100644 index 0000000..6f5bc62 --- /dev/null +++ b/citadel-desktopd/Cargo.toml @@ -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" diff --git a/citadel-desktopd/conf/citadel-desktopd.conf b/citadel-desktopd/conf/citadel-desktopd.conf new file mode 100644 index 0000000..1e7cc02 --- /dev/null +++ b/citadel-desktopd/conf/citadel-desktopd.conf @@ -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" diff --git a/citadel-desktopd/conf/citadel-desktopd.service b/citadel-desktopd/conf/citadel-desktopd.service new file mode 100644 index 0000000..a367e4e --- /dev/null +++ b/citadel-desktopd/conf/citadel-desktopd.service @@ -0,0 +1,5 @@ +[Unit] +Description=Desktop Integration Manager + +[Service] +ExecStart=/usr/libexec/citadel-desktopd /usr/share/citadel/citadel-desktopd.conf diff --git a/citadel-desktopd/src/config.rs b/citadel-desktopd/src/config.rs new file mode 100644 index 0000000..5760e9d --- /dev/null +++ b/citadel-desktopd/src/config.rs @@ -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, +} + + +impl Config { + pub fn from_path>(path: P) -> Result { + 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 { + self.source_paths.as_ref() + } +} + +#[derive(Deserialize)] +struct ConfigToml { + options: Option, + sources: Option>, +} + +#[derive(Deserialize)] +struct Options { + exec_prefix: Option, + target_directory: Option, +} + +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, +} + +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 { + let mut f = File::open(path)?; + let mut buffer = String::new(); + f.read_to_string(&mut buffer)?; + Ok(buffer) +} + +impl ConfigToml { + fn from_path>(path: P) -> Result { + let s = load_as_string(path.as_ref())?; + let config = toml::from_str::(&s)?; + Ok(config) + } + + fn options(&self) -> Result<&Options> { + self.options.as_ref() + .ok_or(format_err!("missing '[options]' section")) + } + + fn sources(&self) -> Result> { + match self.sources { + Some(ref srcs) => Ok(srcs.clone()), + None => Err(format_err!("missing '[[sources]]' section(s)")), + } + } + + fn to_config(&self) -> Result { + 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, + }) + } +} diff --git a/citadel-desktopd/src/desktop.rs b/citadel-desktopd/src/desktop.rs new file mode 100644 index 0000000..542c6a5 --- /dev/null +++ b/citadel-desktopd/src/desktop.rs @@ -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, + // map from key of key/value pair to index of line in lines vector + main_map: HashMap, + // map from group name to map of key/value -> index + groups: HashMap>, +} + + +impl DesktopFile { + + pub fn write_to_dir>(&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(&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(&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(()) + } +} diff --git a/citadel-desktopd/src/desktop_file_sync.rs b/citadel-desktopd/src/desktop_file_sync.rs new file mode 100644 index 0000000..573f623 --- /dev/null +++ b/citadel-desktopd/src/desktop_file_sync.rs @@ -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")), + } +} + diff --git a/citadel-desktopd/src/main.rs b/citadel-desktopd/src/main.rs new file mode 100644 index 0000000..8f4c252 --- /dev/null +++ b/citadel-desktopd/src/main.rs @@ -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 = result::Result; + + +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)); + } + +} diff --git a/citadel-desktopd/src/monitor.rs b/citadel-desktopd/src/monitor.rs new file mode 100644 index 0000000..d71641f --- /dev/null +++ b/citadel-desktopd/src/monitor.rs @@ -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>, + worker_handle: Option, +} + +impl DirectoryMonitor { + pub fn new(handler: Arc>) -> 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, + inotify: Inotify, + exit_flag: Arc, + watch_paths: Vec, + handler: Arc>, +} + + +impl MonitorWorker { + fn start_worker(watch_paths: Vec, handler: Arc>) -> 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, exit_flag: Arc, handler: Arc>) -> Result { + 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>> { + 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 { + 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, +} + +impl WorkerHandle { + fn new(join_handle: JoinHandle<()>, exit_flag: Arc) -> 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 +} \ No newline at end of file diff --git a/citadel-desktopd/src/parser.rs b/citadel-desktopd/src/parser.rs new file mode 100644 index 0000000..d8f8633 --- /dev/null +++ b/citadel-desktopd/src/parser.rs @@ -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, + in_ignored_group: bool, + known_actions: HashSet, +} + + +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>(path: P, exec_prefix: &str) -> Result { + 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(mut r: T, filename: &str, exec_prefix: &str) -> Result { + 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 { + 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 { + if let Some(line) = LineParser::new(s)._parse() { + if validate_line(&line) { + return Some(line) + } + } + None + } + + fn first(&self) -> Option { + self.s.chars().next() + } + + fn last(&self) -> Option { + self.s.chars().next_back() + } + + fn _parse(&mut self) -> Option { + 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 { + 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 { + 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)); + } +} diff --git a/citadel-mkimage/Cargo.lock b/citadel-mkimage/Cargo.lock index a2d9cc5..26025b1 100644 --- a/citadel-mkimage/Cargo.lock +++ b/citadel-mkimage/Cargo.lock @@ -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" diff --git a/citadel-mkimage/Cargo.toml b/citadel-mkimage/Cargo.toml index c7e9d49..cb29aea 100644 --- a/citadel-mkimage/Cargo.toml +++ b/citadel-mkimage/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Bruce Leidl "] homepage = "https://github.com/subgraph/citadel" [dependencies] +libcitadel = { path = "../libcitadel" } clap = "2.32.0" failure = "0.1.3" serde_derive = "1.0.82" diff --git a/citadel-mkimage/src/build.rs b/citadel-mkimage/src/build.rs index ab3a70a..7b5e453 100644 --- a/citadel-mkimage/src/build.rs +++ b/citadel-mkimage/src/build.rs @@ -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; diff --git a/citadel-mkimage/src/config.rs b/citadel-mkimage/src/config.rs index 6e05929..b1f9160 100644 --- a/citadel-mkimage/src/config.rs +++ b/citadel-mkimage/src/config.rs @@ -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")] diff --git a/citadel-mkimage/src/main.rs b/citadel-mkimage/src/main.rs index 27307b9..472f27b 100644 --- a/citadel-mkimage/src/main.rs +++ b/citadel-mkimage/src/main.rs @@ -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 = 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 = result::Result; 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 { - 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 { - let v = arg_matches.value_of("version") - .unwrap_or("0") - .parse::() - .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) -} -*/ diff --git a/citadel-mkimage/src/util.rs b/citadel-mkimage/src/util.rs index cd0cb61..9c5e260 100644 --- a/citadel-mkimage/src/util.rs +++ b/citadel-mkimage/src/util.rs @@ -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>(path: P) -> Result { diff --git a/citadel-mount/Cargo.lock b/citadel-mount/Cargo.lock new file mode 100644 index 0000000..99f9e31 --- /dev/null +++ b/citadel-mount/Cargo.lock @@ -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" diff --git a/citadel-mount/Cargo.toml b/citadel-mount/Cargo.toml new file mode 100644 index 0000000..b8e2cdf --- /dev/null +++ b/citadel-mount/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "citadel-mount" +version = "0.1.0" +authors = ["Bruce Leidl "] +homepage = "http://github.com/subgraph/citadel" + +[dependencies] +libcitadel = { path = "../libcitadel" } +libc = "0.2" +failure = "0.1.3" diff --git a/citadel-mount/src/boot_select.rs b/citadel-mount/src/boot_select.rs new file mode 100644 index 0000000..c7a3b3d --- /dev/null +++ b/citadel-mount/src/boot_select.rs @@ -0,0 +1,142 @@ + +use libcitadel::{Config,Partition,Result,ImageHeader}; + +pub struct BootSelection { + partitions: Vec, +} + +impl BootSelection { + pub fn choose_install_partition(config: &Config) -> Result { + 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 { + 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 { + 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(&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 +} diff --git a/citadel-mount/src/main.rs b/citadel-mount/src/main.rs new file mode 100644 index 0000000..21d6873 --- /dev/null +++ b/citadel-mount/src/main.rs @@ -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::>(); + 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(()) +} diff --git a/citadel-mount/src/rootfs.rs b/citadel-mount/src/rootfs.rs new file mode 100644 index 0000000..677aba9 --- /dev/null +++ b/citadel-mount/src/rootfs.rs @@ -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(()) + } +} diff --git a/citadel-mount/src/uname.rs b/citadel-mount/src/uname.rs new file mode 100644 index 0000000..1239a00 --- /dev/null +++ b/citadel-mount/src/uname.rs @@ -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) + } +} + diff --git a/citadel-realms/Cargo.lock b/citadel-realms/Cargo.lock new file mode 100644 index 0000000..2ee093f --- /dev/null +++ b/citadel-realms/Cargo.lock @@ -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" diff --git a/citadel-realms/Cargo.toml b/citadel-realms/Cargo.toml new file mode 100644 index 0000000..2dd374c --- /dev/null +++ b/citadel-realms/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "citadel-realms" +version = "0.1.0" +authors = ["Bruce Leidl "] +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" diff --git a/citadel-realms/src/appimg.rs b/citadel-realms/src/appimg.rs new file mode 100644 index 0000000..0a93e1a --- /dev/null +++ b/citadel-realms/src/appimg.rs @@ -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(()) +} + diff --git a/citadel-realms/src/config.rs b/citadel-realms/src/config.rs new file mode 100644 index 0000000..6bcffff --- /dev/null +++ b/citadel-realms/src/config.rs @@ -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 { + if path.exists() { + let s = load_as_string(&path)?; + let config = toml::from_str::(&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 { + let mut f = File::open(path)?; + let mut buffer = String::new(); + f.read_to_string(&mut buffer)?; + Ok(buffer) +} diff --git a/citadel-realms/src/main.rs b/citadel-realms/src/main.rs new file mode 100644 index 0000000..a72bdb5 --- /dev/null +++ b/citadel-realms/src/main.rs @@ -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 = result::Result; + +thread_local! { + pub static VERBOSE: RefCell = 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 ' 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() +} diff --git a/citadel-realms/src/manager.rs b/citadel-realms/src/manager.rs new file mode 100644 index 0000000..867f074 --- /dev/null +++ b/citadel-realms/src/manager.rs @@ -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, + + /// Sorted for 'list' + realm_list: Vec, + + /// track status of 'current' and 'default' symlinks + symlinks: Rc>, + + /// finds free ip addresses to use + network: Rc>, + + /// interface to systemd + systemd: Systemd, +} + + +impl RealmManager { + fn new() -> Result { + 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>> { + let mut network = NetworkConfig::new(); + network.add_bridge("clear", "172.17.0.0/24")?; + Ok(Rc::new(RefCell::new(network))) + } + + pub fn load() -> Result { + 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 { + self.symlinks.borrow().current() + } + + pub fn default_realm_name(&self) -> Option { + 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_realmResult<()>>(&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_realmResult<()>>(&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> { + 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 { + 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 { + 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() + } +} diff --git a/citadel-realms/src/network.rs b/citadel-realms/src/network.rs new file mode 100644 index 0000000..ab8a35c --- /dev/null +++ b/citadel-realms/src/network.rs @@ -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, +} + +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 { + 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 { + 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 { + 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, + allocations: HashMap, +} + +impl BridgeAllocator { + pub fn for_bridge(bridge: &str, network: &str) -> Result { + 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::()?; + + 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 { + 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 { + 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::()?; + 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(()) + } +} diff --git a/citadel-realms/src/realm.rs b/citadel-realms/src/realm.rs new file mode 100644 index 0000000..28cea1e --- /dev/null +++ b/citadel-realms/src/realm.rs @@ -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, + + /// 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>, +} + +impl Realm { + pub fn new(name: &str, symlinks: Rc>, network: Rc>) -> Result { + 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 { + 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 { + 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, + default_name: Option, +} + +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 { + self.current_name.clone() + } + + pub fn default(&self) -> Option { + 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> { + 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 { + 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(()) + } + +} + + diff --git a/citadel-realms/src/systemd.rs b/citadel-realms/src/systemd.rs new file mode 100644 index 0000000..79ae428 --- /dev/null +++ b/citadel-realms/src/systemd.rs @@ -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>, +} + +impl Systemd { + + pub fn new(network: Rc>) -> Systemd { + Systemd { network } + } + + pub fn realm_is_active(&self, realm: &Realm) -> Result { + 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 { + self.systemctl_restart(DESKTOPD_SERVICE) + } + pub fn stop_desktopd(&self) -> Result { + 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 { + 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 { + self.run_systemctl("restart", name) + } + + fn systemctl_start(&self, name: &str) -> Result { + self.run_systemctl("start", name) + } + + fn systemctl_stop(&self, name: &str) -> Result { + self.run_systemctl("stop", name) + } + + fn run_systemctl(&self, op: &str, name: &str) -> Result { + 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 { + 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 { + 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 { + 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 +"###; diff --git a/citadel-realms/src/util.rs b/citadel-realms/src/util.rs new file mode 100644 index 0000000..6b34c3a --- /dev/null +++ b/citadel-realms/src/util.rs @@ -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) + } + +} diff --git a/libcitadel/Cargo.lock b/libcitadel/Cargo.lock new file mode 100644 index 0000000..887ea64 --- /dev/null +++ b/libcitadel/Cargo.lock @@ -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" diff --git a/libcitadel/Cargo.toml b/libcitadel/Cargo.toml new file mode 100644 index 0000000..73ebab6 --- /dev/null +++ b/libcitadel/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "libcitadel" +version = "0.1.0" +authors = ["Bruce Leidl "] + +[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" + diff --git a/libcitadel/src/blockdev.rs b/libcitadel/src/blockdev.rs new file mode 100644 index 0000000..29a2df1 --- /dev/null +++ b/libcitadel/src/blockdev.rs @@ -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, + 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>(path: P) -> Result { + BlockDev::open(path.as_ref(), false) + } + + /// Open a block device for read-write operations. + pub fn open_rw>(path: P) -> Result { + BlockDev::open(path.as_ref(), true) + } + + fn open(path: &Path, write: bool) -> Result { + 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 { + 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 { + 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(()) + } + +} diff --git a/libcitadel/src/cmdline.rs b/libcitadel/src/cmdline.rs new file mode 100644 index 0000000..9af4209 --- /dev/null +++ b/libcitadel/src/cmdline.rs @@ -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>, +} + +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 { + 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>, + pos: usize, +} + +impl CommandLineParser { + fn new(cmdline: String) -> CommandLineParser { + CommandLineParser { + cmdline, + varmap: HashMap::new(), + pos: 0, + } + } + + fn parse(mut self) -> HashMap> { + // 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); + +} + + diff --git a/libcitadel/src/config.rs b/libcitadel/src/config.rs new file mode 100644 index 0000000..677c71f --- /dev/null +++ b/libcitadel/src/config.rs @@ -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, + + #[serde (rename="default-citadel-base")] + default_citadel_base: Option, + + channel: HashMap, +} + +impl Config { + + pub fn load_default() -> Result { + Config::load(DEFAULT_CONFIG_PATH) + } + + pub fn load>(path: P) -> Result { + 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 { + let s = path.read_as_string()?; + let mut config = toml::from_str::(&s)?; + for (k,v) in config.channel.iter_mut() { + v.name = k.to_string(); + } + + Ok(config) + } + + + pub fn get_default_citadel_base(&self) -> Option { + match self.default_citadel_base { + Some(ref base) => Some(PathBuf::from(base)), + None => None, + } + } + + pub fn get_default_channel(&self) -> Option { + + 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 { + self.channel.get(name).map(|c| c.clone() ) + } + + pub fn get_private_key(&self, channel: &str) -> Option { + 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 { + 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, + pubkey: String, + keypair: Option, + + #[serde(skip)] + name: String, +} + +impl Channel { + pub fn name(&self) -> &str { + &self.name + } + + pub fn sign(&self, data: &[u8]) -> Result { + 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::(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::(data, &sig)?; + Ok(()) + } + +} + diff --git a/libcitadel/src/disks.rs b/libcitadel/src/disks.rs new file mode 100644 index 0000000..c38aab6 --- /dev/null +++ b/libcitadel/src/disks.rs @@ -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` + pub fn boot_partitions() -> Result> { + 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 { + let v = line.split_whitespace().collect::>(); + if v.len() != 4 { + bail!("could not parse"); + } + Ok(DiskPartition::from_line_components( + v[0].parse::()?, // Major + v[1].parse::()?, // Minor + v[2].parse::()?, // 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>(&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, + + } + } +} + diff --git a/libcitadel/src/header.rs b/libcitadel/src/header.rs new file mode 100644 index 0000000..122a766 --- /dev/null +++ b/libcitadel/src/header.rs @@ -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 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>); + +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>(path: P) -> Result { + //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: &mut R) -> Result { + let mut v = vec![0u8; ImageHeader::HEADER_SIZE]; + r.read_exact(&mut v)?; + Ok(ImageHeader(RefCell::new(v))) + } + + pub fn from_partition>(path: P) -> Result { + 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>(&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 { + 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 { + let mlen = self.metainfo_len(); + assert!(mlen > 0 && mlen < MAX_METAINFO_LEN); + self.read_bytes(METAINFO_OFFSET, mlen) + } + + pub fn signature(&self) -> Vec { + 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(&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 { + Vec::from(&self.0.borrow()[offset..offset+len]) + } +} + + +#[derive(Clone)] +pub struct MetaInfo { + bytes: Vec, + is_parsed: bool, + is_valid: bool, + toml: Option, +} + + +#[derive(Deserialize,Serialize,Clone)] +struct MetaInfoToml { + channel: String, + version: u32, + #[serde(rename="base-version")] + base_version: Option, + date: Option, + gitrev: Option, + nblocks: u32, + shasum: String, + #[serde(rename="verity-salt")] + verity_salt: String, + #[serde(rename="verity-root")] + verity_root: String, +} + +impl MetaInfo { + fn new(bytes: Vec) -> 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::(&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 + } +} diff --git a/libcitadel/src/keys.rs b/libcitadel/src/keys.rs new file mode 100644 index 0000000..635d94f --- /dev/null +++ b/libcitadel/src/keys.rs @@ -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 { + let mut rng = OsRng::new()?; + let pair = Keypair::generate::(&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 { + 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 { + 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::(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::(data, &signature)?; + Ok(()) + } + + fn pubkey(&self) -> &PublicKey { + match *self { + KEYPAIR(ref keypair) => &keypair.public, + PUBLIC(ref public) => &public, + } + } +} diff --git a/libcitadel/src/lib.rs b/libcitadel/src/lib.rs new file mode 100644 index 0000000..dde3b1c --- /dev/null +++ b/libcitadel/src/lib.rs @@ -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 = 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 = result::Result; + +pub const BLOCK_SIZE: usize = 4096; diff --git a/libcitadel/src/partition.rs b/libcitadel/src/partition.rs new file mode 100644 index 0000000..282cf03 --- /dev/null +++ b/libcitadel/src/partition.rs @@ -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, + is_mounted: bool, +} + +#[derive(Clone)] +struct HeaderInfo { + header: ImageHeader, + metainfo: MetaInfo, +} + +impl Partition { + pub fn rootfs_partitions(config: &Config) -> Result> { + 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 { + 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> { + 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, 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> { + 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 { + 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) +} diff --git a/libcitadel/src/path_ext.rs b/libcitadel/src/path_ext.rs new file mode 100644 index 0000000..8c17346 --- /dev/null +++ b/libcitadel/src/path_ext.rs @@ -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; + + /// Write file `self` to partition device with dd command. + fn copy_to_partition>(&self, partition: P) -> Result<()>; + + /// Read entire file `self` and return contents as a `String` + fn read_as_string(&self) -> Result; + + /// Read entire file `self` and return contents as a `Vec` of individual lines. + fn read_as_lines(&self) -> Result>; + + /// 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; + + /// Mount path `self` to `target` + fn mount>(&self, target: P) -> Result<()>; + + /// Mount path `self` to `target` with additional argument `args` to mount command. + fn mount_with_args>(&self, target: P, args: &str) -> Result<()>; + + /// Bind mount path `self` to path `target`. + fn bind_mount>(&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, sizelimit: Option) -> Result; + + /// 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; + + /// 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>(&self, hashfile: P) -> Result; + + /// 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; + + /// + 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 { + 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>(&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 { + 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> { + 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 { + 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>(&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>(&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>(&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, sizelimit: Option) -> Result { + 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 { + 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>(&self, hashfile: P) -> Result { + 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 { + 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 { + 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, +} + +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::>(); + + 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 + } +} + + + diff --git a/libcitadel/src/resource.rs b/libcitadel/src/resource.rs new file mode 100644 index 0000000..d2486ef --- /dev/null +++ b/libcitadel/src/resource.rs @@ -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 { + 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 { + 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(&self, reader: &mut R) -> Result { + 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::>(); + 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, + } + } +} +