Big integration of suite of citadel tools
This commit is contained in:
parent
3e9c676a93
commit
5222830626
242
citadel-tools/citadel-appimg/Cargo.lock
generated
Normal file
242
citadel-tools/citadel-appimg/Cargo.lock
generated
Normal file
@ -0,0 +1,242 @@
|
||||
[[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.5 (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.5"
|
||||
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-appimg"
|
||||
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)",
|
||||
"lazy_static 1.0.0 (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 = "lazy_static"
|
||||
version = "1.0.0"
|
||||
source = "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 = "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 = "rustc-demangle"
|
||||
version = "0.1.7"
|
||||
source = "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 = "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 = "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 = "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 = "vec_map"
|
||||
version = "0.8.0"
|
||||
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"
|
||||
|
||||
[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.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9be26b24e988625409b19736d130f0c7d224f01d06454b5f81d8d23d6c1a618f"
|
||||
"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 lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
||||
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
|
||||
"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 rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11fb43a206a04116ffd7cfcf9bcb941f8eb6cc7ff667272246b0a1c74259a3cb"
|
||||
"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 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 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 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 vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
||||
"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"
|
10
citadel-tools/citadel-appimg/Cargo.toml
Normal file
10
citadel-tools/citadel-appimg/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "citadel-appimg"
|
||||
version = "0.1.0"
|
||||
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
homepage = "http://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.30.0"
|
||||
failure = "0.1.1"
|
||||
lazy_static = "1.0.0"
|
216
citadel-tools/citadel-appimg/src/appimg.rs
Normal file
216
citadel-tools/citadel-appimg/src/appimg.rs
Normal file
@ -0,0 +1,216 @@
|
||||
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::{self,File};
|
||||
use std::io::{BufRead,BufReader};
|
||||
|
||||
use Result;
|
||||
use systemd;
|
||||
|
||||
use util::*;
|
||||
|
||||
lazy_static!{
|
||||
static ref APPIMG_BASE_PATH: PathBuf = PathBuf::from("/storage/appimg");
|
||||
static ref USERDATA_BASE_PATH: PathBuf = PathBuf::from("/storage/user-data");
|
||||
static ref SYSTEMD_UNIT_PATH: PathBuf = PathBuf::from("/run/systemd/system");
|
||||
static ref SYSTEMD_NSPAWN_PATH: PathBuf = PathBuf::from("/run/systemd/nspawn");
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppImg {
|
||||
name: String,
|
||||
config: AppImgConfig,
|
||||
}
|
||||
|
||||
impl AppImg {
|
||||
|
||||
pub fn new(name: &str) -> Result<AppImg> {
|
||||
let mut img = AppImg {
|
||||
name: name.to_string(),
|
||||
config: AppImgConfig::new(name),
|
||||
};
|
||||
img.load_config()?;
|
||||
img.write_systemd_files()?;
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn load_config(&mut self) -> Result<()> {
|
||||
let mut path = APPIMG_BASE_PATH.join(&self.name);
|
||||
path.push("config");
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
self.config.load(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_systemd_files(&self) -> Result<()> {
|
||||
if !self.nspawn_config_path().exists() {
|
||||
self.write_nspawn_config()?;
|
||||
}
|
||||
if !self.service_unit_path().exists() {
|
||||
self.write_service_unit()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
systemd::sysctl_is_active(&self.service_unit_name())
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<()> {
|
||||
if self.is_running() {
|
||||
warn!("image {} is already running", self.name);
|
||||
return Ok(());
|
||||
}
|
||||
self.write_nspawn_config()?;
|
||||
self.write_service_unit()?;
|
||||
systemd::systemctl_start(&self.service_unit_name());
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
if !self.is_running() {
|
||||
warn!("image {} is not running", self.name);
|
||||
return;
|
||||
}
|
||||
systemd::systemctl_stop(&self.service_unit_name());
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn nspawn_config_path(&self) -> PathBuf {
|
||||
SYSTEMD_NSPAWN_PATH.join(&format!("{}.nspawn", self.name))
|
||||
}
|
||||
|
||||
fn service_unit_name(&self) -> String {
|
||||
format!("appimg-{}.service", self.name)
|
||||
}
|
||||
|
||||
fn service_unit_path(&self) -> PathBuf {
|
||||
SYSTEMD_UNIT_PATH.join(&format!("appimg-{}.service", self.name))
|
||||
}
|
||||
|
||||
fn write_nspawn_config(&self) -> Result<()> {
|
||||
let mut extra = String::new();
|
||||
extra += &format!("Bind={}:/home/user\n", self.config.home());
|
||||
if self.config.use_kvm() {
|
||||
extra += "Bind=/dev/kvm\n";
|
||||
}
|
||||
if self.config.use_gpu() {
|
||||
extra += "Bind=/dev/dri/renderD128\n";
|
||||
}
|
||||
|
||||
let content = systemd::generate_nspawn_file(&extra);
|
||||
fs::create_dir_all(SYSTEMD_NSPAWN_PATH.as_path())?;
|
||||
write_string_to_file(&self.nspawn_config_path(), &content)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_service_unit(&self) -> Result<()> {
|
||||
let content = systemd::generate_service_file(&self.name);
|
||||
fs::create_dir_all(&SYSTEMD_UNIT_PATH.as_path())?;
|
||||
|
||||
write_string_to_file(&self.service_unit_path(), &content)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppImgConfig {
|
||||
img_name: String,
|
||||
home: Option<String>,
|
||||
kvm: bool,
|
||||
gpu: bool,
|
||||
}
|
||||
|
||||
const DEFAULT_HOME_PATH: &str = "/storage/user-data/primary-home";
|
||||
|
||||
impl AppImgConfig {
|
||||
fn new(img_name: &str) -> AppImgConfig {
|
||||
AppImgConfig{
|
||||
img_name: img_name.to_string(),
|
||||
home: None,
|
||||
kvm: false,
|
||||
gpu: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn home(&self) -> String {
|
||||
if let Some(ref name) = self.home {
|
||||
if is_valid_name(name) {
|
||||
let home_path = USERDATA_BASE_PATH.join(name);
|
||||
return home_path.to_str().unwrap().to_string();
|
||||
}
|
||||
}
|
||||
DEFAULT_HOME_PATH.to_string()
|
||||
}
|
||||
|
||||
pub fn use_kvm(&self) -> bool {
|
||||
self.kvm
|
||||
|
||||
}
|
||||
|
||||
pub fn use_gpu(&self) -> bool {
|
||||
self.gpu
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.home = None;
|
||||
self.kvm = false;
|
||||
self.gpu = false;
|
||||
}
|
||||
|
||||
fn load(&mut self, path: &Path) -> Result<()> {
|
||||
self.reset();
|
||||
let f = File::open(path)?;
|
||||
let reader = BufReader::new(f);
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
let v = line.split('=').collect::<Vec<_>>();
|
||||
if v.len() == 2 {
|
||||
self.process_keyval(v[0].trim(), v[1].trim());
|
||||
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_keyval(&mut self, k: &str, v: &str) {
|
||||
match k {
|
||||
|
||||
"home" => {
|
||||
if is_valid_name(v) {
|
||||
let home_path = USERDATA_BASE_PATH.join(v);
|
||||
if !home_path.is_dir() {
|
||||
warn!("'home' value '{}' in config file of image {} refers to directory that doesn't exist", v, self.img_name);
|
||||
|
||||
}
|
||||
self.home = Some(v.to_string());
|
||||
} else {
|
||||
warn!("Invalid home value '{}' in config file of image {}", v, self.img_name);
|
||||
}
|
||||
},
|
||||
"use-kvm" => {
|
||||
self.kvm = v == "yes";
|
||||
|
||||
},
|
||||
|
||||
"use-gpu" => {
|
||||
self.gpu = v == "yes";
|
||||
},
|
||||
|
||||
_ => {
|
||||
warn!("unknown keyword '{}' in config file for image {}", k, self.img_name);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
94
citadel-tools/citadel-appimg/src/main.rs
Normal file
94
citadel-tools/citadel-appimg/src/main.rs
Normal file
@ -0,0 +1,94 @@
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
|
||||
extern crate clap;
|
||||
|
||||
macro_rules! warn {
|
||||
($e:expr) => { println!("[!]: {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("[!]: {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
mod appimg;
|
||||
mod util;
|
||||
mod systemd;
|
||||
mod manager;
|
||||
|
||||
use failure::Error;
|
||||
use clap::{App,Arg,ArgMatches,SubCommand};
|
||||
use clap::AppSettings::*;
|
||||
use std::process::exit;
|
||||
use std::result;
|
||||
|
||||
pub type Result<T> = result::Result<T,Error>;
|
||||
|
||||
pub use appimg::AppImg;
|
||||
use manager::ImageManager;
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("citadel-appimg")
|
||||
.about("Subgraph Citadel application image management")
|
||||
.settings(&[ArgRequiredElseHelp, ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder])
|
||||
|
||||
.subcommand(SubCommand::with_name("list")
|
||||
.about("Display list of application images"))
|
||||
|
||||
.subcommand(SubCommand::with_name("start")
|
||||
.about("Launch an application image")
|
||||
.arg(Arg::with_name("name")))
|
||||
|
||||
.subcommand(SubCommand::with_name("stop")
|
||||
.about("Stop a running application image")
|
||||
.arg(Arg::with_name("name").required(true)))
|
||||
|
||||
.subcommand(SubCommand::with_name("default")
|
||||
.about("Set an application image as the default image to boot")
|
||||
.arg(Arg::with_name("name").required(true)))
|
||||
|
||||
.get_matches();
|
||||
|
||||
let result = match matches.subcommand() {
|
||||
("list", _) => list_cmd(),
|
||||
("start", Some(m)) => start_cmd(m),
|
||||
("stop", Some(m)) => stop_cmd(m),
|
||||
("default", Some(m)) => default_cmd(m),
|
||||
_ => Ok(()),
|
||||
};
|
||||
if let Err(e) = result {
|
||||
println!("{}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn list_cmd() -> Result<()> {
|
||||
let manager = ImageManager::load()?;
|
||||
manager.list()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_cmd(matches: &ArgMatches) -> Result<()> {
|
||||
let mut manager = ImageManager::load()?;
|
||||
match matches.value_of("name") {
|
||||
Some(name) => manager.start_image(name),
|
||||
None => manager.start_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_cmd(matches: &ArgMatches) -> Result<()> {
|
||||
let name = matches.value_of("name").unwrap();
|
||||
let mut manager = ImageManager::load()?;
|
||||
manager.stop_image(name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_cmd(matches: &ArgMatches) -> Result<()> {
|
||||
let name = matches.value_of("name").unwrap();
|
||||
let mut manager = ImageManager::load()?;
|
||||
if manager.image_exists(name) {
|
||||
manager.set_default(name)?;
|
||||
} else {
|
||||
warn!("No image '{}' exists", name);
|
||||
}
|
||||
Ok(())
|
||||
}
|
264
citadel-tools/citadel-appimg/src/manager.rs
Normal file
264
citadel-tools/citadel-appimg/src/manager.rs
Normal file
@ -0,0 +1,264 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::io::Write;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::{OpenOptionsExt,symlink};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use AppImg;
|
||||
use Result;
|
||||
use util::*;
|
||||
use systemd;
|
||||
|
||||
lazy_static!{
|
||||
static ref APPIMG_BASE_PATH: PathBuf = PathBuf::from("/storage/appimg");
|
||||
static ref APPIMG_RUN_PATH: PathBuf = PathBuf::from("/run/appimg");
|
||||
}
|
||||
|
||||
|
||||
pub struct ImageManager {
|
||||
images: HashMap<String, AppImg>,
|
||||
default: Option<String>,
|
||||
current: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl ImageManager {
|
||||
fn new() -> Result<ImageManager> {
|
||||
let default = default_appimg()?;
|
||||
let current = current_appimg()?;
|
||||
Ok(ImageManager {
|
||||
images: HashMap::new(),
|
||||
default, current,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load() -> Result<ImageManager> {
|
||||
let mut manager = ImageManager::new()?;
|
||||
for dent in fs::read_dir(APPIMG_BASE_PATH.as_path())? {
|
||||
let path = dent?.path();
|
||||
manager.process_path(&path)?;
|
||||
}
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
fn process_path(&mut self, path: &Path) -> Result<()> {
|
||||
let meta = path.symlink_metadata()?;
|
||||
if !meta.is_dir() {
|
||||
return Ok(())
|
||||
}
|
||||
let name = path_filename(path);
|
||||
if !is_valid_name(name) {
|
||||
warn!("ignoring directory in appimg storage which has invalid appimg name: {}", name);
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let appimg = AppImg::new(name)?;
|
||||
println!("adding: {}", appimg.name());
|
||||
self.images.insert(appimg.name().to_string(), appimg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list(&self) -> Result<()> {
|
||||
for img in self.images.values() {
|
||||
let cur = if self.is_current(img) { "(current)" } else { "" };
|
||||
let def = if self.is_default(img) { "(default)" } else { "" };
|
||||
let run = if img.is_running() { "[running]"} else {""};
|
||||
println!(" {} {} {} {}", img.name(), run, def, cur);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_default(&mut self) -> Result<()> {
|
||||
let name = match self.default {
|
||||
Some(ref s) => s.clone(),
|
||||
None => bail!("No default image to start"),
|
||||
};
|
||||
self.start_image(&name)
|
||||
|
||||
}
|
||||
|
||||
pub fn start_image(&mut self, name: &str) -> Result<()> {
|
||||
match self.images.get(name) {
|
||||
Some(img) => {
|
||||
img.start()?;
|
||||
},
|
||||
None => {
|
||||
warn!("Cannot start '{}'. Image does not exist");
|
||||
return Ok(())
|
||||
},
|
||||
}
|
||||
// if current is not set, set it to this one
|
||||
if self.current.is_none() {
|
||||
self.set_current(name)?;
|
||||
systemd::systemctl_restart("desktopd");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop_image(&mut self, name: &str) -> Result<()> {
|
||||
match self.images.get(name) {
|
||||
Some(img) => img.stop(),
|
||||
None => {
|
||||
warn!("Cannot stop '{}'. Image does not exist");
|
||||
return Ok(())
|
||||
},
|
||||
}
|
||||
let current = match self.current {
|
||||
Some(ref s) => s.clone(),
|
||||
None => return Ok(()),
|
||||
};
|
||||
if current == name {
|
||||
systemd::systemctl_stop("desktopd");
|
||||
let path = APPIMG_RUN_PATH.join("current.appimg");
|
||||
if path.exists() {
|
||||
fs::remove_file(&path)?;
|
||||
}
|
||||
if let Some(img_name) = self.find_running_image_name() {
|
||||
self.set_current(&img_name)?;
|
||||
systemd::systemctl_start("desktopd");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_running_image_name(&self) -> Option<String> {
|
||||
for img in self.images.values() {
|
||||
if img.is_running() {
|
||||
return Some(img.name().to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_current(&self, img: &AppImg) -> bool {
|
||||
self.same_name(img, &self.current)
|
||||
}
|
||||
|
||||
fn is_default(&self, img: &AppImg) -> bool {
|
||||
self.same_name(img, &self.default)
|
||||
}
|
||||
|
||||
fn same_name(&self, img: &AppImg, name: &Option<String>) -> bool {
|
||||
if let Some(ref name) = *name {
|
||||
name == img.name()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
pub fn image_exists(&self, name: &str) -> bool {
|
||||
self.images.contains_key(name)
|
||||
}
|
||||
|
||||
|
||||
pub fn set_default(&mut self, name: &str) -> Result<()> {
|
||||
if !is_valid_name(name) {
|
||||
warn!("{} is not a valid image name", name);
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if let Some(ref default) = self.default {
|
||||
if default == name {
|
||||
warn!("{} is already default appimg", name);
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let path = APPIMG_BASE_PATH.join("default.appimg");
|
||||
if path.exists() {
|
||||
fs::remove_file(&path)?;
|
||||
}
|
||||
symlink(name, &path)?;
|
||||
self.default = Some(name.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_current(&mut self, name: &str) -> Result<()> {
|
||||
if !is_valid_name(name) {
|
||||
warn!("{} is not a valid image name", name);
|
||||
return Ok(())
|
||||
}
|
||||
if let Some(ref current) = self.current {
|
||||
if current == name {
|
||||
warn!("{} is already current appimg", name);
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fs::create_dir_all(APPIMG_RUN_PATH.as_path())?;
|
||||
let path = APPIMG_RUN_PATH.join("current.appimg");
|
||||
let target = APPIMG_BASE_PATH.join(name);
|
||||
if path.exists() {
|
||||
fs::remove_file(&path)?;
|
||||
}
|
||||
symlink(&target, &path)?;
|
||||
|
||||
let script = format!("#!/bin/bash\nmachinectl -E DESKTOP_STARTUP_ID=${{DESKTOP_STARTUP_ID}} shell user@{} /usr/libexec/launch $@\n", name);
|
||||
let script_path = APPIMG_RUN_PATH.join("run-in-image");
|
||||
let mut f = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.mode(0o755)
|
||||
.open(&script_path)?;
|
||||
|
||||
f.write_all(script.as_bytes())?;
|
||||
self.current = Some(name.to_string());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn appimg_symlink(symlink: &Path) -> Result<Option<String>> {
|
||||
if !symlink.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if !symlink.symlink_metadata()?.file_type().is_symlink() {
|
||||
bail!("{} exists but it is not a symlink", symlink.display());
|
||||
}
|
||||
|
||||
let link = fs::read_link(&symlink)?;
|
||||
|
||||
let appimg_name = appimg_name_for_symlink_target(&link)?;
|
||||
if !is_valid_name(&appimg_name) {
|
||||
bail!("symlink {} points to a directory with a name ({}) that is not a valid appimg name", symlink.display(), appimg_name);
|
||||
}
|
||||
|
||||
Ok(Some(appimg_name))
|
||||
}
|
||||
|
||||
fn default_appimg() -> Result<Option<String>> {
|
||||
appimg_symlink(&APPIMG_BASE_PATH.join("default.appimg"))
|
||||
}
|
||||
|
||||
fn current_appimg() -> Result<Option<String>> {
|
||||
appimg_symlink(&APPIMG_RUN_PATH.join("current.appimg"))
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns a name only if target points to some subdirectory of APPIMG_BASE_PATH
|
||||
///
|
||||
fn appimg_name_for_symlink_target(target: &Path) -> Result<String> {
|
||||
let path = if target.is_absolute() {
|
||||
target.to_path_buf()
|
||||
} else if target.components().count() == 1 {
|
||||
APPIMG_BASE_PATH.join(target)
|
||||
} else {
|
||||
bail!("symlink target has invalid value: {}", target.display())
|
||||
};
|
||||
|
||||
match path.parent() {
|
||||
Some(parent) => {
|
||||
if parent != APPIMG_BASE_PATH.as_path() {
|
||||
bail!("symlink target points outside of /storage/appimg directory");
|
||||
}
|
||||
},
|
||||
None => {
|
||||
bail!("symlink target has invalid value (no parent): {}", target.display())
|
||||
},
|
||||
};
|
||||
|
||||
if !path.is_dir() {
|
||||
bail!("symlink target {} is not a directory", path.display());
|
||||
}
|
||||
Ok(path_filename(&path).to_string())
|
||||
}
|
113
citadel-tools/citadel-appimg/src/systemd.rs
Normal file
113
citadel-tools/citadel-appimg/src/systemd.rs
Normal file
@ -0,0 +1,113 @@
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl";
|
||||
|
||||
pub fn sysctl_is_active(name: &str) -> bool {
|
||||
let mut cmd = Command::new(SYSTEMCTL_PATH);
|
||||
cmd.arg("-q");
|
||||
cmd.arg("is-active");
|
||||
cmd.arg(name);
|
||||
match cmd.status() {
|
||||
Ok(status) => status.success(),
|
||||
Err(e) => {
|
||||
warn!("failed to execute /usr/bin/systemctl: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn systemctl_restart(name: &str) -> bool {
|
||||
run_systemctl("restart", name)
|
||||
}
|
||||
|
||||
pub fn systemctl_start(name: &str) -> bool {
|
||||
run_systemctl("start", name)
|
||||
}
|
||||
|
||||
pub fn systemctl_stop(name: &str) -> bool {
|
||||
run_systemctl("stop", name)
|
||||
}
|
||||
|
||||
fn run_systemctl(op: &str, name: &str) -> bool {
|
||||
let mut cmd = Command::new(SYSTEMCTL_PATH);
|
||||
cmd.arg(op);
|
||||
cmd.arg(name);
|
||||
match cmd.output() {
|
||||
Err(e) => {
|
||||
warn!("failed to execute /usr/bin/systemctl: {}", e);
|
||||
false
|
||||
}
|
||||
Ok(output) => {
|
||||
if !output.status.success() {
|
||||
warn!("error running systemctl {}: {}", op, String::from_utf8(output.stderr).unwrap());
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_nspawn_file(extra_bind_mounts: &str) -> String {
|
||||
NSPAWN_FILE_TEMPLATE.replace("$EXTRA_BIND_MOUNTS", extra_bind_mounts)
|
||||
}
|
||||
|
||||
pub fn generate_service_file(appimg_name: &str) -> String {
|
||||
APPIMG_SERVICE_TEMPLATE.replace("$APPIMG_NAME", appimg_name)
|
||||
}
|
||||
|
||||
pub const NSPAWN_FILE_TEMPLATE: &str = r###"
|
||||
[Exec]
|
||||
Boot=true
|
||||
Environment=IFCONFIG_IP=172.17.0.2/24
|
||||
Environment=IFCONFIG_GW=172.17.0.1
|
||||
|
||||
[Files]
|
||||
BindReadOnly=/usr/share/themes/Adapta
|
||||
BindReadOnly=/usr/share/themes/Adapta-Eta
|
||||
BindReadOnly=/usr/share/themes/Adapta-Nokto
|
||||
BindReadOnly=/usr/share/themes/Adapta-Nokto-Eta
|
||||
BindReadOnly=/usr/share/icons/Paper
|
||||
|
||||
BindReadOnly=/storage/citadel-state/resolv.conf:/etc/resolv.conf
|
||||
|
||||
#
|
||||
# Bind mounts for sound and pulse audio
|
||||
#
|
||||
Bind=/dev/snd
|
||||
Bind=/dev/shm
|
||||
BindReadOnly=/run/user/1000/pulse:/run/user/host/pulse
|
||||
|
||||
BindReadOnly=/tmp/.X11-unix
|
||||
BindReadOnly=/run/user/1000/wayland-0:/run/user/host/wayland-0
|
||||
|
||||
$EXTRA_BIND_MOUNTS
|
||||
|
||||
#
|
||||
# Uncomment to enable kvm access in container
|
||||
#
|
||||
#Bind=/dev/kvm
|
||||
|
||||
#
|
||||
# Uncomment to enable GPU access in container
|
||||
#
|
||||
#Bind=/dev/dri/renderD128
|
||||
|
||||
[Network]
|
||||
Zone=clear
|
||||
"###;
|
||||
|
||||
pub const APPIMG_SERVICE_TEMPLATE: &str = r###"
|
||||
[Unit]
|
||||
Description=Application Image $APPIMG_NAME instance
|
||||
Wants=desktopd.service
|
||||
|
||||
[Service]
|
||||
Environment=SYSTEMD_NSPAWN_SHARE_NS_IPC=1
|
||||
ExecStart=/usr/bin/systemd-nspawn --quiet --keep-unit --machine=$APPIMG_NAME --link-journal=try-guest --directory=/storage/appimg/$APPIMG_NAME/rootfs
|
||||
|
||||
KillMode=mixed
|
||||
Type=notify
|
||||
RestartForceExitStatus=133
|
||||
SuccessExitStatus=133
|
||||
"###;
|
39
citadel-tools/citadel-appimg/src/util.rs
Normal file
39
citadel-tools/citadel-appimg/src/util.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use std::io::Write;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
pub fn write_string_to_file(path: &Path, s: &str) -> Result<()> {
|
||||
let mut f = File::create(path)?;
|
||||
f.write_all(s.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn is_valid_name(name: &str) -> bool {
|
||||
is_first_char_alphabetic(name) && name.chars().all(is_alphanum_or_dash)
|
||||
}
|
479
citadel-tools/citadel-desktopd/Cargo.lock
generated
Normal file
479
citadel-tools/citadel-desktopd/Cargo.lock
generated
Normal file
@ -0,0 +1,479 @@
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "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"
|
15
citadel-tools/citadel-desktopd/Cargo.toml
Normal file
15
citadel-tools/citadel-desktopd/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "citadel-desktopd"
|
||||
version = "0.1.0"
|
||||
authors = ["brl@subgraph.com"]
|
||||
|
||||
[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"
|
@ -0,0 +1,6 @@
|
||||
[options]
|
||||
exec_prefix="/run/appimg/run-in-image"
|
||||
target_directory="/home/citadel/.local/share/applications"
|
||||
|
||||
[[sources]]
|
||||
path="/run/appimg/current.appimg/rootfs/usr/share/applications"
|
@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Desktop Integration Manager
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/libexec/citadel-desktopd /usr/share/citadel/citadel-desktopd.conf
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
120
citadel-tools/citadel-desktopd/src/config.rs
Normal file
120
citadel-tools/citadel-desktopd/src/config.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use Result;
|
||||
use toml;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
exec_prefix: String,
|
||||
target_directory: PathBuf,
|
||||
source_paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
|
||||
impl Config {
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||
let toml = ConfigToml::from_path(path)?;
|
||||
let config = toml.to_config()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn exec_prefix(&self) -> &str {
|
||||
self.exec_prefix.as_ref()
|
||||
}
|
||||
|
||||
pub fn target_directory(&self) -> &Path {
|
||||
self.target_directory.as_ref()
|
||||
}
|
||||
|
||||
pub fn source_paths(&self) -> &Vec<PathBuf> {
|
||||
self.source_paths.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ConfigToml {
|
||||
options: Option<Options>,
|
||||
sources: Option<Vec<Source>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Options {
|
||||
exec_prefix: Option<String>,
|
||||
target_directory: Option<String>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn exec_prefix(&self) -> Result<&str> {
|
||||
match self.exec_prefix {
|
||||
Some(ref s) => Ok(s.as_str()),
|
||||
None => Err(format_err!("missing 'exec_prefix=' field")),
|
||||
}
|
||||
}
|
||||
|
||||
fn target_directory(&self) -> Result<&str> {
|
||||
match self.target_directory {
|
||||
Some(ref s) => Ok(s.as_str()),
|
||||
None => Err(format_err!("missing 'target_directory=' field")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Clone)]
|
||||
struct Source {
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
fn path(&self) -> Result<&str> {
|
||||
match self.path {
|
||||
Some(ref s) => Ok(s.as_str()),
|
||||
None => Err(format_err!("missing 'path=' field")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_as_string(path: &Path) -> Result<String> {
|
||||
let mut f = File::open(path)?;
|
||||
let mut buffer = String::new();
|
||||
f.read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
impl ConfigToml {
|
||||
fn from_path<P: AsRef<Path>>(path: P) -> Result<ConfigToml> {
|
||||
let s = load_as_string(path.as_ref())?;
|
||||
let config = toml::from_str::<ConfigToml>(&s)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn options(&self) -> Result<&Options> {
|
||||
self.options.as_ref()
|
||||
.ok_or(format_err!("missing '[options]' section"))
|
||||
}
|
||||
|
||||
fn sources(&self) -> Result<Vec<Source>> {
|
||||
match self.sources {
|
||||
Some(ref srcs) => Ok(srcs.clone()),
|
||||
None => Err(format_err!("missing '[[sources]]' section(s)")),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_config(&self) -> Result<Config> {
|
||||
let options = self.options()?;
|
||||
let exec_prefix = options.exec_prefix()?.to_string() + " ";
|
||||
let target_path = options.target_directory()?.to_string();
|
||||
let target_directory = PathBuf::from(target_path);
|
||||
|
||||
let mut source_paths = Vec::new();
|
||||
for src in self.sources()? {
|
||||
let path = src.path()?;
|
||||
source_paths.push(PathBuf::from(path));
|
||||
}
|
||||
Ok(Config{
|
||||
exec_prefix,
|
||||
target_directory,
|
||||
source_paths,
|
||||
})
|
||||
}
|
||||
}
|
188
citadel-tools/citadel-desktopd/src/desktop.rs
Normal file
188
citadel-tools/citadel-desktopd/src/desktop.rs
Normal file
@ -0,0 +1,188 @@
|
||||
use std::io::Write;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
use Result;
|
||||
|
||||
|
||||
pub struct DesktopFile {
|
||||
filename: String,
|
||||
lines: Vec<Line>,
|
||||
// map from key of key/value pair to index of line in lines vector
|
||||
main_map: HashMap<String, usize>,
|
||||
// map from group name to map of key/value -> index
|
||||
groups: HashMap<String,HashMap<String,usize>>,
|
||||
}
|
||||
|
||||
|
||||
impl DesktopFile {
|
||||
|
||||
pub fn write_to_dir<P: AsRef<Path>>(&self, directory: P) -> Result<()> {
|
||||
let mut path = directory.as_ref().to_path_buf();
|
||||
path.push(self.filename.as_str());
|
||||
let f = File::create(&path)?;
|
||||
self.write_to(f)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_to<W: Write>(&self, mut w: W) -> Result<()> {
|
||||
for line in &self.lines {
|
||||
line.write_to(&mut w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn filename(&self) -> &str {
|
||||
self.filename.as_ref()
|
||||
}
|
||||
|
||||
//
|
||||
// Conditions for translating a .desktop entry
|
||||
//
|
||||
// Type=Application // Mandatory 'Type' key must be Application
|
||||
// NotShowIn= // If 'NotShowIn' key is present, must not contain GNOME
|
||||
// OnlyShowIn= // If 'OnlyShowIn' key is present, must contain 'GNOME'
|
||||
// Terminal=false // If 'Terminal' key is present, must be false
|
||||
// Hidden=false // If 'Hidden' key is present, must be false
|
||||
//
|
||||
pub fn is_showable(&self) -> bool {
|
||||
self.is_application_type() && self.show_in_gnome() &&
|
||||
!(self.needs_terminal() || self.is_hidden())
|
||||
}
|
||||
|
||||
fn needs_terminal(&self) -> bool {
|
||||
self.key_exists_and_not_false("Terminal")
|
||||
}
|
||||
|
||||
fn is_application_type(&self) -> bool {
|
||||
if let Some(t) = self.get_key_val("Type") {
|
||||
return t == "Application"
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn show_in_gnome(&self) -> bool {
|
||||
if self.key_exists("NotShowIn") && self.key_value_contains("NotShowIn", "GNOME") {
|
||||
return false;
|
||||
}
|
||||
if self.key_exists("OnlyShowIn") && !self.key_value_contains("OnlyShowIn", "GNOME") {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn key_value_contains(&self, key: &str, s: &str) -> bool {
|
||||
match self.get_key_val(key) {
|
||||
Some(val) => val.contains(s),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn key_exists(&self, key: &str) -> bool {
|
||||
self.main_map.contains_key(key)
|
||||
}
|
||||
|
||||
fn is_hidden(&self) -> bool {
|
||||
self.key_exists_and_not_false("Hidden")
|
||||
}
|
||||
|
||||
fn key_exists_and_not_false(&self, key: &str) -> bool {
|
||||
if let Some(s) = self.get_key_val(key) {
|
||||
if s != "false" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_key_val(&self, key: &str) -> Option<&str> {
|
||||
if let Some(idx) = self.main_map.get(key) {
|
||||
match self.lines[*idx] {
|
||||
Line::KeyValue(_, ref v) => return Some(v),
|
||||
ref line => panic!("Key lookup on '{}' returned wrong line type: {:?}", key, line),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
pub fn new(filename: &str) -> DesktopFile {
|
||||
DesktopFile {
|
||||
filename: filename.to_string(),
|
||||
lines: Vec::new(),
|
||||
main_map: HashMap::new(),
|
||||
groups: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_line(&mut self, line: Line) {
|
||||
if line.is_key_value_type() {
|
||||
let idx = self.lines.len();
|
||||
self.main_map.insert(line.get_key_string(), idx);
|
||||
}
|
||||
self.lines.push(line);
|
||||
}
|
||||
|
||||
pub fn add_action_line(&mut self, action: &str, line: Line) {
|
||||
if line.is_key_value_type() {
|
||||
let idx = self.lines.len();
|
||||
let map = self.groups.entry(action.to_string()).or_insert(HashMap::new());
|
||||
map.insert(line.get_key_string(), idx);
|
||||
}
|
||||
self.lines.push(line);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Line {
|
||||
Empty,
|
||||
Comment(String),
|
||||
ExecLine(String),
|
||||
KeyValue(String,String),
|
||||
KeyLocaleValue(String,String,String),
|
||||
DesktopHeader,
|
||||
ActionHeader(String),
|
||||
GroupHeader(String)
|
||||
}
|
||||
|
||||
|
||||
impl Line {
|
||||
pub fn is_action_header(&self) -> bool {
|
||||
match *self {
|
||||
Line::ActionHeader(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_key_value_type(&self) -> bool {
|
||||
match *self {
|
||||
Line::KeyValue(..) | Line::KeyLocaleValue(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key_string(&self) -> String {
|
||||
match *self {
|
||||
Line::KeyValue(ref k, ..) => k.to_string(),
|
||||
Line::KeyLocaleValue(ref k, ref loc, ..) => format!("{}[{}]", k, loc),
|
||||
_ => panic!("get_key_string() called on Line item which is not a key/value type"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to<W: Write>(&self, mut w: W) -> Result<()> {
|
||||
match *self {
|
||||
Line::Empty => writeln!(w)?,
|
||||
Line::Comment(ref s) => writeln!(w, "#{}", s)?,
|
||||
Line::ExecLine(ref s) => writeln!(w, "Exec={}", s)?,
|
||||
Line::KeyValue(ref k, ref v) => writeln!(w, "{}={}", k, v)?,
|
||||
Line::KeyLocaleValue(ref k, ref loc, ref v) => writeln!(w, "{}[{}]={}", k, loc, v)?,
|
||||
Line::DesktopHeader => writeln!(w, "[Desktop Entry]")?,
|
||||
Line::ActionHeader(ref action) => writeln!(w, "[Desktop Action {}]", action)?,
|
||||
Line::GroupHeader(..) => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
120
citadel-tools/citadel-desktopd/src/desktop_file_sync.rs
Normal file
120
citadel-tools/citadel-desktopd/src/desktop_file_sync.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use std::sync::{Arc,Mutex};
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use monitor::{DirectoryMonitor,MonitorEventHandler};
|
||||
use parser::DesktopFileParser;
|
||||
use config::Config;
|
||||
use Result;
|
||||
|
||||
|
||||
pub struct DesktopFileSync {
|
||||
config: Config,
|
||||
monitor: DirectoryMonitor,
|
||||
}
|
||||
|
||||
impl DesktopFileSync {
|
||||
pub fn new(config: Config) -> DesktopFileSync {
|
||||
let handler = Arc::new(Mutex::new(SyncHandler::new(config.clone())));
|
||||
let monitor = DirectoryMonitor::new(handler.clone());
|
||||
DesktopFileSync {
|
||||
config, monitor
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_target_directory(&self) -> Result<()> {
|
||||
let entries = fs::read_dir(self.config.target_directory())?;
|
||||
|
||||
for entry in entries {
|
||||
let path = entry?.path();
|
||||
if is_desktop_file(&path) {
|
||||
fs::remove_file(&path).context(format!("remove_file({:?})", path))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sync_source(&mut self, src: &Path) -> Result<()> {
|
||||
self.clear_target_directory()?;
|
||||
self.sync_source_directory(src)?;
|
||||
self.monitor.set_monitor_sources(&[src.to_path_buf()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync_source_directory(&self, src: &Path) -> Result<()> {
|
||||
let entries = fs::read_dir(src)?;
|
||||
for entry in entries {
|
||||
let path = entry?.path();
|
||||
if is_desktop_file(path.as_path()) {
|
||||
if let Err(e) = sync_desktop_file(path.as_path(), &self.config) {
|
||||
info!("error syncing desktop file {:?}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct SyncHandler {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl SyncHandler {
|
||||
fn new(config: Config) -> SyncHandler {
|
||||
SyncHandler { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorEventHandler for SyncHandler {
|
||||
fn file_added(&self, path: &Path) -> Result<()> {
|
||||
info!("file_added: {:?}", path);
|
||||
if is_desktop_file(path) {
|
||||
sync_desktop_file(path, &self.config)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn file_removed(&self, path: &Path) -> Result<()> {
|
||||
info!("file_removed: {:?}", path);
|
||||
let filename = filename_from_path(path)?;
|
||||
let target_path = self.config.target_directory().join(filename);
|
||||
if target_path.exists() {
|
||||
fs::remove_file(target_path.as_path())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_desktop_file(source: &Path, config: &Config) -> Result<()> {
|
||||
if !is_desktop_file(source) {
|
||||
return Err(format_err!("source path [{:?}] is not desktop file", source));
|
||||
}
|
||||
let df = DesktopFileParser::parse_from_path(source, config.exec_prefix())?;
|
||||
if df.is_showable() {
|
||||
df.write_to_dir(config.target_directory())?;
|
||||
} else {
|
||||
info!("ignoring {} as not showable", df.filename());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_desktop_file(path: &Path) -> bool {
|
||||
if let Some(ext) = path.extension() {
|
||||
return ext == "desktop"
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn filename_from_path(path: &Path) -> Result<&str> {
|
||||
let filename = match path.file_name() {
|
||||
Some(name) => name,
|
||||
None => return Err(format_err!("Path {:?} has no filename component", path)),
|
||||
};
|
||||
match filename.to_str() {
|
||||
Some(s) => Ok(s),
|
||||
None => Err(format_err!("Filename has invalid utf8 encoding")),
|
||||
}
|
||||
}
|
||||
|
57
citadel-tools/citadel-desktopd/src/main.rs
Normal file
57
citadel-tools/citadel-desktopd/src/main.rs
Normal file
@ -0,0 +1,57 @@
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
extern crate env_logger;
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
extern crate inotify;
|
||||
extern crate nix;
|
||||
|
||||
mod desktop;
|
||||
mod parser;
|
||||
mod config;
|
||||
mod monitor;
|
||||
mod desktop_file_sync;
|
||||
|
||||
use std::result;
|
||||
use std::process;
|
||||
use failure::Error;
|
||||
use desktop_file_sync::DesktopFileSync;
|
||||
|
||||
use config::Config;
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
|
||||
fn main() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
env_logger::init();
|
||||
|
||||
let mut args = std::env::args();
|
||||
args.next();
|
||||
if args.len() != 1 {
|
||||
println!("expected config file argument");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let config_path = args.next().unwrap();
|
||||
let config = match Config::from_path(&config_path) {
|
||||
Err(e) => {
|
||||
warn!("Failed to load configuration file: {}", e);
|
||||
process::exit(1);
|
||||
},
|
||||
Ok(config) => config,
|
||||
};
|
||||
|
||||
let src = config.source_paths().first().unwrap().clone();
|
||||
|
||||
let mut dfs = DesktopFileSync::new(config.clone());
|
||||
if let Err(e) = dfs.sync_source(src.as_path()) {
|
||||
warn!("error calling sync_source: {}", e);
|
||||
}
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::new(120, 0));
|
||||
}
|
||||
|
||||
}
|
210
citadel-tools/citadel-desktopd/src/monitor.rs
Normal file
210
citadel-tools/citadel-desktopd/src/monitor.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::sync::{Arc,Mutex,Once,ONCE_INIT};
|
||||
use std::sync::atomic::{AtomicBool,Ordering};
|
||||
use std::thread::{self,JoinHandle};
|
||||
use std::os::unix::thread::JoinHandleExt;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use nix::libc;
|
||||
use nix::sys::signal;
|
||||
|
||||
use inotify::{Events,Inotify,EventMask,WatchMask,Event,WatchDescriptor};
|
||||
|
||||
use Result;
|
||||
|
||||
pub trait MonitorEventHandler: Send+Sync {
|
||||
fn file_added(&self, path: &Path) -> Result<()> { let _ = path; Ok(()) }
|
||||
fn file_removed(&self, path: &Path) -> Result<()> { let _ = path; Ok(()) }
|
||||
fn directory_added(&self, path: &Path) -> Result<()> { let _ = path; Ok(()) }
|
||||
fn directory_removed(&self, path: &Path) -> Result<()> { let _ = path; Ok(()) }
|
||||
}
|
||||
|
||||
pub struct DirectoryMonitor {
|
||||
event_handler: Arc<Mutex<MonitorEventHandler>>,
|
||||
worker_handle: Option<WorkerHandle>,
|
||||
}
|
||||
|
||||
impl DirectoryMonitor {
|
||||
pub fn new(handler: Arc<Mutex<MonitorEventHandler>>) -> DirectoryMonitor {
|
||||
initialize();
|
||||
DirectoryMonitor {
|
||||
event_handler: handler,
|
||||
worker_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_monitor_sources(&mut self, sources: &[PathBuf]) {
|
||||
if let Some(handle) = self.worker_handle.take() {
|
||||
handle.stop();
|
||||
handle.wait();
|
||||
}
|
||||
let sources = Vec::from(sources);
|
||||
let h = MonitorWorker::start_worker(sources, self.event_handler.clone());
|
||||
self.worker_handle = Some(h);
|
||||
}
|
||||
}
|
||||
|
||||
struct MonitorWorker {
|
||||
descriptors: HashMap<WatchDescriptor,PathBuf>,
|
||||
inotify: Inotify,
|
||||
exit_flag: Arc<AtomicBool>,
|
||||
watch_paths: Vec<PathBuf>,
|
||||
handler: Arc<Mutex<MonitorEventHandler>>,
|
||||
}
|
||||
|
||||
|
||||
impl MonitorWorker {
|
||||
fn start_worker(watch_paths: Vec<PathBuf>, handler: Arc<Mutex<MonitorEventHandler>>) -> WorkerHandle {
|
||||
let exit_flag = Arc::new(AtomicBool::new(false));
|
||||
let flag_clone = exit_flag.clone();
|
||||
let jhandle = thread::spawn(move || {
|
||||
|
||||
let mut worker = match MonitorWorker::new(watch_paths, flag_clone, handler) {
|
||||
Ok(worker) => worker,
|
||||
Err(e) => {
|
||||
info!("failed to initialize inotify handle: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Err(e) = worker.run() {
|
||||
info!("error returned from worker thread: {}", e);
|
||||
}
|
||||
});
|
||||
WorkerHandle::new(jhandle, exit_flag)
|
||||
}
|
||||
|
||||
fn new(watch_paths: Vec<PathBuf>, exit_flag: Arc<AtomicBool>, handler: Arc<Mutex<MonitorEventHandler>>) -> Result<MonitorWorker> {
|
||||
Ok(MonitorWorker {
|
||||
descriptors: HashMap::new(),
|
||||
inotify: Inotify::init()?,
|
||||
exit_flag,
|
||||
watch_paths,
|
||||
handler,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_watches(&mut self) -> Result<()> {
|
||||
let watch_flags = WatchMask::CREATE | WatchMask::DELETE | WatchMask::MOVED_TO |
|
||||
WatchMask::DONT_FOLLOW | WatchMask::ONLYDIR;
|
||||
for p in &self.watch_paths {
|
||||
let wd = self.inotify.add_watch(p, watch_flags)?;
|
||||
self.descriptors.insert(wd, p.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_events<'a>(&mut self, buffer: &'a mut [u8]) -> Result<Option<Events<'a>>> {
|
||||
if self.exit_flag.load(Ordering::Relaxed) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match self.inotify.read_events_blocking(buffer) {
|
||||
Ok(events) => Ok(Some(events)),
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::Interrupted {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_events(&self, events: Events) {
|
||||
for ev in events {
|
||||
if let Err(e) = self.handle_event(&ev) {
|
||||
info!("error handling inotify event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<()> {
|
||||
info!("running monitor event loop");
|
||||
self.add_watches()?;
|
||||
let mut buffer = [0u8; 4096];
|
||||
loop {
|
||||
match self.read_events(&mut buffer)? {
|
||||
Some(events) => self.process_events(events),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn full_event_path(&self, ev: &Event) -> Result<PathBuf> {
|
||||
let filename = ev.name
|
||||
.ok_or(format_err!("inotify event received without a filename"))?;
|
||||
let path = self.descriptors.get(&ev.wd)
|
||||
.ok_or(format_err!("Failed to find descriptor for received inotify event"))?;
|
||||
Ok(path.join(filename))
|
||||
}
|
||||
|
||||
fn handle_event(&self, ev: &Event) -> Result<()> {
|
||||
let handler = self.handler.lock().unwrap();
|
||||
let pb = self.full_event_path(ev)?;
|
||||
let path = pb.as_path();
|
||||
let is_create = ev.mask.intersects(EventMask::CREATE|EventMask::MOVED_TO);
|
||||
if !is_create && !ev.mask.contains(EventMask::DELETE) {
|
||||
return Err(format_err!("Unexpected mask value for inotify event: {:?}", ev.mask));
|
||||
}
|
||||
|
||||
if ev.mask.contains(EventMask::ISDIR) {
|
||||
if is_create {
|
||||
handler.directory_added(path)?;
|
||||
} else {
|
||||
handler.directory_removed(path)?;
|
||||
}
|
||||
|
||||
} else {
|
||||
if is_create {
|
||||
handler.file_added(path)?;
|
||||
} else {
|
||||
handler.file_removed(path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorkerHandle {
|
||||
join_handle: JoinHandle<()>,
|
||||
exit_flag: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl WorkerHandle {
|
||||
fn new(join_handle: JoinHandle<()>, exit_flag: Arc<AtomicBool>) -> WorkerHandle {
|
||||
WorkerHandle { join_handle, exit_flag }
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
info!("calling stop on monitor");
|
||||
let tid = self.join_handle.as_pthread_t();
|
||||
self.exit_flag.store(true, Ordering::Relaxed);
|
||||
unsafe {
|
||||
libc::pthread_kill(tid, signal::SIGUSR1 as libc::c_int);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait(self) {
|
||||
if let Err(e) = self.join_handle.join() {
|
||||
warn!("monitor thread panic with '{:?}'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static INITIALIZE_ONCE: Once = ONCE_INIT;
|
||||
|
||||
fn initialize() {
|
||||
INITIALIZE_ONCE.call_once(|| {
|
||||
let h = signal::SigHandler::Handler(sighandler);
|
||||
let sa = signal::SigAction::new(h, signal::SaFlags::empty(), signal::SigSet::empty());
|
||||
if let Err(e) = unsafe { signal::sigaction(signal::SIGUSR1, &sa) } {
|
||||
warn!("Error setting signal handler: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extern fn sighandler(_: libc::c_int) {
|
||||
// do nothing, signal is only used to EINTR blocking inotify call
|
||||
}
|
307
citadel-tools/citadel-desktopd/src/parser.rs
Normal file
307
citadel-tools/citadel-desktopd/src/parser.rs
Normal file
@ -0,0 +1,307 @@
|
||||
|
||||
use std::io::Read;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::collections::HashSet;
|
||||
use desktop::{DesktopFile,Line};
|
||||
use Result;
|
||||
|
||||
lazy_static! {
|
||||
// These are the keys which are copied into the translated .desktop files
|
||||
static ref KEY_WHITELIST: HashSet<&'static str> = [
|
||||
"Type", "Version", "Name", "GenericName", "NoDisplay", "Comment", "Icon", "Hidden",
|
||||
"OnlyShowIn", "NotShowIn", "Path", "Terminal", "Actions", "MimeType",
|
||||
"Categories", "Keywords", "StartupNotify", "StartupWMClass", "URL", "DocPath",
|
||||
"X-GNOME-FullName", "X-GNOME-Provides", "X-Desktop-File-Install-Version", "X-GNOME-UsesNotifications",
|
||||
"X-GNOME-DocPath", "X-Geoclue-Reason", "X-GNOME-SingleWindow", "X-GNOME-Gettext-Domain",
|
||||
"X-MultipleArgs",
|
||||
].iter().cloned().collect();
|
||||
|
||||
// These are keys which are recognized but deliberately ignored.
|
||||
static ref KEY_IGNORELIST: HashSet<&'static str> = [
|
||||
"DBusActivatable", "Implements", "TryExec", "InitialPreference", "Encoding", "X-KDE-Protocols", "X-GIO-NoFuse", "X-Gnome-Vfs-System",
|
||||
"X-GNOME-Autostart-Phase", "X-GNOME-Autostart-Notify", "X-GNOME-AutoRestart",
|
||||
"X-GNOME-Bugzilla-Bugzilla", "X-GNOME-Bugzilla-Product", "X-GNOME-Bugzilla-Component", "X-GNOME-Bugzilla-Version",
|
||||
"X-GNOME-Bugzilla-ExtraInfoScript", "X-GNOME-Bugzilla-OtherBinaries", "X-GNOME-Autostart-enabled",
|
||||
"X-AppInstall-Package", "X-KDE-SubstituteUID", "X-Ubuntu-Gettext-Domain", "X-AppInstall-Keywords",
|
||||
"X-Ayatana-Desktop-Shortcuts", "X-GNOME-Settings-Panel", "X-GNOME-WMSettingsModule", "X-GNOME-WMName",
|
||||
"X-GnomeWMSettingsLibrary",
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
fn is_whitelisted_key(key: &str) -> bool {
|
||||
KEY_WHITELIST.contains(key)
|
||||
}
|
||||
|
||||
fn filename_from_path(path: &Path) -> Result<&str> {
|
||||
let filename = match path.file_name() {
|
||||
Some(name) => name,
|
||||
None => return Err(format_err!("Path {:?} has no filename component", path)),
|
||||
};
|
||||
match filename.to_str() {
|
||||
Some(s) => Ok(s),
|
||||
None => Err(format_err!("Filename has invalid utf8 encoding")),
|
||||
}
|
||||
}
|
||||
pub struct DesktopFileParser {
|
||||
desktop_file: DesktopFile,
|
||||
exec_prefix: String,
|
||||
seen_header: bool,
|
||||
current_action: Option<String>,
|
||||
in_ignored_group: bool,
|
||||
known_actions: HashSet<String>,
|
||||
}
|
||||
|
||||
|
||||
impl DesktopFileParser {
|
||||
fn new(filename: &str, exec_prefix: &str) -> DesktopFileParser {
|
||||
DesktopFileParser {
|
||||
desktop_file: DesktopFile::new(filename),
|
||||
exec_prefix: exec_prefix.to_string(),
|
||||
seen_header: false,
|
||||
current_action: None,
|
||||
in_ignored_group: false,
|
||||
known_actions: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_from_path<P: AsRef<Path>>(path: P, exec_prefix: &str) -> Result<DesktopFile> {
|
||||
let filename = filename_from_path(path.as_ref())?;
|
||||
let f = File::open(path.as_ref())?;
|
||||
DesktopFileParser::parse_from_reader(f, filename, exec_prefix)
|
||||
}
|
||||
|
||||
fn parse_from_reader<T: Read>(mut r: T, filename: &str, exec_prefix: &str) -> Result<DesktopFile> {
|
||||
let mut buffer = String::new();
|
||||
r.read_to_string(&mut buffer)?;
|
||||
DesktopFileParser::parse_from_string(&buffer, filename, exec_prefix)
|
||||
}
|
||||
|
||||
fn parse_from_string(body: &str, filename: &str, exec_prefix: &str) -> Result<DesktopFile> {
|
||||
let mut parser = DesktopFileParser::new(filename, exec_prefix);
|
||||
for s in body.lines() {
|
||||
match LineParser::parse(s) {
|
||||
Some(line) => parser.process_line(line)?,
|
||||
None => return Err(format_err!("Failed to parse line: '{}'", s))
|
||||
}
|
||||
}
|
||||
Ok(parser.desktop_file)
|
||||
}
|
||||
|
||||
fn process_initial(&mut self, line: Line) -> Result<()> {
|
||||
match line {
|
||||
Line::Comment(_) | Line::Empty => {},
|
||||
Line::DesktopHeader => self.seen_header = true,
|
||||
_ => return Err(format_err!("Missing Desktop Entry header"))
|
||||
}
|
||||
self.desktop_file.add_line(line);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_line(&mut self, mut line: Line) -> Result<()> {
|
||||
if self.in_ignored_group && !line.is_action_header() {
|
||||
return Ok(())
|
||||
}
|
||||
if !self.seen_header {
|
||||
return self.process_initial(line)
|
||||
}
|
||||
|
||||
if let Line::KeyValue(ref k, ref value) = line {
|
||||
if k == "Actions" {
|
||||
for s in value.split_terminator(";") {
|
||||
self.known_actions.insert(s.trim().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
match line {
|
||||
Line::ExecLine(ref mut s) => {
|
||||
s.insert_str(0,self.exec_prefix.as_str())
|
||||
},
|
||||
Line::DesktopHeader => return Err(format_err!("Duplicate Desktop Entry header")),
|
||||
Line::ActionHeader(ref action) => {
|
||||
if self.known_actions.contains(action) {
|
||||
self.current_action = Some(action.to_string());
|
||||
self.in_ignored_group = false;
|
||||
} else {
|
||||
return Err(format_err!("Desktop Action header with undecleared action: {}", action))
|
||||
}
|
||||
},
|
||||
Line::GroupHeader(_) => {
|
||||
self.in_ignored_group = true;
|
||||
return Ok(())
|
||||
},
|
||||
Line::KeyLocaleValue(ref k,_,_) | Line::KeyValue(ref k,_) => {
|
||||
|
||||
if !is_whitelisted_key(k) {
|
||||
if !KEY_IGNORELIST.contains(k.as_str()) {
|
||||
info!("Unknown key in {}: {}", self.desktop_file.filename(), k);
|
||||
}
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
if let Some(ref action) = self.current_action {
|
||||
self.desktop_file.add_action_line(action, line)
|
||||
} else {
|
||||
self.desktop_file.add_line(line);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const DESKTOP_ACTION: &'static str = "Desktop Action ";
|
||||
|
||||
struct LineParser<'a> {
|
||||
s: &'a str,
|
||||
}
|
||||
|
||||
impl <'a> LineParser<'a> {
|
||||
fn new(s: &'a str) -> LineParser<'a> {
|
||||
LineParser {
|
||||
s,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(s: &'a str) -> Option<Line> {
|
||||
if let Some(line) = LineParser::new(s)._parse() {
|
||||
if validate_line(&line) {
|
||||
return Some(line)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn first(&self) -> Option<char> {
|
||||
self.s.chars().next()
|
||||
}
|
||||
|
||||
fn last(&self) -> Option<char> {
|
||||
self.s.chars().next_back()
|
||||
}
|
||||
|
||||
fn _parse(&mut self) -> Option<Line> {
|
||||
match self.first() {
|
||||
None => Some(Line::Empty),
|
||||
Some('#') => Some(Line::Comment(self.s[1..].to_string())),
|
||||
Some('[') => self.parse_header(),
|
||||
Some(_) => self.parse_keyval(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_header(&mut self) -> Option<Line> {
|
||||
if self.last().unwrap() != ']' {
|
||||
return None
|
||||
}
|
||||
let content = &self.s[1..self.s.len() - 1];
|
||||
if content.starts_with(DESKTOP_ACTION) {
|
||||
let action = &content[DESKTOP_ACTION.len()..];
|
||||
return Some(Line::ActionHeader(action.to_string()))
|
||||
} else if content == "Desktop Entry" {
|
||||
return Some(Line::DesktopHeader)
|
||||
}
|
||||
return Some(Line::GroupHeader(content.to_string()))
|
||||
}
|
||||
|
||||
fn parse_keyval(&self) -> Option<Line> {
|
||||
let parts: Vec<&str> = self.s.splitn(2, "=").collect();
|
||||
if parts.len() != 2 {
|
||||
return None
|
||||
}
|
||||
let key = parts[0].trim();
|
||||
let val = parts[1].trim();
|
||||
if !key.contains("[") {
|
||||
if key == "Exec" {
|
||||
return Some(Line::ExecLine(val.to_string()))
|
||||
}
|
||||
return Some(Line::KeyValue(key.to_string(), val.to_string()))
|
||||
}
|
||||
self.parse_locale(key).map(|(key,locale)| Line::KeyLocaleValue(key, locale, val.to_string()))
|
||||
}
|
||||
|
||||
fn parse_locale(&self, key: &str) -> Option<(String,String)> {
|
||||
let idx = key.find("[").unwrap();
|
||||
let (k,loc) = key.split_at(idx);
|
||||
let mut chars = loc.chars();
|
||||
if let Some(']') = chars.next_back() {
|
||||
chars.next();
|
||||
if k.trim() == "Exec" {
|
||||
// Exec key with locale not allowed
|
||||
return None;
|
||||
}
|
||||
return Some((k.trim().to_string(), chars.as_str().to_string()))
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_alphanum_or_dash(c: char) -> bool {
|
||||
is_ascii(c) && (c.is_alphanumeric() || c == '-')
|
||||
}
|
||||
|
||||
fn is_ascii(c: char) -> bool {
|
||||
c as u32 <= 0x7F
|
||||
}
|
||||
|
||||
fn is_first_char_alphabetic(s: &str) -> bool {
|
||||
if let Some(c) = s.chars().next() {
|
||||
return is_ascii(c) && c.is_alphabetic()
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_valid_key(key: &str) -> bool {
|
||||
if !is_first_char_alphabetic(key) {
|
||||
return false
|
||||
}
|
||||
key.chars().all(is_alphanum_or_dash)
|
||||
}
|
||||
|
||||
fn is_valid_locale(locale: &str) -> bool {
|
||||
!locale.is_empty() && locale.chars().all(|c| {
|
||||
is_alphanum_or_dash(c) || c == '_' || c == '.' || c == '@'
|
||||
})
|
||||
}
|
||||
|
||||
fn is_valid_value(value: &str) -> bool {
|
||||
value.chars().all(|c| {
|
||||
!(c.is_control() || c as u32 == 0 )
|
||||
})
|
||||
}
|
||||
|
||||
fn is_valid_action(action: &str) -> bool {
|
||||
is_first_char_alphabetic(action) && action.chars().all(is_alphanum_or_dash)
|
||||
}
|
||||
|
||||
fn is_valid_group(group: &str) -> bool {
|
||||
is_first_char_alphabetic(group) && group.chars().all(|c| {
|
||||
is_ascii(c) && !c.is_control()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_valid_exec(val: &str) -> bool {
|
||||
val.chars().all(|c| {
|
||||
is_ascii(c) && !(c.is_control() || c as u32 == 0)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_line(line: &Line) -> bool {
|
||||
match *line {
|
||||
Line::ExecLine(ref s) => is_valid_exec(s),
|
||||
Line::KeyValue(ref k, ref v) => is_valid_key(k) && is_valid_value(v),
|
||||
Line::KeyLocaleValue(ref k, ref l, ref v) => is_valid_key(k) && is_valid_locale(l) && is_valid_value(v),
|
||||
Line::ActionHeader(ref action) => is_valid_action(action),
|
||||
Line::GroupHeader(ref group) => is_valid_group(group),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
let tests = vec!["###", "", "# hello", "[Desktop Entry]", "[Desktop Action foo]", "Foo=Bar", "Foo[hehe]=Lol"];
|
||||
for t in tests {
|
||||
println!("{:?}", LineParser::parse(t));
|
||||
}
|
||||
}
|
3
citadel-tools/citadel-rootfs/.gitignore
vendored
Normal file
3
citadel-tools/citadel-rootfs/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
**/target/
|
||||
**/*.rs.bk
|
||||
.idea/
|
535
citadel-tools/citadel-rootfs/Cargo.lock
generated
Normal file
535
citadel-tools/citadel-rootfs/Cargo.lock
generated
Normal file
@ -0,0 +1,535 @@
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.4"
|
||||
source = "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.6 (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 = "block-buffer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.4 (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 = "build_const"
|
||||
version = "0.2.0"
|
||||
source = "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.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 = "citadel-rootfs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ed25519-dalek 0.6.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.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lzma-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.6 (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 = "clear_on_drop"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.4 (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.2 (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.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.7.2"
|
||||
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.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"curve25519-dalek 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.1 (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.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 0.5.1 (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 = "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.1 (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 = "gcc"
|
||||
version = "0.3.54"
|
||||
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.9.0 (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 = "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 = "lzma-rs"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (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-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 = "rand"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (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.6"
|
||||
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 = "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 = "sha2"
|
||||
version = "0.7.0"
|
||||
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.2 (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 = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "0.5.1"
|
||||
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 = "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 = "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 = "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.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.9.0"
|
||||
source = "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 = "vec_map"
|
||||
version = "0.8.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"
|
||||
|
||||
[metadata]
|
||||
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
|
||||
"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f"
|
||||
"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 block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
|
||||
"checksum build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e90dc84f5e62d2ebe7676b83c22d33b6db8bd27340fb6ffbff0a364efa0cb9c9"
|
||||
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
|
||||
"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 clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1c07b9257a00f3fc93b7f3c417fc15607ec7a56823bc2c37ec744e266387de5b"
|
||||
"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17"
|
||||
"checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7"
|
||||
"checksum curve25519-dalek 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6734ff1a930d90b3ee54b7d6eba1b520f8724a1f353cf4f2b4b171a9ce63d814"
|
||||
"checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603"
|
||||
"checksum ed25519-dalek 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3692ef38cc617236a39120ef0b91794e5e4d5c96227607a6740bfaaab53ac3c"
|
||||
"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 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 gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
|
||||
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
|
||||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
"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 lzma-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d49cc18d2e4235afb294250b5eae3c8ac150fc3662fd46fbb7f48c6cb6567b7"
|
||||
"checksum nix 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fd5681d13fda646462cfbd4e5f2051279a89a544d50eb98c365b507246839f"
|
||||
"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 rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
||||
"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.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f312457f8a4fa31d3581a6f423a70d6c33a10b95291985df55f1ff670ec10ce8"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"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 sha2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7daca11f2fdb8559c4f6c588386bed5e2ad4b6605c1442935a7f08144a918688"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum subtle 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7a6bab57c3efd01ebd3d750f4244ae0af4cdd1fc505a7904a41603192b803c5"
|
||||
"checksum subtle 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc7f6353c2ee5407358d063a14cccc1630804527090a6fb5a9489ce4924280fb"
|
||||
"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 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 typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd"
|
||||
"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 vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
||||
"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"
|
19
citadel-tools/citadel-rootfs/Cargo.toml
Normal file
19
citadel-tools/citadel-rootfs/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "citadel-rootfs"
|
||||
version = "0.1.0"
|
||||
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
homepage = "https://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.30.0"
|
||||
failure = "0.1.1"
|
||||
libc = "0.2"
|
||||
nix = "0.10.0"
|
||||
ed25519-dalek = "^0.6"
|
||||
rand = "0.4.2"
|
||||
sha2 = "0.7.0"
|
||||
toml = "0.4.5"
|
||||
serde_derive = "1.0.27"
|
||||
serde = "1.0.27"
|
||||
rustc-serialize = "0.3.24"
|
||||
lzma-rs = "0.1.0"
|
13
citadel-tools/citadel-rootfs/conf/citadel-rootfs.conf
Normal file
13
citadel-tools/citadel-rootfs/conf/citadel-rootfs.conf
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
# This is where images to be installed are stored
|
||||
citadel_updates = "/storage/citadel-updates"
|
||||
|
||||
kernel_updates = "/storage/kernel-updates"
|
||||
|
||||
# This is where update images are built
|
||||
image_builds = "/storage/image-builds"
|
||||
|
||||
[channel.test]
|
||||
update_server = ""
|
||||
pubkey = "7a6743a61cff946083f2496c4df0e5afb958c84eec3185e63cbe7a695c20e732"
|
154
citadel-tools/citadel-rootfs/src/blockdev.rs
Normal file
154
citadel-tools/citadel-rootfs/src/blockdev.rs
Normal file
@ -0,0 +1,154 @@
|
||||
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;
|
||||
use util::path_str;
|
||||
|
||||
const REQUIRED_ALIGNMENT: usize = 4096;
|
||||
const DEFAULT_ALIGNMENT: usize = REQUIRED_ALIGNMENT;
|
||||
|
||||
pub struct AlignedBuffer {
|
||||
buffer: Vec<u8>,
|
||||
alignment: usize,
|
||||
size: usize,
|
||||
align_offset: usize,
|
||||
}
|
||||
|
||||
impl AlignedBuffer {
|
||||
|
||||
pub fn new(size: usize) -> AlignedBuffer {
|
||||
AlignedBuffer::new_with_alignment(size, DEFAULT_ALIGNMENT)
|
||||
}
|
||||
|
||||
pub fn from_slice(bytes: &[u8]) -> AlignedBuffer {
|
||||
AlignedBuffer::from_slice_with_alignment(bytes, DEFAULT_ALIGNMENT)
|
||||
}
|
||||
|
||||
pub fn new_with_alignment(size: usize, alignment: usize) -> AlignedBuffer {
|
||||
AlignedBuffer {
|
||||
alignment, size,
|
||||
buffer: vec![0; 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 with 0x12, 114; u64);
|
||||
|
||||
pub struct BlockDev {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl BlockDev {
|
||||
pub fn open_ro<P: AsRef<Path>>(path: P) -> Result<BlockDev> {
|
||||
BlockDev::open(path.as_ref(), false)
|
||||
}
|
||||
pub fn open_rw<P: AsRef<Path>>(path: P) -> Result<BlockDev> {
|
||||
BlockDev::open(path.as_ref(), true)
|
||||
}
|
||||
|
||||
fn open(path: &Path, write: bool) -> Result<BlockDev> {
|
||||
let mut oo = OpenOptions::new();
|
||||
oo.read(true);
|
||||
oo.custom_flags(libc::O_DIRECT | libc::O_SYNC);
|
||||
if write {
|
||||
oo.write(true);
|
||||
}
|
||||
let file = oo.open(path)
|
||||
.map_err(|e| format_err!("Failed to open block device {}: {}", path_str(path), e))?;
|
||||
Ok(BlockDev{file})
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Result<u64> {
|
||||
let mut sz = 0u64;
|
||||
unsafe {
|
||||
blk_getsize64(self.file.as_raw_fd(), &mut sz)
|
||||
.map_err(|e| format_err!("Error calling getsize ioctl on block device: {}", e))?;
|
||||
}
|
||||
Ok(sz)
|
||||
}
|
||||
|
||||
pub fn nsectors(&self) -> Result<usize> {
|
||||
Ok((self.size()? as usize) >> 9)
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
pub fn read_sectors(&mut self, offset: usize, buffer: &mut [u8]) -> Result<()> {
|
||||
self.setup_io(offset, buffer)?;
|
||||
self.file.read_exact(buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_sectors(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
|
||||
self.setup_io(offset, buffer)?;
|
||||
self.file.write_all(buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
100
citadel-tools/citadel-rootfs/src/boot.rs
Normal file
100
citadel-tools/citadel-rootfs/src/boot.rs
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
|
||||
use {Result,Partition,Config};
|
||||
|
||||
pub struct BootSelection {
|
||||
partitions: Vec<Partition>,
|
||||
}
|
||||
|
||||
impl BootSelection {
|
||||
pub fn load_partitions() -> Result<BootSelection> {
|
||||
let partitions = Partition::rootfs_partitions()
|
||||
.map_err(|e| format_err!("Could not load rootfs partition info: {}", e))?;
|
||||
|
||||
Ok(BootSelection {
|
||||
partitions
|
||||
})
|
||||
}
|
||||
|
||||
pub fn choose_install_partition(&self) -> Option<&Partition> {
|
||||
self.choose(|p| {
|
||||
// first pass, if there is a partition which is not mounted and
|
||||
// not initialized use that one
|
||||
!p.is_mounted() && !p.is_initialized()
|
||||
}).or_else(|| self.choose(|p| {
|
||||
// second pass, just find one that's not mounted
|
||||
!p.is_mounted()
|
||||
}))
|
||||
}
|
||||
|
||||
fn choose<F>(&self, pred: F) -> Option<&Partition>
|
||||
where F: Sized + Fn(&&Partition) -> bool
|
||||
{
|
||||
self.partitions.iter().find(pred)
|
||||
}
|
||||
|
||||
/// Find the best rootfs partition to boot from
|
||||
pub 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(&self, config: &Config) -> Result<()> {
|
||||
for p in &self.partitions {
|
||||
if let Err(e) = p.boot_scan(config) {
|
||||
warn!("error in bootscan of partition {}: {}", p.path_str(), e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_better<'a>(current_best: &Option<&'a Partition>, other: &'a Partition) -> bool {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// These are guaranteed to unwrap()
|
||||
let best_version = best.metainfo().unwrap().version();
|
||||
let other_version = other.metainfo().unwrap().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
|
||||
}
|
138
citadel-tools/citadel-rootfs/src/config.rs
Normal file
138
citadel-tools/citadel-rootfs/src/config.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ed25519_dalek::{Signature,PublicKey,Keypair};
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use sha2::Sha512;
|
||||
use toml;
|
||||
|
||||
use util::{read_file_as_string,path_str};
|
||||
use Result;
|
||||
|
||||
|
||||
const DEFAULT_CONFIG_PATH: &str = "/usr/share/citadel/citadel-rootfs.conf";
|
||||
fn default_citadel_updates() -> String { "/storage/citadel-updates".to_string() }
|
||||
fn default_kernel_updates() -> String { "/storage/kernel-updates".to_string() }
|
||||
fn default_image_builds() -> String { "/storage/image-builds".to_string() }
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
default_channel: Option<String>,
|
||||
|
||||
#[serde (default= "default_citadel_updates")]
|
||||
citadel_updates: String,
|
||||
|
||||
#[serde (default = "default_kernel_updates")]
|
||||
kernel_updates: String,
|
||||
|
||||
#[serde (default = "default_image_builds")]
|
||||
image_builds: String,
|
||||
|
||||
channel: HashMap<String, Channel>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
||||
pub fn load_default() -> Result<Config> {
|
||||
Config::load(DEFAULT_CONFIG_PATH)
|
||||
}
|
||||
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||
let config = match Config::from_path(path.as_ref()) {
|
||||
Ok(config) => config,
|
||||
Err(e) => bail!("Failed to load config file {}: {}", path_str(path.as_ref()), e),
|
||||
};
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn from_path(path: &Path) -> Result<Config> {
|
||||
let s = read_file_as_string(path.as_ref())?;
|
||||
let mut config = toml::from_str::<Config>(&s)?;
|
||||
for (k,v) in config.channel.iter_mut() {
|
||||
v.name = k.to_string();
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn get_default_channel(&self) -> Option<Channel> {
|
||||
|
||||
if let Some(ref name) = self.default_channel {
|
||||
if let Some(c) = self.channel(name) {
|
||||
return Some(c);
|
||||
}
|
||||
}
|
||||
|
||||
if self.channel.len() == 1 {
|
||||
return self.channel.values().next().map(|c| c.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn channel(&self, name: &str) -> Option<Channel> {
|
||||
self.channel.get(name).map(|c| c.clone() )
|
||||
}
|
||||
|
||||
pub fn citadel_updates_base(&self) -> &str {
|
||||
&self.citadel_updates
|
||||
}
|
||||
|
||||
pub fn kernel_updates_base(&self) -> &str {
|
||||
&self.kernel_updates
|
||||
}
|
||||
|
||||
pub fn image_builds_base(&self) -> &str {
|
||||
&self.image_builds
|
||||
}
|
||||
|
||||
pub fn get_private_key(&self, channel: &str) -> Option<String> {
|
||||
if let Some(channel_config) = self.channel.get(channel) {
|
||||
if let Some(ref key) = channel_config.keypair {
|
||||
return Some(key.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_public_key(&self, channel: &str) -> Option<String> {
|
||||
if let Some(channel_config) = self.channel.get(channel) {
|
||||
return Some(channel_config.pubkey.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Clone)]
|
||||
pub struct Channel {
|
||||
update_server: Option<String>,
|
||||
pubkey: String,
|
||||
keypair: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn sign(&self, data: &[u8]) -> Result<Signature> {
|
||||
let keybytes = match self.keypair {
|
||||
Some(ref hex) => hex.from_hex()?,
|
||||
None => bail!("No private signing key available for channel {}", self.name),
|
||||
};
|
||||
let privkey = Keypair::from_bytes(&keybytes)?;
|
||||
let sig = privkey.sign::<Sha512>(data);
|
||||
Ok(sig)
|
||||
}
|
||||
|
||||
pub fn verify(&self, data: &[u8], sigbytes: &[u8]) -> Result<bool> {
|
||||
let keybytes = self.pubkey.from_hex()?;
|
||||
let pubkey = PublicKey::from_bytes(&keybytes)?;
|
||||
let sig = Signature::from_bytes(sigbytes)?;
|
||||
Ok(pubkey.verify::<Sha512>(data, &sig))
|
||||
}
|
||||
|
||||
}
|
||||
|
290
citadel-tools/citadel-rootfs/src/main.rs
Normal file
290
citadel-tools/citadel-rootfs/src/main.rs
Normal file
@ -0,0 +1,290 @@
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate nix;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
|
||||
extern crate libc;
|
||||
extern crate clap;
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
extern crate ed25519_dalek;
|
||||
extern crate sha2;
|
||||
extern crate rand;
|
||||
extern crate rustc_serialize;
|
||||
|
||||
|
||||
thread_local! {
|
||||
pub static VERBOSE: RefCell<bool> = RefCell::new(false);
|
||||
pub static SYSOP: RefCell<bool> = RefCell::new(false);
|
||||
}
|
||||
|
||||
pub fn verbose() -> bool {
|
||||
VERBOSE.with(|f| {
|
||||
*f.borrow()
|
||||
})
|
||||
}
|
||||
|
||||
fn sysop() -> bool {
|
||||
SYSOP.with(|f| {
|
||||
*f.borrow()
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! info {
|
||||
($e:expr) => { if ::verbose() { println!("[+] {}", $e);} };
|
||||
($fmt:expr, $($arg:tt)+) => { if ::verbose() { println!("[+] {}", format!($fmt, $($arg)+));} };
|
||||
}
|
||||
macro_rules! warn {
|
||||
($e:expr) => { if ::verbose() { println!("WARNING: {}", $e);} };
|
||||
($fmt:expr, $($arg:tt)+) => { if ::verbose() { println!("WARNING: {}", format!($fmt, $($arg)+));} };
|
||||
}
|
||||
|
||||
macro_rules! notify {
|
||||
($e:expr) => { println!("[+] {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("[+] {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
use std::result;
|
||||
use std::process::exit;
|
||||
|
||||
use failure::Error;
|
||||
use clap::{App,Arg,ArgMatches, SubCommand};
|
||||
use clap::AppSettings::*;
|
||||
use sha2::Sha512;
|
||||
use rand::OsRng;
|
||||
use ed25519_dalek::Keypair;
|
||||
use rustc_serialize::hex::ToHex;
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
|
||||
|
||||
pub use config::Config;
|
||||
pub use metainfo::Metainfo;
|
||||
pub use blockdev::BlockDev;
|
||||
pub use partition::{Partition,MAX_METAINFO_LEN};
|
||||
use unpacker::UpdateImageUnpacker;
|
||||
use packer::UpdateImagePacker;
|
||||
use boot::BootSelection;
|
||||
|
||||
mod boot;
|
||||
mod metainfo;
|
||||
mod partition;
|
||||
mod blockdev;
|
||||
mod config;
|
||||
mod packer;
|
||||
mod unpacker;
|
||||
mod util;
|
||||
|
||||
pub type Result<T> = result::Result<T,Error>;
|
||||
|
||||
fn main() {
|
||||
match env::var("CITADEL_SYSOP") {
|
||||
Ok(_) => SYSOP.with(|f| *f.borrow_mut() = true),
|
||||
_ => {},
|
||||
};
|
||||
|
||||
let mut app = App::new("citadel-rootfs")
|
||||
.about("Subgraph Citadel rootfs partition management")
|
||||
.settings(&[ArgRequiredElseHelp, ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder])
|
||||
.arg(Arg::with_name("v")
|
||||
.help("Verbose output")
|
||||
.short("v")
|
||||
.long("verbose"))
|
||||
.arg(Arg::with_name("config")
|
||||
.help("Optionally specify an alternate config file")
|
||||
.takes_value(true)
|
||||
.short("c") .long("config"))
|
||||
|
||||
.subcommand(SubCommand::with_name("list")
|
||||
.about("Show information about all rootfs partitions"))
|
||||
|
||||
.subcommand(SubCommand::with_name("which-boot")
|
||||
.about("Show which rootfs paritition would currently boot according to the boot selection algorithm"))
|
||||
|
||||
.subcommand(SubCommand::with_name("verify-update")
|
||||
.about("Verify the signature of an update image"))
|
||||
|
||||
.subcommand(SubCommand::with_name("update")
|
||||
.about("Download update if available and install")
|
||||
.arg(Arg::with_name("download-only")
|
||||
.help("Only download available update, don't install")
|
||||
.long("download")))
|
||||
|
||||
.subcommand(SubCommand::with_name("install-update")
|
||||
.about("Install an update image")
|
||||
.arg(Arg::with_name("image")
|
||||
.required(true)));
|
||||
if sysop() {
|
||||
|
||||
app = app.subcommand(SubCommand::with_name("build-update")
|
||||
.about("Create an update image from a raw citadel-image.ext2 file")
|
||||
.arg(Arg::with_name("image")
|
||||
.required(true)))
|
||||
|
||||
.subcommand(SubCommand::with_name("genkeys")
|
||||
.about("Generate a new update keypair"));
|
||||
|
||||
}
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
|
||||
let config = load_config(&matches);
|
||||
|
||||
if matches.is_present("v") {
|
||||
VERBOSE.with(|f| *f.borrow_mut() = true);
|
||||
}
|
||||
|
||||
let result = match matches.subcommand() {
|
||||
("list", Some(_)) => list_cmd(&config),
|
||||
("which-boot", Some(_)) => { which_boot_cmd(&config)},
|
||||
("update", Some(m)) => update_cmd(&config, m),
|
||||
("verify-update", Some(m)) => verify_update_cmd(&config, m),
|
||||
("install-update", Some(m)) => install_update_cmd(&config, m),
|
||||
("build-update", Some(m)) => build_update_cmd(&config, m),
|
||||
("genkeys", Some(_)) => genkeys_cmd(),
|
||||
("mount-rootfs", Some(m)) => mount_rootfs_cmd(&config, m),
|
||||
(s, Some(_)) => {info!("subcommand: {}", s); Ok(())},
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
println!("{}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_config(arg_matches: &ArgMatches) -> Config {
|
||||
let config_load = match arg_matches.value_of("config") {
|
||||
Some(path) => Config::load(path),
|
||||
None => Config::load_default(),
|
||||
};
|
||||
match config_load {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_cmd(_config: &Config) -> Result<()> {
|
||||
println!("{:^30} {:^14} {:^8} {:^8} {:^12}", "DEVICE PATH", "MOUNTED", "CHANNEL", "VERSION", "STATUS");
|
||||
for p in Partition::rootfs_partitions()? {
|
||||
let info = partition_info(&p);
|
||||
println!("{:^30} {:^14} {:^8} {:^8} {:^12}", info.0, info.1, info.2, info.3, info.4);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn partition_info(part: &Partition) -> (String,String,String,String,String) {
|
||||
let mounted = if part.is_mounted() { "[Yes]".to_string() } else { String::new() };
|
||||
let status = if part.is_initialized() { part.status_label() } else { "Not Initialized".to_string() };
|
||||
let (channel, version) = match part.metainfo() {
|
||||
Ok(meta) => (meta.channel().to_string(), meta.version().to_string()),
|
||||
_ => (String::new(), String::new()),
|
||||
};
|
||||
(part.path_str().to_string(), mounted, channel, version, status)
|
||||
}
|
||||
|
||||
fn mount_rootfs_cmd(_config: &Config, _matches: &ArgMatches) -> Result<()> {
|
||||
// mounting installer rootfs should happen here too?
|
||||
// perhaps based on cmd line flag
|
||||
// maybe define guid like the gpt generator looks for
|
||||
// for: rootfs inside luks, rootfs outside luks
|
||||
let bs = BootSelection::load_partitions()?;
|
||||
let _p = match bs.choose_boot_partition() {
|
||||
Some(p) => p,
|
||||
None => bail!("None of the rootfs partitions have a bootable image"),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn which_boot_cmd(_config: &Config) -> Result<()> {
|
||||
let select = BootSelection::load_partitions()?;
|
||||
match select.choose_boot_partition() {
|
||||
Some(part) => {
|
||||
notify!("Next boot will be from partition: {}", part.path_str());
|
||||
},
|
||||
None => {
|
||||
warn!("None of the rootfs partitions are currently in bootable state.");
|
||||
warn!("Unless a valid image is installed, computer will fail to boot");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_update_cmd(_config: &Config, _matches: &ArgMatches) -> Result<()> {
|
||||
println!("do verify_update");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_cmd(_config: &Config, _matches: &ArgMatches) -> Result<()> {
|
||||
// note to self, remember to verify that downloaded update image version matches the version
|
||||
// which is expected.
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
fn install_update_cmd(config: &Config, matches: &ArgMatches) -> Result<()> {
|
||||
let image_path = match matches.value_of("image") {
|
||||
Some(val) => val,
|
||||
None => bail!("install-update requires an image path"),
|
||||
};
|
||||
let unpack = UpdateImageUnpacker::open(image_path, config)?;
|
||||
info!("unpacking image channel: {} version: {}", unpack.metainfo().channel(), unpack.metainfo().version());
|
||||
unpack.unpack_disk_image()?;
|
||||
info!("decompressing image");
|
||||
unpack.decompress_disk_image()?;
|
||||
info!("verifying shasum");
|
||||
unpack.verify_shasum()?;
|
||||
|
||||
let bs = BootSelection::load_partitions()?;
|
||||
let p = match bs.choose_install_partition() {
|
||||
Some(p) => p,
|
||||
None => bail!("None of the rootfs partitions are available to install update to"),
|
||||
};
|
||||
info!("installing to {}", p.path_str());
|
||||
unpack.write_partition(p)?;
|
||||
notify!("Update image successfully installed to {}", p.path_str());
|
||||
|
||||
|
||||
//update::UpdateImage::new(path);
|
||||
// 1) read header, extract metainfo
|
||||
// 2) verify signature on metainfo
|
||||
// 3) determine if this version/channel makes sense to be installed
|
||||
// 4) xtrat image data to temporary file with https://crates.io/crates/lzma-rs
|
||||
// 5) verify sha256 on image data
|
||||
//
|
||||
// 6) choose rootfs partition
|
||||
// 7) write partition info block, setting status to INVALID
|
||||
// 8) write update image to device
|
||||
// 9) run verifyupdate using provided --salt
|
||||
// 10) re-rewrite parition info block with status NEW
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_update_cmd(config: &Config, matches: &ArgMatches) -> Result<()> {
|
||||
let image_path = match matches.value_of("image") {
|
||||
Some(val) => val,
|
||||
None => bail!("build-update requires an image path"),
|
||||
};
|
||||
let channel = match config.get_default_channel() {
|
||||
Some(ch) => ch,
|
||||
None => bail!("Could not determine default channel from config file"),
|
||||
};
|
||||
let mut builder = UpdateImagePacker::new(config, channel, image_path)?;
|
||||
builder.build()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn genkeys_cmd() -> Result<()> {
|
||||
let mut rng = OsRng::new()?;
|
||||
let keypair = Keypair::generate::<Sha512>(&mut rng);
|
||||
|
||||
println!("pubkey = \"{}\"", keypair.public.to_bytes().to_hex());
|
||||
println!("privkey = \"{}\"", keypair.to_bytes().to_hex());
|
||||
|
||||
Ok(())
|
||||
}
|
48
citadel-tools/citadel-rootfs/src/metainfo.rs
Normal file
48
citadel-tools/citadel-rootfs/src/metainfo.rs
Normal file
@ -0,0 +1,48 @@
|
||||
#[derive(Deserialize,Serialize,Clone)]
|
||||
pub struct Metainfo {
|
||||
channel: String,
|
||||
version: u32,
|
||||
base_version: u32,
|
||||
date: String,
|
||||
gitrev: String,
|
||||
nsectors: u32,
|
||||
shasum: String,
|
||||
verity_salt: String,
|
||||
verity_root: String,
|
||||
}
|
||||
|
||||
|
||||
impl Metainfo {
|
||||
|
||||
pub fn channel(&self) -> &str {
|
||||
&self.channel
|
||||
}
|
||||
|
||||
pub fn version(&self) -> u32 {
|
||||
self.version
|
||||
}
|
||||
|
||||
pub fn date(&self) -> &str {
|
||||
&self.date
|
||||
}
|
||||
|
||||
pub fn gitrev(&self) -> &str {
|
||||
&self.gitrev
|
||||
}
|
||||
|
||||
pub fn nsectors(&self) -> usize {
|
||||
self.nsectors as usize
|
||||
}
|
||||
|
||||
pub fn shasum(&self) -> &str {
|
||||
&self.shasum
|
||||
}
|
||||
|
||||
pub fn verity_root(&self) -> &str {
|
||||
&self.verity_root
|
||||
}
|
||||
|
||||
pub fn verity_salt(&self) -> &str {
|
||||
&self.verity_salt
|
||||
}
|
||||
}
|
209
citadel-tools/citadel-rootfs/src/packer.rs
Normal file
209
citadel-tools/citadel-rootfs/src/packer.rs
Normal file
@ -0,0 +1,209 @@
|
||||
|
||||
use std::path::Path;
|
||||
use std::io::{self,Write};
|
||||
use std::fs::{File,OpenOptions};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use config::{Config,Channel};
|
||||
use util::*;
|
||||
|
||||
use Result;
|
||||
|
||||
const IMAGE_FILENAME: &str = "citadel-image.ext2";
|
||||
|
||||
///
|
||||
///
|
||||
pub struct UpdateImagePacker {
|
||||
workdir: Workdir,
|
||||
version: usize,
|
||||
channel: Channel,
|
||||
nsectors: usize,
|
||||
verity_salt: String,
|
||||
verity_root: String,
|
||||
shasum: String,
|
||||
header: Vec<u8>,
|
||||
}
|
||||
|
||||
impl UpdateImagePacker {
|
||||
pub fn new(config: &Config, channel: Channel, image_path: &str) -> Result<UpdateImagePacker> {
|
||||
let mut workdir = Workdir::new(config.image_builds_base(), channel.name());
|
||||
let version = workdir.find_next_version()?;
|
||||
|
||||
sanity_check_source(image_path)?;
|
||||
let mut from = File::open(image_path)?;
|
||||
let mut to = File::create(workdir.filepath(IMAGE_FILENAME))?;
|
||||
io::copy(&mut from, &mut to)?;
|
||||
|
||||
Ok(UpdateImagePacker {
|
||||
workdir, version,
|
||||
channel: channel.to_owned(),
|
||||
nsectors: 0,
|
||||
verity_salt: String::new(),
|
||||
verity_root: String::new(),
|
||||
shasum: String::new(),
|
||||
header: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Result<()> {
|
||||
self.pad_image(4096)?;
|
||||
let meta = self.workdir.filepath(IMAGE_FILENAME).metadata()?;
|
||||
self.nsectors = (meta.len() / 512) as usize;
|
||||
self.build_verity()?;
|
||||
self.calculate_image_shasum()?;
|
||||
self.build_update_header()?;
|
||||
self.compress_image()?;
|
||||
self.write_update_image()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pad_image(&self, size: usize) -> Result<()> {
|
||||
let path = self.workdir.filepath(IMAGE_FILENAME);
|
||||
let meta = path.metadata()?;
|
||||
let rem = (meta.len() as usize) % size;
|
||||
if rem == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let padlen = size - rem;
|
||||
info!("padding image with {} bytes", padlen);
|
||||
let zeros = vec![0u8; padlen];
|
||||
|
||||
let mut file = OpenOptions::new().append(true).open(&path)?;
|
||||
file.write_all(&zeros)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_verity(&mut self) -> Result<()> {
|
||||
info!("Building dm-verity hash tree");
|
||||
let verity_output = run_verityformat_command(&self.workdir.filepath(IMAGE_FILENAME), &self.workdir.filepath("verifyhash.out"))?;
|
||||
write_string_to_file(&self.workdir.filepath("verityinfo"), &verity_output)?;
|
||||
|
||||
let map = UpdateImagePacker::parse_verity_output(&verity_output);
|
||||
|
||||
self.verity_root = match map.get("Root hash") {
|
||||
Some(v) => v.to_owned(),
|
||||
None => bail!("No root hash found in veritysetup output"),
|
||||
};
|
||||
|
||||
self.verity_salt = match map.get("Salt") {
|
||||
Some(v) => v.to_owned(),
|
||||
None => bail!("No Salt found in veritysetup output"),
|
||||
};
|
||||
|
||||
info!("Verity root: {}", self.verity_root);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_image_shasum(&mut self) -> Result<()> {
|
||||
info!("Calculating sha256 digest over image file");
|
||||
self.shasum = run_sha256_command(&self.workdir.filepath(IMAGE_FILENAME))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_verity_output(output: &str) -> HashMap<String,String> {
|
||||
let mut map = HashMap::new();
|
||||
for line in output.lines() {
|
||||
if let Some((k,v)) = UpdateImagePacker::parse_verity_line(line) {
|
||||
map.insert(k, v);
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
fn parse_verity_line(line: &str) -> Option<(String,String)> {
|
||||
let v = line.split(':').map(|s| s.trim())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
if v.len() == 2 {
|
||||
Some((v[0].to_string(), v[1].to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn build_update_header(&mut self) -> Result<()> {
|
||||
info!("Creating update image header");
|
||||
let metainfo = self.generate_metainfo()?;
|
||||
|
||||
let mut f = File::create(self.workdir.filepath("metainfo"))?;
|
||||
f.write_all(&metainfo)?;
|
||||
|
||||
let sig = self.channel.sign(&metainfo)?;
|
||||
|
||||
let mut szbuf = [0u8; 2];
|
||||
szbuf[0] = (metainfo.len() >> 8) as u8;
|
||||
szbuf[1] = metainfo.len() as u8;
|
||||
|
||||
self.header.write_all(b"UPDT")?;
|
||||
self.header.write_all(&szbuf)?;
|
||||
self.header.write_all(&metainfo)?;
|
||||
self.header.write_all(&sig.to_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_metainfo(&self) -> Result<Vec<u8>> {
|
||||
let mut v = Vec::new();
|
||||
writeln!(v, "channel = \"{}\"", self.channel.name())?;
|
||||
writeln!(v, "version = {}", self.version)?;
|
||||
writeln!(v, "base_version = {}", self.version)?;
|
||||
writeln!(v, "date = \"\"")?;
|
||||
writeln!(v, "gitrev = \"\"")?;
|
||||
writeln!(v, "nsectors = {}", self.nsectors)?;
|
||||
|
||||
writeln!(v, "shasum = \"{}\"", self.shasum)?;
|
||||
writeln!(v, "verity_salt = \"{}\"", self.verity_salt)?;
|
||||
writeln!(v, "verity_root = \"{}\"", self.verity_root)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
fn compress_image(&self) -> Result<()> {
|
||||
info!("Compressing image file");
|
||||
run_xz_command(&self.workdir.filepath(IMAGE_FILENAME), false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_update_image(&self) -> Result<()> {
|
||||
let img_path = self.workdir.filepath("citadel-update.img");
|
||||
info!("writing update image to {}", path_str(&img_path));
|
||||
let mut out = File::create(&img_path)?;
|
||||
|
||||
out.write_all(self.header.as_slice())?;
|
||||
|
||||
let mut image = File::open(self.workdir.filepath("citadel-image.ext2.xz"))?;
|
||||
io::copy(&mut image, &mut out)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn sanity_check_source<P: AsRef<Path>>(src: P) -> Result<()> {
|
||||
let src: &Path = src.as_ref();
|
||||
let meta = match src.metadata() {
|
||||
Ok(md) => md,
|
||||
Err(e) => bail!("Could not load image file {}: {}", path_str(src), e),
|
||||
};
|
||||
|
||||
if !meta.file_type().is_file() {
|
||||
bail!("Image file {} exists but is not a regular file");
|
||||
}
|
||||
|
||||
let filetype = match run_file_command(&src) {
|
||||
Ok(s) => s,
|
||||
Err(e) => bail!("{}", e),
|
||||
};
|
||||
|
||||
if filetype.starts_with("XZ") {
|
||||
bail!("Image file is compressed, decompress first");
|
||||
} else if !filetype.starts_with("Linux rev 1.0 ext2 filesystem data") {
|
||||
bail!("Image file is not an ext2 filesystem as expected:\n {}",
|
||||
filetype.trim_right());
|
||||
}
|
||||
|
||||
if meta.len() % 512 != 0 {
|
||||
bail!("Image file size is not a multiple of sector size (512 bytes)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
481
citadel-tools/citadel-rootfs/src/partition.rs
Normal file
481
citadel-tools/citadel-rootfs/src/partition.rs
Normal file
@ -0,0 +1,481 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::str;
|
||||
use std::cell::{RefCell,Ref};
|
||||
|
||||
use toml;
|
||||
use ed25519_dalek::SIGNATURE_LENGTH;
|
||||
|
||||
use Result;
|
||||
use Metainfo;
|
||||
use BlockDev;
|
||||
use Config;
|
||||
use blockdev::AlignedBuffer;
|
||||
use util::*;
|
||||
|
||||
const MAGIC: &[u8] = b"CTDL";
|
||||
|
||||
/// Size in bytes of the Partition Info Block
|
||||
const BLOCK_SIZE: usize = 4096;
|
||||
|
||||
/// Size of the PIB header up to the metainfo data
|
||||
const HEADER_SIZE: usize = 8;
|
||||
|
||||
/// The maximum length of metainfo data in a Partition Info Block.
|
||||
/// This size is the entire block minus the header and signature.
|
||||
pub const MAX_METAINFO_LEN: usize = BLOCK_SIZE - (HEADER_SIZE + SIGNATURE_LENGTH);
|
||||
|
||||
///
|
||||
/// Flag to override the algorithm which selects a partition to mount during boot
|
||||
///
|
||||
const FLAG_PREFER: u8 = 1;
|
||||
|
||||
///
|
||||
/// The last 4096 bytes of a rootfs block device stores a structure
|
||||
/// called the Partition Info Block.
|
||||
///
|
||||
/// The layout of this structure is the following:
|
||||
///
|
||||
/// field size (bytes) offset
|
||||
/// ----- ------------ ------
|
||||
///
|
||||
/// magic 4 0
|
||||
/// status 1 4
|
||||
/// flags 1 5
|
||||
/// length 2 6
|
||||
///
|
||||
/// metainfo <length> 8
|
||||
///
|
||||
/// signature 64 8 + length
|
||||
///
|
||||
/// magic : Must match ascii bytes 'CTDL' for the block to be considered valid
|
||||
///
|
||||
/// status : See `PartitionStatus` for description of defined valid values
|
||||
///
|
||||
/// flags : Only one flag is defined, `FLAG_PREFER`
|
||||
///
|
||||
/// length : Big endian 16 bit size in bytes of metainfo field
|
||||
///
|
||||
/// metainfo : A utf-8 TOML document with various fields describing the rootfs image
|
||||
/// on this partition.
|
||||
///
|
||||
/// signature : ed25519 signature of the content of metainfo field
|
||||
///
|
||||
struct Infoblock(RefCell<Vec<u8>>);
|
||||
|
||||
impl Infoblock {
|
||||
fn new() -> Infoblock {
|
||||
let v = vec![0; BLOCK_SIZE];
|
||||
Infoblock(RefCell::new(v))
|
||||
}
|
||||
|
||||
fn reset(&self) {
|
||||
for b in &mut self.0.borrow_mut()[..] {
|
||||
*b = 0;
|
||||
}
|
||||
self.write_bytes(0, MAGIC);
|
||||
}
|
||||
|
||||
fn w8(&self, idx: usize, val: u8) {
|
||||
self.0.borrow_mut()[idx] = val;
|
||||
}
|
||||
|
||||
fn r8(&self, idx: usize) -> u8 {
|
||||
self.0.borrow()[idx]
|
||||
}
|
||||
|
||||
fn write_bytes(&self, offset: usize, data: &[u8]) {
|
||||
self.0.borrow_mut()[offset..offset+data.len()].copy_from_slice(data)
|
||||
}
|
||||
|
||||
fn read_bytes(&self, offset: usize, len: usize) -> Vec<u8> {
|
||||
Vec::from(&self.0.borrow()[offset..offset+len])
|
||||
}
|
||||
|
||||
fn status(&self) -> u8 {
|
||||
self.r8(4)
|
||||
}
|
||||
|
||||
fn set_status(&self, status: u8) {
|
||||
self.w8(4, status);
|
||||
}
|
||||
|
||||
fn has_status(&self, status: u8) -> bool {
|
||||
self.is_valid() && self.status() == status
|
||||
}
|
||||
|
||||
fn flags(&self) -> u8 {
|
||||
self.r8(5)
|
||||
}
|
||||
|
||||
fn has_flag(&self, flag: u8) -> bool {
|
||||
self.is_valid() && (self.flags() & flag) == flag
|
||||
}
|
||||
|
||||
/// Returns `true` if flag value changed
|
||||
fn set_flag(&self, flag: u8, value: bool) -> bool {
|
||||
let old = self.flags();
|
||||
|
||||
if value {
|
||||
self.w8(5, old | flag);
|
||||
} else {
|
||||
self.w8(5, old & !flag);
|
||||
}
|
||||
self.flags() == old
|
||||
}
|
||||
|
||||
fn metainfo_len(&self) -> usize {
|
||||
let high = self.r8(6) as usize;
|
||||
let low = self.r8(7) as usize;
|
||||
(high << 8) | low
|
||||
}
|
||||
|
||||
fn set_metainfo_len(&self, mlen: usize) {
|
||||
assert!(mlen <= MAX_METAINFO_LEN);
|
||||
let high = (mlen >> 8) as u8;
|
||||
let low = mlen as u8;
|
||||
self.w8(6, high);
|
||||
self.w8(7, low);
|
||||
}
|
||||
|
||||
fn write_metainfo(&self, metainfo: &[u8]) {
|
||||
self.set_metainfo_len(metainfo.len());
|
||||
self.write_bytes(8, metainfo);
|
||||
}
|
||||
|
||||
fn write_signature(&self, signature: &[u8]) {
|
||||
assert_eq!(signature.len(), SIGNATURE_LENGTH);
|
||||
self.write_bytes(8 + self.metainfo_len(), signature);
|
||||
}
|
||||
|
||||
fn read_metainfo(&self) -> Vec<u8> {
|
||||
assert!(self.is_valid());
|
||||
self.read_bytes(8, self.metainfo_len())
|
||||
}
|
||||
|
||||
fn read_signature(&self) -> Vec<u8> {
|
||||
assert!(self.is_valid());
|
||||
self.read_bytes(8 + self.metainfo_len(), SIGNATURE_LENGTH)
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
&self.0.borrow()[0..4] == MAGIC &&
|
||||
is_valid_status_code(self.status()) &&
|
||||
self.flags() & !FLAG_PREFER == 0 &&
|
||||
self.metainfo_len() > 0 && self.metainfo_len() <= MAX_METAINFO_LEN
|
||||
}
|
||||
|
||||
fn from_slice(&self, bytes: &[u8]) {
|
||||
self.0.borrow_mut().copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
fn as_ref(&self) -> Ref<Vec<u8>> {
|
||||
self.0.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Partition {
|
||||
path: PathBuf,
|
||||
is_mounted: bool,
|
||||
infoblock: Infoblock,
|
||||
}
|
||||
|
||||
impl Partition {
|
||||
///
|
||||
/// Return a `Vec` of all rootfs partitions on the system. Usually
|
||||
/// there are two (rootfsA and rootfsB).
|
||||
///
|
||||
pub fn rootfs_partitions() -> Result<Vec<Partition>> {
|
||||
let mut v = Vec::new();
|
||||
for path in rootfs_partition_paths()? {
|
||||
v.push(Partition::load(&path)?);
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Construct a new `Partition` object for the device `dev` and load
|
||||
/// the Partition Info Block structure from the block device.
|
||||
fn load(dev: &Path) -> Result<Partition> {
|
||||
let is_mounted = is_path_mounted(dev)?;
|
||||
let part = Partition::new(dev, is_mounted);
|
||||
part.read_infoblock()?;
|
||||
Ok(part)
|
||||
}
|
||||
|
||||
fn new(path: &Path, is_mounted: bool) -> Partition {
|
||||
Partition {
|
||||
path: path.to_path_buf(),
|
||||
is_mounted,
|
||||
infoblock: Infoblock::new(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// For the passed in `BlockDev` instance calculate and return
|
||||
/// the sector offset of the Partition Info Block, which is
|
||||
/// located 8 sectors (4096 bytes) from the end of the partition.
|
||||
///
|
||||
fn infoblock_offset(&self, bdev: &BlockDev) -> Result<usize> {
|
||||
let nsectors = bdev.nsectors()?;
|
||||
if nsectors < 8 {
|
||||
bail!("{} is a block device but it's very short, {} sectors",
|
||||
self.path_str(), nsectors);
|
||||
}
|
||||
Ok(nsectors - 8)
|
||||
}
|
||||
|
||||
///
|
||||
/// Open the block device for this partition and load the
|
||||
/// Partition Info Block into the internal buffer `self.infoblock`.
|
||||
///
|
||||
fn read_infoblock(&self) -> Result<()> {
|
||||
let mut dev = BlockDev::open_ro(&self.path)?;
|
||||
let off = self.infoblock_offset(&dev)?;
|
||||
let mut buffer = AlignedBuffer::new(BLOCK_SIZE);
|
||||
dev.read_sectors(off, buffer.as_mut())?;
|
||||
self.infoblock.from_slice(buffer.as_ref());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Open the block device for this partition and write the
|
||||
/// internal buffer `self.infoblock` into the Partition Info
|
||||
/// Block.
|
||||
///
|
||||
fn write_infoblock(&self) -> Result<()> {
|
||||
let mut dev = BlockDev::open_rw(&self.path)?;
|
||||
let off = self.infoblock_offset(&dev)?;
|
||||
let buffer = AlignedBuffer::from_slice(self.infoblock.as_ref().as_slice());
|
||||
dev.write_sectors(off, buffer.as_ref())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn path_str(&self) -> &str {
|
||||
self.path.to_str().unwrap()
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns true if this partition is currently mounted and
|
||||
/// cannot be written to.
|
||||
///
|
||||
pub fn is_mounted(&self) -> bool {
|
||||
self.is_mounted
|
||||
}
|
||||
|
||||
///
|
||||
/// Update the Partition Info Block for this partition with a new
|
||||
/// status field.
|
||||
///
|
||||
pub fn write_status(&self, status: u8) -> Result<()> {
|
||||
self.infoblock.set_status(status);
|
||||
self.write_infoblock()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_prefer_flag(&self, value: bool) -> Result<()> {
|
||||
if self.infoblock.set_flag(FLAG_PREFER, value) {
|
||||
self.write_infoblock()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Write metainfo and signature to the Partition Info Block of this partition.
|
||||
///
|
||||
/// This also sets the internal buffer `self.infoblock` to the block contents
|
||||
/// written to disk.
|
||||
///
|
||||
/// Writing new partition info also set status to `STATUS_INVALID` as this function
|
||||
/// is meant to be called in preparation for writing a raw disk image to partition.
|
||||
///
|
||||
/// Caller should write status as `STATUS_NEW` after raw image has been successfully
|
||||
/// written to partition.
|
||||
///
|
||||
pub fn write_partition_info(&self, metainfo: &[u8], signature: &[u8]) -> Result<()> {
|
||||
let mlen = metainfo.len();
|
||||
|
||||
if mlen > MAX_METAINFO_LEN {
|
||||
bail!("cannot write partition because metainfo field is too long ({} bytes)", metainfo.len());
|
||||
}
|
||||
|
||||
if mlen == 0 {
|
||||
bail!("cannot write partition because metainfo is empty");
|
||||
}
|
||||
|
||||
if signature.len() != SIGNATURE_LENGTH {
|
||||
bail!("cannot write partition info because signature has wrong length {} != {}",
|
||||
signature.len(), SIGNATURE_LENGTH);
|
||||
}
|
||||
|
||||
self.infoblock.reset();
|
||||
self.infoblock.write_metainfo(metainfo);
|
||||
self.infoblock.write_signature(signature);
|
||||
|
||||
self.write_infoblock()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns true only if this partition has a valid Partition Information
|
||||
/// Block and the signature on the metainfo field can be verified with
|
||||
/// the provided `PublicKey`.
|
||||
///
|
||||
pub fn verify_signature(&self, config: &Config) -> Result<bool> {
|
||||
if !self.infoblock.is_valid() {
|
||||
bail!("Cannot verify signature because partition is invalid");
|
||||
}
|
||||
let metainfo = self.metainfo()?;
|
||||
let channel = match config.channel(metainfo.channel()) {
|
||||
Some(ch) => ch,
|
||||
None => bail!("No public key configured for channel '{}'", metainfo.channel()),
|
||||
};
|
||||
|
||||
let data = self.infoblock.read_metainfo();
|
||||
let signature = self.infoblock.read_signature();
|
||||
Ok(channel.verify(&data, &signature)?)
|
||||
}
|
||||
|
||||
///
|
||||
/// Parse the bytes from the metainfo section of the Partition Information
|
||||
/// Block and return a `Metainfo` structure.
|
||||
///
|
||||
pub fn metainfo(&self) -> Result<Metainfo> {
|
||||
if !self.infoblock.is_valid() {
|
||||
bail!("partition is invalid");
|
||||
}
|
||||
let bytes = self.infoblock.read_metainfo();
|
||||
let metainfo = toml::from_slice::<Metainfo>(&bytes)?;
|
||||
Ok(metainfo)
|
||||
}
|
||||
|
||||
pub fn is_new(&self) -> bool {
|
||||
self.infoblock.has_status(STATUS_NEW)
|
||||
}
|
||||
|
||||
pub fn is_good(&self) -> bool {
|
||||
self.infoblock.has_status(STATUS_GOOD)
|
||||
}
|
||||
|
||||
pub fn is_preferred(&self) -> bool {
|
||||
self.infoblock.has_flag(FLAG_PREFER)
|
||||
}
|
||||
|
||||
pub fn status_label(&self) -> String {
|
||||
status_code_label(self.infoblock.status())
|
||||
}
|
||||
|
||||
/// `true` if the Partition Info Block fields
|
||||
/// contain legal values. `false` indicates that
|
||||
/// the data is corrupted or was never written to
|
||||
/// this partition.
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.infoblock.is_valid()
|
||||
}
|
||||
|
||||
pub fn write_image(&self, image_path: &Path, metainfo: &[u8], signature: &[u8]) -> Result<()> {
|
||||
if self.is_mounted {
|
||||
bail!("Cannot write to mounted device {}", self.path_str());
|
||||
}
|
||||
info!("Writing raw rootfs disk image to {}", self.path_str());
|
||||
self.write_partition_info(metainfo, signature)?;
|
||||
run_write_image_dd(image_path, &self.path)?;
|
||||
|
||||
let meta = image_path.metadata()?;
|
||||
let len = meta.len() as usize;
|
||||
let nblocks = len / 4096;
|
||||
|
||||
info!("Generating dm-verity hash tree");
|
||||
let mut verityinfo = image_path.to_path_buf();
|
||||
verityinfo.pop();
|
||||
verityinfo.push("verity-format.out");
|
||||
let out = run_verityinstall_command(self.path(), self.metainfo()?.verity_salt(), nblocks, len)?;
|
||||
write_string_to_file(&verityinfo, &out)?;
|
||||
|
||||
info!("Setting parition status field to STATUS_NEW");
|
||||
self.write_status(STATUS_NEW)?;
|
||||
|
||||
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.
|
||||
///
|
||||
pub fn boot_scan(&self, config: &Config) -> Result<()> {
|
||||
if !self.is_initialized() {
|
||||
return Ok(());
|
||||
}
|
||||
if self.infoblock.status() == STATUS_TRY_BOOT {
|
||||
warn!("Partition {} has STATUS_TRY_BOOT, assuming it failed boot attempt and marking STATUS_FAILED", self.path_str());
|
||||
self.write_status(STATUS_FAILED)?;
|
||||
}
|
||||
|
||||
if let Err(_) = self.metainfo() {
|
||||
warn!("Partition {} has invalid metainfo, setting STATUS_BAD_META", self.path_str());
|
||||
self.write_status(STATUS_BAD_META)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match self.verify_signature(config) {
|
||||
Err(e) => {
|
||||
warn!("Error verifying parition signature on {}: {}", self.path_str(), e);
|
||||
warn!("Partition {} has bad signature, marking STATUS_BAD_SIG", self.path_str());
|
||||
self.write_status(STATUS_BAD_SIG)?;
|
||||
},
|
||||
Ok(false) => {
|
||||
warn!("Partition {} has bad signature, marking STATUS_BAD_SIG", self.path_str());
|
||||
self.write_status(STATUS_BAD_SIG)?;
|
||||
},
|
||||
Ok(true) => { /* signature good */ },
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Set on partition before writing a new rootfs disk image
|
||||
const STATUS_INVALID : u8 = 0;
|
||||
|
||||
/// Set on partition after write of new rootfs disk image completes successfully
|
||||
const STATUS_NEW : u8 = 1;
|
||||
|
||||
/// Set on boot selected partition if in `STATUS_NEW` state.
|
||||
const STATUS_TRY_BOOT: u8 = 2;
|
||||
|
||||
/// Set on boot when a `STATUS_TRY_BOOT` partition successfully launches desktop
|
||||
const STATUS_GOOD : u8 = 3;
|
||||
|
||||
/// Set on boot for any partition in state `STATUS_TRY_BOOT`
|
||||
const STATUS_FAILED : u8 = 4;
|
||||
|
||||
/// Set on boot selected partition when signature fails to verify
|
||||
const STATUS_BAD_SIG : u8 = 5;
|
||||
|
||||
/// Set on boot selected partition when Metainfo cannot be parsed from Partition Info Block
|
||||
const STATUS_BAD_META: u8 = 6;
|
||||
|
||||
const CODE_TO_LABEL: [&str; 7] = ["Invalid", "New", "Try Boot", "Good", "Failed Boot", "Bad Signature", "Bad Metainfo"];
|
||||
|
||||
fn is_valid_status_code(code: u8) -> bool {
|
||||
code <= STATUS_BAD_META
|
||||
}
|
||||
|
||||
fn status_code_label(code: u8) -> String {
|
||||
if is_valid_status_code(code) {
|
||||
CODE_TO_LABEL[code as usize].to_string()
|
||||
} else {
|
||||
format!("Invalid status code: {}", code)
|
||||
}
|
||||
}
|
||||
|
129
citadel-tools/citadel-rootfs/src/unpacker.rs
Normal file
129
citadel-tools/citadel-rootfs/src/unpacker.rs
Normal file
@ -0,0 +1,129 @@
|
||||
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::{self,File};
|
||||
use std::io::{self,Read};
|
||||
|
||||
use toml;
|
||||
use ed25519_dalek::SIGNATURE_LENGTH;
|
||||
|
||||
use Result;
|
||||
use Config;
|
||||
use Metainfo;
|
||||
use Partition;
|
||||
use util::*;
|
||||
use MAX_METAINFO_LEN;
|
||||
|
||||
pub struct UpdateImageUnpacker {
|
||||
path: PathBuf,
|
||||
workdir: Workdir,
|
||||
metainfo: Metainfo,
|
||||
metainfo_bytes: Vec<u8>,
|
||||
signature_bytes: Vec<u8>,
|
||||
header_len: usize,
|
||||
}
|
||||
|
||||
impl UpdateImageUnpacker {
|
||||
pub fn open<P: AsRef<Path>>(path: P, config: &Config) -> Result<UpdateImageUnpacker> {
|
||||
let mut f = File::open(path.as_ref())?;
|
||||
|
||||
UpdateImageUnpacker::read_magic(&mut f)?;
|
||||
let metainfo_bytes = UpdateImageUnpacker::read_metainfo(&mut f)?;
|
||||
let metainfo = toml::from_slice::<Metainfo>(&metainfo_bytes)?;
|
||||
|
||||
let mut signature_bytes = vec![0; SIGNATURE_LENGTH];
|
||||
f.read_exact(&mut signature_bytes)?;
|
||||
|
||||
let channel = match config.channel(metainfo.channel()) {
|
||||
Some(ch) => ch,
|
||||
None => bail!("Channel '{}' not found in configuration", metainfo.channel()),
|
||||
};
|
||||
|
||||
if !channel.verify(metainfo_bytes.as_slice(), &signature_bytes)? {
|
||||
bail!("Signature verification failed");
|
||||
}
|
||||
|
||||
|
||||
let mut workdir = Workdir::new(config.citadel_updates_base(), metainfo.channel());
|
||||
workdir.set_version(metainfo.version() as usize)?;
|
||||
|
||||
let mlen = metainfo_bytes.len();
|
||||
|
||||
Ok(UpdateImageUnpacker {
|
||||
path: PathBuf::from(path.as_ref()),
|
||||
workdir, metainfo, metainfo_bytes,
|
||||
signature_bytes,
|
||||
|
||||
header_len: 6 + SIGNATURE_LENGTH + mlen,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_magic(r: &mut File) -> Result<()> {
|
||||
let mut buf = [0u8; 4];
|
||||
r.read_exact(&mut buf)?;
|
||||
|
||||
if &buf != b"UPDT" {
|
||||
bail!("not an update image, bad magic value");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn read_metainfo(r: &mut File) -> Result<Vec<u8>> {
|
||||
let mut lenbuf = [0u8; 2];
|
||||
r.read_exact(&mut lenbuf)?;
|
||||
let len = (lenbuf[0] as usize) << 8 | (lenbuf[1] as usize);
|
||||
if len == 0 || len > MAX_METAINFO_LEN {
|
||||
bail!("metainfo length field has invalid value: {}", len);
|
||||
}
|
||||
let mut bytes = vec![0u8; len];
|
||||
r.read_exact(bytes.as_mut_slice())?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
pub fn metainfo(&self) -> &Metainfo {
|
||||
&self.metainfo
|
||||
}
|
||||
|
||||
pub fn unpack_disk_image(&self) -> Result<()> {
|
||||
let mut from = File::open(&self.path)?;
|
||||
info!("{} -> {}", path_str(&self.path), path_str(&self.workdir.filepath("citadel-image.ext2.xz")));
|
||||
let mut to = File::create(&self.workdir.filepath("citadel-image.ext2.xz"))?;
|
||||
let mut discard = vec![0u8; self.header_len];
|
||||
from.read_exact(&mut discard)?;
|
||||
io::copy(&mut from, &mut to)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decompress_disk_image(&self) -> Result<()> {
|
||||
let output = self.workdir.filepath("citadel-image.ext2");
|
||||
if output.exists() {
|
||||
fs::remove_file(&output)?;
|
||||
}
|
||||
run_xz_command(&self.workdir.filepath("citadel-image.ext2.xz"), true)?;
|
||||
|
||||
let file_sz = fs::metadata(&output)?.len() as usize;
|
||||
let meta_sz = self.metainfo.nsectors() * 512;
|
||||
if file_sz != meta_sz {
|
||||
bail!("Uncompressed images size {} does not match size declared in metainfo {}", file_sz, meta_sz);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_shasum(&self) -> Result<()> {
|
||||
let path = self.workdir.filepath("citadel-image.ext2");
|
||||
let shasum = run_sha256_command(&path)?;
|
||||
if shasum != self.metainfo.shasum() {
|
||||
let mut bad = path.clone();
|
||||
bad.pop(); bad.push("citadel-image.ext2.badsum");
|
||||
fs::rename(&path, &bad)?;
|
||||
bail!("Failed sha256 sum of {}: {}", path_str(&path), shasum);
|
||||
}
|
||||
info!("GOOD: {}", shasum);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_partition(&self, part: &Partition) -> Result<()> {
|
||||
let path = self.workdir.filepath("citadel-image.ext2");
|
||||
part.write_image(&path, &self.metainfo_bytes, &self.signature_bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
168
citadel-tools/citadel-rootfs/src/util.rs
Normal file
168
citadel-tools/citadel-rootfs/src/util.rs
Normal file
@ -0,0 +1,168 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::{self,File};
|
||||
use std::process::Command;
|
||||
use std::io::{Read,Write,BufReader,BufRead};
|
||||
|
||||
use Result;
|
||||
|
||||
pub struct Workdir(PathBuf);
|
||||
|
||||
impl Workdir {
|
||||
pub fn new(base: &str, channel: &str) -> Workdir {
|
||||
let mut pb = PathBuf::from(base);
|
||||
pb.push(channel);
|
||||
Workdir(pb)
|
||||
}
|
||||
|
||||
pub fn find_next_version(&mut self) -> Result<usize> {
|
||||
let mut version = 1;
|
||||
loop {
|
||||
let path = self.0.join(version.to_string());
|
||||
if !path.exists() {
|
||||
self.set_version(version)?;
|
||||
return Ok(version);
|
||||
}
|
||||
version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_version(&mut self, version: usize) -> Result<()> {
|
||||
self.0.push(version.to_string());
|
||||
fs::create_dir_all(&self.0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn filepath(&self, name: &str) -> PathBuf {
|
||||
self.0.join(name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Returns `true` if `path` matches the source field (first field)
|
||||
/// of any of the mount lines listed in /proc/mounts
|
||||
///
|
||||
pub fn is_path_mounted(path: &Path) -> Result<bool> {
|
||||
let path_str = path.to_str().unwrap();
|
||||
let f = File::open("/proc/mounts")?;
|
||||
let reader = BufReader::new(f);
|
||||
for line in reader.lines() {
|
||||
if let Some(s) = line?.split_whitespace().next() {
|
||||
if s == path_str {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
///
|
||||
/// Converts a `Path` into `&str` representation, assuming
|
||||
/// that it contains valid utf-8
|
||||
///
|
||||
pub fn path_str(path: &Path) -> &str {
|
||||
path.to_str().unwrap()
|
||||
}
|
||||
|
||||
pub fn rootfs_partition_paths() -> Result<Vec<PathBuf>> {
|
||||
let mut rootfs_paths = Vec::new();
|
||||
for dent in fs::read_dir("/dev/mapper")? {
|
||||
let path = dent?.path();
|
||||
if is_path_rootfs(&path) {
|
||||
rootfs_paths.push(path);
|
||||
}
|
||||
}
|
||||
Ok(rootfs_paths)
|
||||
}
|
||||
pub 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;
|
||||
}
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
pub fn write_string_to_file(path: &Path, s: &str) -> Result<()> {
|
||||
let mut f = File::create(path)?;
|
||||
f.write_all(s.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_file_as_string(path: &Path) -> Result<String> {
|
||||
let mut f = File::open(path)?;
|
||||
let mut buffer = String::new();
|
||||
f.read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn run_file_command(path: &Path) -> Result<String> {
|
||||
let path = path_str(path);
|
||||
let output = try_run_command("/usr/bin/file", &["-b", path])?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn run_xz_command(path: &Path, decompress: bool) -> Result<()> {
|
||||
let path = path_str(path);
|
||||
if decompress {
|
||||
let _ = try_run_command("/usr/bin/xz", &["-d", path])?;
|
||||
} else {
|
||||
let _ = try_run_command("/usr/bin/xz", &["-T0", path])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_sha256_command(path: &Path) -> Result<String> {
|
||||
let path = path_str(path);
|
||||
let output = try_run_command("/usr/bin/sha256sum", &[path])?;
|
||||
|
||||
let v: Vec<&str> = output.split_whitespace().collect();
|
||||
Ok(v[0].trim().to_owned())
|
||||
}
|
||||
|
||||
pub fn run_verityformat_command(srcfile: &Path, hashfile: &Path) -> Result<String> {
|
||||
let srcfile = path_str(srcfile);
|
||||
let hashfile = path_str(hashfile);
|
||||
let output = try_run_command("/usr/sbin/veritysetup",
|
||||
&["format", srcfile, hashfile])?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn run_verityinstall_command(block_device: &Path, salt: &str, data_blocks: usize, hash_offset: usize) -> Result<String> {
|
||||
let data_device = path_str(block_device).to_owned();
|
||||
let hash_device = path_str(block_device).to_owned();
|
||||
let arg1 = format!("--data-blocks={}", data_blocks);
|
||||
let arg2 = format!("--hash-offset={}", hash_offset);
|
||||
let arg3 = format!("--salt={}", salt);
|
||||
let output = try_run_command("/usr/sbin/veritysetup", &[&arg1, &arg2, &arg3, "format", &data_device, &hash_device])?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn run_write_image_dd(image_src: &Path, block_device: &Path) -> Result<()> {
|
||||
let src = format!("if={}", path_str(image_src));
|
||||
let dst = format!("of={}", path_str(block_device));
|
||||
let _ = try_run_command("/bin/dd", &[&src, &dst, "bs=4M"])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_run_command(cmd_path: &str, args: &[&str]) -> Result<String> {
|
||||
let mut cmd = Command::new(cmd_path);
|
||||
for arg in args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
let result = cmd.output()?;
|
||||
|
||||
if !result.status.success() {
|
||||
let err = String::from_utf8(result.stderr)?;
|
||||
let argstr = args.join(" ");
|
||||
bail!("{} {} command failed: {}", cmd_path, argstr, err);
|
||||
}
|
||||
let output = String::from_utf8(result.stdout)?;
|
||||
Ok(output)
|
||||
}
|
36
meta-citadel/recipes-core/citadel-tools/citadel-appimg.bb
Normal file
36
meta-citadel/recipes-core/citadel-tools/citadel-appimg.bb
Normal file
@ -0,0 +1,36 @@
|
||||
SUMMARY = "citadel-appimg"
|
||||
|
||||
SRC_URI = "\
|
||||
crate://crates.io/ansi_term/0.11.0 \
|
||||
crate://crates.io/atty/0.2.8 \
|
||||
crate://crates.io/backtrace-sys/0.1.16 \
|
||||
crate://crates.io/backtrace/0.3.5 \
|
||||
crate://crates.io/bitflags/1.0.1 \
|
||||
crate://crates.io/cc/1.0.5 \
|
||||
crate://crates.io/cfg-if/0.1.2 \
|
||||
crate://crates.io/clap/2.31.1 \
|
||||
crate://crates.io/failure/0.1.1 \
|
||||
crate://crates.io/failure_derive/0.1.1 \
|
||||
crate://crates.io/lazy_static/1.0.0 \
|
||||
crate://crates.io/libc/0.2.39 \
|
||||
crate://crates.io/quote/0.3.15 \
|
||||
crate://crates.io/redox_syscall/0.1.37 \
|
||||
crate://crates.io/redox_termios/0.1.1 \
|
||||
crate://crates.io/rustc-demangle/0.1.7 \
|
||||
crate://crates.io/strsim/0.7.0 \
|
||||
crate://crates.io/syn/0.11.11 \
|
||||
crate://crates.io/synom/0.11.3 \
|
||||
crate://crates.io/synstructure/0.6.1 \
|
||||
crate://crates.io/termion/1.5.1 \
|
||||
crate://crates.io/textwrap/0.9.0 \
|
||||
crate://crates.io/unicode-width/0.1.4 \
|
||||
crate://crates.io/unicode-xid/0.0.4 \
|
||||
crate://crates.io/vec_map/0.8.0 \
|
||||
crate://crates.io/winapi-i686-pc-windows-gnu/0.4.0 \
|
||||
crate://crates.io/winapi-x86_64-pc-windows-gnu/0.4.0 \
|
||||
crate://crates.io/winapi/0.3.4 \
|
||||
"
|
||||
|
||||
inherit cargo
|
||||
|
||||
require citadel-tools.inc
|
75
meta-citadel/recipes-core/citadel-tools/citadel-desktopd.bb
Normal file
75
meta-citadel/recipes-core/citadel-tools/citadel-desktopd.bb
Normal file
@ -0,0 +1,75 @@
|
||||
SUMMARY = "citadel-desktopd"
|
||||
|
||||
SRC_URI = "\
|
||||
crate://crates.io/aho-corasick/0.6.4 \
|
||||
crate://crates.io/atty/0.2.6 \
|
||||
crate://crates.io/backtrace-sys/0.1.16 \
|
||||
crate://crates.io/backtrace/0.3.5 \
|
||||
crate://crates.io/bitflags/1.0.1 \
|
||||
crate://crates.io/byteorder/1.2.1 \
|
||||
crate://crates.io/bytes/0.4.6 \
|
||||
crate://crates.io/cc/1.0.4 \
|
||||
crate://crates.io/cfg-if/0.1.2 \
|
||||
crate://crates.io/chrono/0.4.0 \
|
||||
crate://crates.io/env_logger/0.5.3 \
|
||||
crate://crates.io/failure/0.1.1 \
|
||||
crate://crates.io/failure_derive/0.1.1 \
|
||||
crate://crates.io/gcc/0.3.54 \
|
||||
crate://crates.io/inotify-sys/0.1.2 \
|
||||
crate://crates.io/inotify/0.5.0 \
|
||||
crate://crates.io/iovec/0.1.2 \
|
||||
crate://crates.io/lazy_static/1.0.0 \
|
||||
crate://crates.io/libc/0.2.36 \
|
||||
crate://crates.io/log/0.4.1 \
|
||||
crate://crates.io/memchr/2.0.1 \
|
||||
crate://crates.io/nix/0.10.0 \
|
||||
crate://crates.io/num-integer/0.1.36 \
|
||||
crate://crates.io/num-iter/0.1.35 \
|
||||
crate://crates.io/num-traits/0.1.43 \
|
||||
crate://crates.io/num-traits/0.2.0 \
|
||||
crate://crates.io/num/0.1.41 \
|
||||
crate://crates.io/quote/0.3.15 \
|
||||
crate://crates.io/redox_syscall/0.1.37 \
|
||||
crate://crates.io/redox_termios/0.1.1 \
|
||||
crate://crates.io/regex-syntax/0.4.2 \
|
||||
crate://crates.io/regex/0.2.5 \
|
||||
crate://crates.io/rustc-demangle/0.1.5 \
|
||||
crate://crates.io/serde/1.0.27 \
|
||||
crate://crates.io/serde_derive/1.0.27 \
|
||||
crate://crates.io/serde_derive_internals/0.19.0 \
|
||||
crate://crates.io/syn/0.11.11 \
|
||||
crate://crates.io/synom/0.11.3 \
|
||||
crate://crates.io/synstructure/0.6.1 \
|
||||
crate://crates.io/termcolor/0.3.3 \
|
||||
crate://crates.io/termion/1.5.1 \
|
||||
crate://crates.io/thread_local/0.3.5 \
|
||||
crate://crates.io/time/0.1.39 \
|
||||
crate://crates.io/toml/0.4.5 \
|
||||
crate://crates.io/unicode-xid/0.0.4 \
|
||||
crate://crates.io/unreachable/1.0.0 \
|
||||
crate://crates.io/utf8-ranges/1.0.0 \
|
||||
crate://crates.io/void/1.0.2 \
|
||||
crate://crates.io/winapi-i686-pc-windows-gnu/0.4.0 \
|
||||
crate://crates.io/winapi-x86_64-pc-windows-gnu/0.4.0 \
|
||||
crate://crates.io/winapi/0.2.8 \
|
||||
crate://crates.io/winapi/0.3.4 \
|
||||
crate://crates.io/wincolor/0.1.5 \
|
||||
"
|
||||
|
||||
inherit cargo systemd
|
||||
|
||||
SYSTEMD_SERVICE_${PN} = "citadel-desktopd.service"
|
||||
|
||||
do_install() {
|
||||
install -d ${D}${libexecdir}
|
||||
install -d ${D}${datadir}/citadel
|
||||
install -d ${D}${systemd_system_unitdir}
|
||||
|
||||
install -m 755 ${B}/target/${CARGO_TARGET_SUBDIR}/citadel-desktopd ${D}${libexecdir}
|
||||
install -m 644 ${B}/conf/citadel-desktopd.conf ${D}${datadir}/citadel
|
||||
install -m 644 ${B}/conf/citadel-desktopd.service ${D}${systemd_system_unitdir}
|
||||
}
|
||||
|
||||
FILES_${PN} += "${datadir}/citadel"
|
||||
|
||||
require citadel-tools.inc
|
76
meta-citadel/recipes-core/citadel-tools/citadel-rootfs.bb
Normal file
76
meta-citadel/recipes-core/citadel-tools/citadel-rootfs.bb
Normal file
@ -0,0 +1,76 @@
|
||||
SUMMARY = "citadel-rootfs"
|
||||
|
||||
SRC_URI = "\
|
||||
crate://crates.io/ansi_term/0.10.2 \
|
||||
crate://crates.io/arrayref/0.3.4 \
|
||||
crate://crates.io/atty/0.2.6 \
|
||||
crate://crates.io/backtrace-sys/0.1.16 \
|
||||
crate://crates.io/backtrace/0.3.5 \
|
||||
crate://crates.io/bitflags/1.0.1 \
|
||||
crate://crates.io/block-buffer/0.3.3 \
|
||||
crate://crates.io/build_const/0.2.0 \
|
||||
crate://crates.io/byte-tools/0.2.0 \
|
||||
crate://crates.io/byteorder/1.2.1 \
|
||||
crate://crates.io/bytes/0.4.6 \
|
||||
crate://crates.io/cc/1.0.4 \
|
||||
crate://crates.io/cfg-if/0.1.2 \
|
||||
crate://crates.io/clap/2.30.0 \
|
||||
crate://crates.io/clear_on_drop/0.2.3 \
|
||||
crate://crates.io/crc/1.7.0 \
|
||||
crate://crates.io/curve25519-dalek/0.14.4 \
|
||||
crate://crates.io/digest/0.7.2 \
|
||||
crate://crates.io/ed25519-dalek/0.6.1 \
|
||||
crate://crates.io/failure/0.1.1 \
|
||||
crate://crates.io/failure_derive/0.1.1 \
|
||||
crate://crates.io/fake-simd/0.1.2 \
|
||||
crate://crates.io/fuchsia-zircon-sys/0.3.3 \
|
||||
crate://crates.io/fuchsia-zircon/0.3.3 \
|
||||
crate://crates.io/gcc/0.3.54 \
|
||||
crate://crates.io/generic-array/0.9.0 \
|
||||
crate://crates.io/iovec/0.1.2 \
|
||||
crate://crates.io/libc/0.2.36 \
|
||||
crate://crates.io/log/0.4.1 \
|
||||
crate://crates.io/lzma-rs/0.1.0 \
|
||||
crate://crates.io/nix/0.10.0 \
|
||||
crate://crates.io/num-traits/0.1.43 \
|
||||
crate://crates.io/num-traits/0.2.0 \
|
||||
crate://crates.io/quote/0.3.15 \
|
||||
crate://crates.io/rand/0.4.2 \
|
||||
crate://crates.io/redox_syscall/0.1.37 \
|
||||
crate://crates.io/redox_termios/0.1.1 \
|
||||
crate://crates.io/rustc-demangle/0.1.6 \
|
||||
crate://crates.io/rustc-serialize/0.3.24 \
|
||||
crate://crates.io/serde/1.0.27 \
|
||||
crate://crates.io/serde_derive/1.0.27 \
|
||||
crate://crates.io/serde_derive_internals/0.19.0 \
|
||||
crate://crates.io/sha2/0.7.0 \
|
||||
crate://crates.io/strsim/0.7.0 \
|
||||
crate://crates.io/subtle/0.3.0 \
|
||||
crate://crates.io/subtle/0.5.1 \
|
||||
crate://crates.io/syn/0.11.11 \
|
||||
crate://crates.io/synom/0.11.3 \
|
||||
crate://crates.io/synstructure/0.6.1 \
|
||||
crate://crates.io/termion/1.5.1 \
|
||||
crate://crates.io/textwrap/0.9.0 \
|
||||
crate://crates.io/toml/0.4.5 \
|
||||
crate://crates.io/typenum/1.9.0 \
|
||||
crate://crates.io/unicode-width/0.1.4 \
|
||||
crate://crates.io/unicode-xid/0.0.4 \
|
||||
crate://crates.io/vec_map/0.8.0 \
|
||||
crate://crates.io/void/1.0.2 \
|
||||
crate://crates.io/winapi-i686-pc-windows-gnu/0.4.0 \
|
||||
crate://crates.io/winapi-x86_64-pc-windows-gnu/0.4.0 \
|
||||
crate://crates.io/winapi/0.2.8 \
|
||||
crate://crates.io/winapi/0.3.4 \
|
||||
"
|
||||
|
||||
do_install() {
|
||||
install -d ${D}${bindir}
|
||||
install -d ${D}${datadir}/citadel
|
||||
|
||||
install -m 755 ${B}/target/${CARGO_TARGET_SUBDIR}/citadel-rootfs ${D}${bindir}
|
||||
install -m 644 ${B}/conf/citadel-rootfs.conf ${D}${datadir}/citadel
|
||||
}
|
||||
|
||||
inherit cargo
|
||||
require citadel-tools.inc
|
16
meta-citadel/recipes-core/citadel-tools/citadel-tools.inc
Normal file
16
meta-citadel/recipes-core/citadel-tools/citadel-tools.inc
Normal file
@ -0,0 +1,16 @@
|
||||
HOMEPAGE = "http://github.com/subgraph/citadel"
|
||||
LICENSE = "CLOSED"
|
||||
LIC_FILES_CHKSUM=""
|
||||
|
||||
# what is this even for?
|
||||
CARGO_SRC_DIR=""
|
||||
|
||||
S = "${WORKDIR}/${PN}"
|
||||
do_unpack_src() {
|
||||
rm -rf ${WORKDIR}/${PN}
|
||||
cp -a ${TOPDIR}/../citadel-tools/${PN} ${WORKDIR}
|
||||
rm -rf ${WORKDIR}$/${PN}/target
|
||||
}
|
||||
# don't use cached task
|
||||
#do_unpack_src[nostamp] = "1"
|
||||
addtask unpack_src after do_unpack before do_patch
|
@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Launch default appimg
|
||||
ConditionPathExists=!/run/appimg
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/citadel-appimg start
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
@ -1,5 +1,5 @@
|
||||
[Unit]
|
||||
Description=Watch Just watching Run User
|
||||
Description=Watch for creation of /run/user/1000
|
||||
After=run-user-1000.mount
|
||||
Requires=run-user-1000.mount
|
||||
|
@ -0,0 +1,6 @@
|
||||
[Unit]
|
||||
Description=Watch run-user service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/systemctl --no-block start launch-default-appimg.path
|
@ -0,0 +1,29 @@
|
||||
|
||||
DESCRIPTION = "Install systemd unit file to automatically start default appimg"
|
||||
LICENSE = "MIT"
|
||||
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420"
|
||||
SECTION = ""
|
||||
DEPENDS = ""
|
||||
|
||||
inherit systemd
|
||||
|
||||
SRC_URI = "file://launch-default-appimg.path file://launch-default-appimg.service file://watch-run-user.path file://watch-run-user.service"
|
||||
|
||||
S = "${WORKDIR}"
|
||||
|
||||
SYSTEMD_SERVICE_${PN} = "watch-run-user.path"
|
||||
RDEPENDS_${PN} = "bash"
|
||||
|
||||
FILES_${PN} += "\
|
||||
${systemd_system_unitdir}/watch-run-user.service \
|
||||
${systemd_system_unitdir}/launch-default-appimg.path \
|
||||
${systemd_system_unitdir}/launch-default-appimg.service \
|
||||
"
|
||||
|
||||
do_install() {
|
||||
install -d ${D}${systemd_system_unitdir}
|
||||
install -m 644 ${WORKDIR}/launch-default-appimg.path ${D}${systemd_system_unitdir}
|
||||
install -m 644 ${WORKDIR}/launch-default-appimg.service ${D}${systemd_system_unitdir}
|
||||
install -m 644 ${WORKDIR}/watch-run-user.path ${D}${systemd_system_unitdir}
|
||||
install -m 644 ${WORKDIR}/watch-run-user.service ${D}${systemd_system_unitdir}
|
||||
}
|
@ -75,8 +75,10 @@ RDEPENDS_${PN} = "\
|
||||
e2fsprogs \
|
||||
dosfstools \
|
||||
btrfs-tools \
|
||||
primary-user-appimg\
|
||||
dash-to-panel \
|
||||
desktopd \
|
||||
launch-default-appimg \
|
||||
citadel-desktopd \
|
||||
citadel-rootfs \
|
||||
citadel-appimg \
|
||||
iproute2 \
|
||||
"
|
||||
|
@ -1,12 +0,0 @@
|
||||
[Unit]
|
||||
Description=Default User appimg
|
||||
ConditionPathExists=/storage/appimg/primary/rootfs
|
||||
|
||||
[Service]
|
||||
Environment=SYSTEMD_NSPAWN_SHARE_NS_IPC=1
|
||||
ExecStart=/usr/bin/systemd-nspawn --quiet --keep-unit --machine=primary --link-journal=try-guest --directory=/storage/appimg/default.appimg/rootfs
|
||||
|
||||
KillMode=mixed
|
||||
Type=notify
|
||||
RestartForceExitStatus=133
|
||||
SuccessExitStatus=133
|
@ -1,37 +0,0 @@
|
||||
[Exec]
|
||||
Boot=true
|
||||
Environment=IFCONFIG_IP=172.17.0.2/24
|
||||
Environment=IFCONFIG_GW=172.17.0.1
|
||||
|
||||
[Files]
|
||||
BindReadOnly=/usr/share/themes/Adapta
|
||||
BindReadOnly=/usr/share/themes/Adapta-Eta
|
||||
BindReadOnly=/usr/share/themes/Adapta-Nokto
|
||||
BindReadOnly=/usr/share/themes/Adapta-Nokto-Eta
|
||||
BindReadOnly=/usr/share/icons/Paper
|
||||
|
||||
Bind=/storage/user-data/primary-home:/home/user
|
||||
BindReadOnly=/storage/citadel-state/resolv.conf:/etc/resolv.conf
|
||||
|
||||
#
|
||||
# Bind mounts for sound and pulse audio
|
||||
#
|
||||
Bind=/dev/snd
|
||||
Bind=/dev/shm
|
||||
BindReadOnly=/run/user/1000/pulse:/run/user/host/pulse
|
||||
|
||||
BindReadOnly=/tmp/.X11-unix
|
||||
BindReadOnly=/run/user/1000/wayland-0:/run/user/host/wayland-0
|
||||
|
||||
#
|
||||
# Uncomment to enable kvm access in container
|
||||
#
|
||||
#Bind=/dev/kvm
|
||||
|
||||
#
|
||||
# Uncomment to enable GPU access in container
|
||||
#
|
||||
#Bind=/dev/dri/renderD128
|
||||
|
||||
[Network]
|
||||
Zone=clear
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
machinectl -E DESKTOP_STARTUP_ID=${DESKTOP_STARTUP_ID} shell user@primary /usr/libexec/launch $@
|
@ -1,6 +0,0 @@
|
||||
[Unit]
|
||||
Description=Watch run-user service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/systemctl --no-block start primary-user-appimg.path
|
@ -1,33 +0,0 @@
|
||||
|
||||
DESCRIPTION = "Install systemd unit file to automatically start primary-user-appimg container"
|
||||
LICENSE = "MIT"
|
||||
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420"
|
||||
SECTION = ""
|
||||
DEPENDS = ""
|
||||
|
||||
inherit systemd
|
||||
|
||||
SRC_URI = "file://primary-user-appimg.path file://primary-user-appimg.service file://primary.nspawn file://watch-run-user.path file://watch-run-user.service file://run-in-image"
|
||||
|
||||
S = "${WORKDIR}"
|
||||
|
||||
SYSTEMD_SERVICE_${PN} = "watch-run-user.path"
|
||||
RDEPENDS_${PN} = "bash"
|
||||
|
||||
FILES_${PN} += "\
|
||||
${systemd_system_unitdir}/watch-run-user.service \
|
||||
${systemd_system_unitdir}/primary-user-appimg.path \
|
||||
${systemd_system_unitdir}/primary-user-appimg.service \
|
||||
"
|
||||
|
||||
do_install() {
|
||||
install -d ${D}${systemd_system_unitdir}
|
||||
install -m 644 ${WORKDIR}/primary-user-appimg.path ${D}${systemd_system_unitdir}
|
||||
install -m 644 ${WORKDIR}/primary-user-appimg.service ${D}${systemd_system_unitdir}
|
||||
install -m 644 ${WORKDIR}/watch-run-user.path ${D}${systemd_system_unitdir}
|
||||
install -m 644 ${WORKDIR}/watch-run-user.service ${D}${systemd_system_unitdir}
|
||||
install -d ${D}${sysconfdir}/systemd/nspawn
|
||||
install -m 644 ${WORKDIR}/primary.nspawn ${D}${sysconfdir}/systemd/nspawn
|
||||
install -d ${D}${libexecdir}
|
||||
install -m 755 ${WORKDIR}/run-in-image ${D}${libexecdir}
|
||||
}
|
Loading…
Reference in New Issue
Block a user