forked from brl/citadel-tools
Initial implementation of RealmFS
This commit is contained in:
parent
884d056420
commit
e7151f8de2
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -132,7 +132,9 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libcitadel 0.1.0",
|
||||||
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -6,6 +6,7 @@ homepage = "http://github.com/subgraph/citadel"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
libcitadel = { path = "../libcitadel" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
clap = "2.30.0"
|
clap = "2.30.0"
|
||||||
failure = "0.1.1"
|
failure = "0.1.1"
|
||||||
@ -14,3 +15,4 @@ serde_derive = "1.0.27"
|
|||||||
serde = "1.0.27"
|
serde = "1.0.27"
|
||||||
termcolor = "0.3"
|
termcolor = "0.3"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
lazy_static = "1.2.0"
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
use std::path::Path;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use crate::Realm;
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
const BASE_APPIMG_PATH: &str = "/storage/appimg/base.appimg";
|
|
||||||
const BTRFS_COMMAND: &str = "/usr/bin/btrfs";
|
|
||||||
|
|
||||||
pub fn clone_base_appimg(target_realm: &Realm) -> Result<()> {
|
|
||||||
if !Path::new(BASE_APPIMG_PATH).exists() {
|
|
||||||
bail!("base appimg does not exist at {}", BASE_APPIMG_PATH);
|
|
||||||
}
|
|
||||||
let target = format!("/realms/realm-{}/rootfs", target_realm.name());
|
|
||||||
let target_path = Path::new(&target);
|
|
||||||
|
|
||||||
if target_path.exists() {
|
|
||||||
bail!("cannot create clone of base appimg for realm '{}' because rootfs directory already exists at {}",
|
|
||||||
target_realm.name(), target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !target_path.parent().unwrap().exists() {
|
|
||||||
bail!("cannot create clone of base appimg for realm '{}' because realm directory /realms/realm-{} does not exist.",
|
|
||||||
target_realm.name(), target_realm.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::new(BTRFS_COMMAND)
|
|
||||||
.args(&["subvolume", "snapshot", BASE_APPIMG_PATH, &target ])
|
|
||||||
.status()
|
|
||||||
.map_err(|e| format_err!("failed to execute {}: {}", BTRFS_COMMAND, e))?;
|
|
||||||
Ok(())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_rootfs_subvolume(realm: &Realm) -> Result<()> {
|
|
||||||
let path = realm.base_path().join("rootfs");
|
|
||||||
Command::new(BTRFS_COMMAND)
|
|
||||||
.args(&["subvolume", "delete", path.to_str().unwrap() ])
|
|
||||||
.status()
|
|
||||||
.map_err(|e| format_err!("failed to execute {}: {}", BTRFS_COMMAND, e))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
#[macro_use] extern crate failure;
|
#[macro_use] extern crate failure;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
|
#[macro_use] extern crate lazy_static;
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use clap::{App,Arg,ArgMatches,SubCommand};
|
use clap::{App,Arg,ArgMatches,SubCommand};
|
||||||
@ -33,13 +34,15 @@ mod util;
|
|||||||
mod systemd;
|
mod systemd;
|
||||||
mod config;
|
mod config;
|
||||||
mod network;
|
mod network;
|
||||||
mod appimg;
|
|
||||||
|
use libcitadel::RealmFS;
|
||||||
|
|
||||||
use crate::realm::{Realm,RealmSymlinks};
|
use crate::realm::{Realm,RealmSymlinks};
|
||||||
use crate::manager::RealmManager;
|
use crate::manager::RealmManager;
|
||||||
use crate::config::RealmConfig;
|
use crate::config::RealmConfig;
|
||||||
use crate::systemd::Systemd;
|
use crate::systemd::Systemd;
|
||||||
use crate::network::NetworkConfig;
|
use crate::network::NetworkConfig;
|
||||||
|
use crate::config::GLOBAL_CONFIG;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let app = App::new("citadel-realms")
|
let app = App::new("citadel-realms")
|
||||||
@ -118,7 +121,7 @@ fn main() {
|
|||||||
.long("appimg")
|
.long("appimg")
|
||||||
.help("Name of application image in /storage/appimg directory. Default is to use base.appimg")
|
.help("Name of application image in /storage/appimg directory. Default is to use base.appimg")
|
||||||
.takes_value(true)))
|
.takes_value(true)))
|
||||||
|
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("new")
|
.subcommand(SubCommand::with_name("new")
|
||||||
.arg(Arg::with_name("help").long("help").hidden(true))
|
.arg(Arg::with_name("help").long("help").hidden(true))
|
||||||
|
@ -5,9 +5,10 @@ use std::cell::{RefCell,Cell};
|
|||||||
use std::fs::{self,File};
|
use std::fs::{self,File};
|
||||||
use std::os::unix::fs::{symlink,MetadataExt};
|
use std::os::unix::fs::{symlink,MetadataExt};
|
||||||
|
|
||||||
use crate::{RealmConfig,Result,Systemd,NetworkConfig};
|
use libcitadel::{CommandLine,RealmFS};
|
||||||
|
|
||||||
|
use crate::{RealmConfig,Result,Systemd,NetworkConfig,GLOBAL_CONFIG};
|
||||||
use crate::util::*;
|
use crate::util::*;
|
||||||
use crate::appimg::*;
|
|
||||||
|
|
||||||
const REALMS_BASE_PATH: &str = "/realms";
|
const REALMS_BASE_PATH: &str = "/realms";
|
||||||
const REALMS_RUN_PATH: &str = "/run/realms";
|
const REALMS_RUN_PATH: &str = "/run/realms";
|
||||||
@ -45,8 +46,7 @@ impl Realm {
|
|||||||
|
|
||||||
fn load_config(&mut self) -> Result<()> {
|
fn load_config(&mut self) -> Result<()> {
|
||||||
let path = self.base_path().join("config");
|
let path = self.base_path().join("config");
|
||||||
self.config = RealmConfig::load_or_default(&path)
|
self.config = RealmConfig::load_or_default(&path);
|
||||||
.map_err(|e| format_err!("failed to load realm config file {}: {}", path.display(), e))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,11 +105,89 @@ impl Realm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&self) -> Result<()> {
|
pub fn start(&self) -> Result<()> {
|
||||||
|
self.setup_realmfs(self.config.realmfs())?;
|
||||||
self.systemd.start_realm(self)?;
|
self.systemd.start_realm(self)?;
|
||||||
info!("Started realm '{}'", self.name());
|
info!("Started realm '{}'", self.name());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_realmfs(&self, name: &str) -> Result<()> {
|
||||||
|
let mut realmfs = self.get_named_realmfs(name)?;
|
||||||
|
self.setup_rootfs_link(&realmfs)?;
|
||||||
|
|
||||||
|
info!("Starting realm with realmfs = {}", name);
|
||||||
|
if !realmfs.is_mounted() {
|
||||||
|
if realmfs.is_sealed() {
|
||||||
|
realmfs.mount_verity()?;
|
||||||
|
} else {
|
||||||
|
if CommandLine::sealed() {
|
||||||
|
bail!("Cannot start realm because realmfs {} is not sealed and citadel.sealed is set", name);
|
||||||
|
}
|
||||||
|
realmfs.mount_rw()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return named RealmFS instance if it already exists.
|
||||||
|
/// Otherwise, create it as a fork of the 'default' image.
|
||||||
|
/// The default image is either 'base' or some other name
|
||||||
|
/// from the global realm config file.
|
||||||
|
///
|
||||||
|
/// If the default image does not exist, then create that too
|
||||||
|
/// as a fork of 'base' image.
|
||||||
|
fn get_named_realmfs(&self, name: &str) -> Result<RealmFS> {
|
||||||
|
if RealmFS::named_image_exists(name) {
|
||||||
|
return RealmFS::load_by_name(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if CommandLine::sealed() {
|
||||||
|
bail!("Realm {} needs RealmFS {} which does not exist and cannot be created in sealed realmfs mode", self.name(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let default = GLOBAL_CONFIG.realmfs();
|
||||||
|
|
||||||
|
let default_image = if RealmFS::named_image_exists(default) {
|
||||||
|
RealmFS::load_by_name(default)?
|
||||||
|
} else {
|
||||||
|
// If default image name is something other than 'base' and does
|
||||||
|
// not exist, create it as a fork of 'base'
|
||||||
|
let base = RealmFS::load_by_name("base")?;
|
||||||
|
base.fork(default)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Requested name might be the default image that was just created, if so return it.
|
||||||
|
let image = if name == default {
|
||||||
|
default_image
|
||||||
|
} else {
|
||||||
|
default_image.fork(name)?
|
||||||
|
};
|
||||||
|
Ok(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure rootfs in realm directory is a symlink pointing to the correct realmfs mountpoint
|
||||||
|
fn setup_rootfs_link(&self, realmfs: &RealmFS) -> Result<()> {
|
||||||
|
let mountpoint = realmfs.mountpoint();
|
||||||
|
let rootfs = self.base_path().join("rootfs");
|
||||||
|
|
||||||
|
if rootfs.exists() {
|
||||||
|
let link = fs::read_link(&rootfs)?;
|
||||||
|
if link == mountpoint {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
fs::remove_file(&rootfs)?;
|
||||||
|
}
|
||||||
|
symlink(mountpoint, rootfs)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readonly_rootfs(&self) -> bool {
|
||||||
|
if CommandLine::sealed() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
!self.config.realmfs_write()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stop(&self) -> Result<()> {
|
pub fn stop(&self) -> Result<()> {
|
||||||
self.systemd.stop_realm(self)?;
|
self.systemd.stop_realm(self)?;
|
||||||
if self.is_current() {
|
if self.is_current() {
|
||||||
@ -158,9 +236,6 @@ impl Realm {
|
|||||||
self.create_home_directory()
|
self.create_home_directory()
|
||||||
.map_err(|e| format_err!("failed to create realm home directory {}: {}", self.base_path().join("home").display(), e))?;
|
.map_err(|e| format_err!("failed to create realm home directory {}: {}", self.base_path().join("home").display(), e))?;
|
||||||
|
|
||||||
// This must be last step because if an error is returned caller assumes that subvolume was
|
|
||||||
// never created and does not need to be removed.
|
|
||||||
clone_base_appimg(self)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,8 +258,6 @@ impl Realm {
|
|||||||
if self.is_running()? {
|
if self.is_running()? {
|
||||||
self.stop()?;
|
self.stop()?;
|
||||||
}
|
}
|
||||||
info!("removing rootfs subvolume for '{}'", self.name());
|
|
||||||
delete_rootfs_subvolume(self)?;
|
|
||||||
|
|
||||||
info!("removing realm directory {}", self.base_path().display());
|
info!("removing realm directory {}", self.base_path().display());
|
||||||
fs::remove_dir_all(self.base_path())?;
|
fs::remove_dir_all(self.base_path())?;
|
||||||
|
@ -283,9 +283,9 @@ impl Systemd {
|
|||||||
|
|
||||||
fn generate_nspawn_file(&self, realm: &Realm) -> Result<String> {
|
fn generate_nspawn_file(&self, realm: &Realm) -> Result<String> {
|
||||||
Ok(NSPAWN_FILE_TEMPLATE
|
Ok(NSPAWN_FILE_TEMPLATE
|
||||||
.replace("$EXTRA_BIND_MOUNTS", &self.generate_extra_bind_mounts(realm)?)
|
.replace("$EXTRA_BIND_MOUNTS", &self.generate_extra_bind_mounts(realm)?)
|
||||||
|
.replace("$EXTRA_FILE_OPTIONS", &self.generate_extra_file_options(realm)?)
|
||||||
.replace("$NETWORK_CONFIG", &self.generate_network_config(realm)?))
|
.replace("$NETWORK_CONFIG", &self.generate_network_config(realm)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_extra_bind_mounts(&self, realm: &Realm) -> Result<String> {
|
fn generate_extra_bind_mounts(&self, realm: &Realm) -> Result<String> {
|
||||||
@ -327,6 +327,15 @@ impl Systemd {
|
|||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_extra_file_options(&self, realm: &Realm) -> Result<String> {
|
||||||
|
let mut s = String::new();
|
||||||
|
if realm.readonly_rootfs() {
|
||||||
|
writeln!(s, "ReadOnly=true")?;
|
||||||
|
writeln!(s, "Overlay=+/var::/var")?;
|
||||||
|
}
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_network_config(&self, realm: &Realm) -> Result<String> {
|
fn generate_network_config(&self, realm: &Realm) -> Result<String> {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
if realm.config().network() {
|
if realm.config().network() {
|
||||||
@ -363,6 +372,8 @@ BindReadOnly=/storage/citadel-state/resolv.conf:/etc/resolv.conf
|
|||||||
|
|
||||||
$EXTRA_BIND_MOUNTS
|
$EXTRA_BIND_MOUNTS
|
||||||
|
|
||||||
|
$EXTRA_FILE_OPTIONS
|
||||||
|
|
||||||
"###;
|
"###;
|
||||||
|
|
||||||
pub const REALM_SERVICE_TEMPLATE: &str = r###"
|
pub const REALM_SERVICE_TEMPLATE: &str = r###"
|
||||||
@ -372,7 +383,7 @@ Wants=citadel-desktopd.service
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Environment=SYSTEMD_NSPAWN_SHARE_NS_IPC=1
|
Environment=SYSTEMD_NSPAWN_SHARE_NS_IPC=1
|
||||||
ExecStart=/usr/bin/systemd-nspawn --quiet --notify-ready=yes --keep-unit --machine=$REALM_NAME --link-journal=try-guest --directory=$ROOTFS
|
ExecStart=/usr/bin/systemd-nspawn --quiet --notify-ready=yes --keep-unit --machine=$REALM_NAME --link-journal=auto --directory=$ROOTFS
|
||||||
|
|
||||||
KillMode=mixed
|
KillMode=mixed
|
||||||
Type=notify
|
Type=notify
|
||||||
|
@ -42,7 +42,7 @@ const MAX_REALM_NAME_LEN:usize = 128;
|
|||||||
/// * may only contain ascii characters which are letters, numbers, or the dash '-' symbol
|
/// * may only contain ascii characters which are letters, numbers, or the dash '-' symbol
|
||||||
/// * must not be empty or have a length exceeding 128 characters
|
/// * must not be empty or have a length exceeding 128 characters
|
||||||
pub fn is_valid_realm_name(name: &str) -> bool {
|
pub fn is_valid_realm_name(name: &str) -> bool {
|
||||||
name.len() <= MAX_REALM_NAME_LEN &&
|
name.len() <= MAX_REALM_NAME_LEN &&
|
||||||
// Also false on empty string
|
// Also false on empty string
|
||||||
is_first_char_alphabetic(name) &&
|
is_first_char_alphabetic(name) &&
|
||||||
name.chars().all(is_alphanum_or_dash)
|
name.chars().all(is_alphanum_or_dash)
|
||||||
|
@ -58,6 +58,7 @@ mod resource;
|
|||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod verity;
|
pub mod verity;
|
||||||
mod mount;
|
mod mount;
|
||||||
|
mod realmfs;
|
||||||
|
|
||||||
pub use crate::config::OsRelease;
|
pub use crate::config::OsRelease;
|
||||||
pub use crate::blockdev::BlockDev;
|
pub use crate::blockdev::BlockDev;
|
||||||
@ -67,6 +68,7 @@ pub use crate::partition::Partition;
|
|||||||
pub use crate::resource::ResourceImage;
|
pub use crate::resource::ResourceImage;
|
||||||
pub use crate::keys::{KeyPair,PublicKey};
|
pub use crate::keys::{KeyPair,PublicKey};
|
||||||
pub use crate::mount::Mount;
|
pub use crate::mount::Mount;
|
||||||
|
pub use crate::realmfs::RealmFS;
|
||||||
|
|
||||||
const DEVKEYS_HEX: &str =
|
const DEVKEYS_HEX: &str =
|
||||||
"3053020101300506032b6570042204206ed2849c6c5168e1aebc50005ac3d4a4e84af4889e4e0189bb4c787e6ee0be49a1230321006b652764c62a1de35e7e37af2b743e9a5b82cee2211cf3091d2514441b417f5f";
|
"3053020101300506032b6570042204206ed2849c6c5168e1aebc50005ac3d4a4e84af4889e4e0189bb4c787e6ee0be49a1230321006b652764c62a1de35e7e37af2b743e9a5b82cee2211cf3091d2514441b417f5f";
|
||||||
|
224
libcitadel/src/realmfs.rs
Normal file
224
libcitadel/src/realmfs.rs
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
|
||||||
|
use std::path::{Path,PathBuf};
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::{ImageHeader,MetaInfo,Mount,Result,util,verity};
|
||||||
|
|
||||||
|
const BASE_PATH: &'static str = "/storage/realms/realmfs-images";
|
||||||
|
const RUN_DIRECTORY: &str = "/run/images";
|
||||||
|
const MAX_REALMFS_NAME_LEN: usize = 40;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct RealmFS {
|
||||||
|
path: PathBuf,
|
||||||
|
mountpoint: PathBuf,
|
||||||
|
header: ImageHeader,
|
||||||
|
metainfo: MetaInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmFS {
|
||||||
|
|
||||||
|
/// Locate a RealmFS image by name in the default location using the standard name convention
|
||||||
|
pub fn load_by_name(name: &str) -> Result<RealmFS> {
|
||||||
|
if !util::is_valid_name(name, MAX_REALMFS_NAME_LEN) {
|
||||||
|
bail!("Invalid realmfs name '{}'", name);
|
||||||
|
}
|
||||||
|
let path = Path::new(BASE_PATH).join(format!("{}-realmfs.img", name));
|
||||||
|
if !path.exists() {
|
||||||
|
bail!("No image found at {}", path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
RealmFS::load_from_path(path, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn named_image_exists(name: &str) -> bool {
|
||||||
|
if !util::is_valid_name(name, MAX_REALMFS_NAME_LEN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let path = Path::new(BASE_PATH).join(format!("{}-realmfs.img", name));
|
||||||
|
path.exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load RealmFS image from an exact path.
|
||||||
|
pub fn load_from_path<P: AsRef<Path>>(path: P, name: &str) -> Result<RealmFS> {
|
||||||
|
let path = path.as_ref().to_owned();
|
||||||
|
let header = ImageHeader::from_file(&path)?;
|
||||||
|
if !header.is_magic_valid() {
|
||||||
|
bail!("Image file {} does not have a valid header", path.display());
|
||||||
|
}
|
||||||
|
let metainfo = header.metainfo()?;
|
||||||
|
let mountpoint = PathBuf::from(format!("{}/{}-realmfs.mountpoint", RUN_DIRECTORY, name));
|
||||||
|
|
||||||
|
Ok(RealmFS{
|
||||||
|
path,
|
||||||
|
mountpoint,
|
||||||
|
header,
|
||||||
|
metainfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_rw(&mut self) -> Result<()> {
|
||||||
|
// XXX fail if already verity mounted?
|
||||||
|
// XXX strip dm-verity tree if present?
|
||||||
|
// XXX just remount if not verity mounted, but currently ro mounted?
|
||||||
|
self.mount(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_ro(&mut self) -> Result<()> {
|
||||||
|
self.mount(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount(&mut self, read_only: bool) -> Result<()> {
|
||||||
|
let flags = if read_only {
|
||||||
|
Some("-oro")
|
||||||
|
} else {
|
||||||
|
Some("-orw")
|
||||||
|
};
|
||||||
|
if !self.mountpoint.exists() {
|
||||||
|
fs::create_dir_all(self.mountpoint())?;
|
||||||
|
}
|
||||||
|
let loopdev = self.create_loopdev()?;
|
||||||
|
util::mount(&loopdev.to_string_lossy(), self.mountpoint(), flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_verity(&self) -> Result<()> {
|
||||||
|
if self.is_mounted() {
|
||||||
|
bail!("RealmFS image is already mounted");
|
||||||
|
}
|
||||||
|
if !self.is_sealed() {
|
||||||
|
bail!("Cannot verity mount RealmFS image because it's not sealed");
|
||||||
|
}
|
||||||
|
if !self.mountpoint.exists() {
|
||||||
|
fs::create_dir_all(self.mountpoint())?;
|
||||||
|
}
|
||||||
|
let dev = self.setup_verity_device()?;
|
||||||
|
util::mount(&dev.to_string_lossy(), &self.mountpoint, Some("-oro"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_verity_device(&self) -> Result<PathBuf> {
|
||||||
|
|
||||||
|
// TODO verify signature
|
||||||
|
|
||||||
|
if !self.header.has_flag(ImageHeader::FLAG_HASH_TREE) {
|
||||||
|
self.generate_verity()?;
|
||||||
|
}
|
||||||
|
verity::setup_image_device(&self.path, &self.metainfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_loopdev(&self) -> Result<PathBuf> {
|
||||||
|
let args = format!("--offset 4096 -f --show {}", self.path.display());
|
||||||
|
let output = util::exec_cmdline_with_output("/sbin/losetup", args)?;
|
||||||
|
Ok(PathBuf::from(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_mounted(&self) -> bool {
|
||||||
|
match Mount::is_target_mounted(self.mountpoint()) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Error reading /proc/mounts: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fork(&self, new_name: &str) -> Result<RealmFS> {
|
||||||
|
if !util::is_valid_name(new_name, MAX_REALMFS_NAME_LEN) {
|
||||||
|
bail!("Invalid realmfs name '{}'", new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// during install the images have a different base directory
|
||||||
|
let mut new_path = self.path.clone();
|
||||||
|
new_path.pop();
|
||||||
|
new_path.push(format!("{}-realmfs.img", new_name));
|
||||||
|
|
||||||
|
if new_path.exists() {
|
||||||
|
bail!("RealmFS image for name {} already exists", new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = format!("--reflink=auto {} {}", self.path.display(), new_path.display());
|
||||||
|
util::exec_cmdline("/usr/bin/cp", args)?;
|
||||||
|
|
||||||
|
let header = ImageHeader::new();
|
||||||
|
header.set_metainfo_bytes(&self.generate_fork_metainfo(new_name));
|
||||||
|
header.write_header_to(&new_path)?;
|
||||||
|
|
||||||
|
let realmfs = RealmFS::load_from_path(&new_path, new_name)?;
|
||||||
|
|
||||||
|
// forking unseals since presumably the image is being forked to modify it
|
||||||
|
realmfs.truncate_verity()?;
|
||||||
|
Ok(realmfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn generate_fork_metainfo(&self, name: &str) -> Vec<u8> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
writeln!(v, "image-type = \"realmfs\"").unwrap();
|
||||||
|
writeln!(v, "realmfs-name = \"{}\"", name).unwrap();
|
||||||
|
writeln!(v, "nblocks = {}", self.metainfo.nblocks()).unwrap();
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove verity tree from image file by truncating file to the number of blocks in metainfo
|
||||||
|
fn truncate_verity(&self) -> Result<()> {
|
||||||
|
let verity_flag = self.header.has_flag(ImageHeader::FLAG_HASH_TREE);
|
||||||
|
if verity_flag {
|
||||||
|
self.header.clear_flag(ImageHeader::FLAG_HASH_TREE);
|
||||||
|
self.header.write_header_to(&self.path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let meta = self.path.metadata()?;
|
||||||
|
let expected = (self.metainfo.nblocks() + 1) * 4096;
|
||||||
|
let actual = meta.len() as usize;
|
||||||
|
|
||||||
|
if actual > expected {
|
||||||
|
if !verity_flag {
|
||||||
|
warn!("RealmFS length was greater than length indicated by metainfo but FLAG_HASH_TREE not set");
|
||||||
|
}
|
||||||
|
let f = fs::OpenOptions::new().write(true).open(&self.path)?;
|
||||||
|
f.set_len(expected as u64)?;
|
||||||
|
} else if actual < expected {
|
||||||
|
bail!("RealmFS image {} is shorter than length indicated by metainfo", self.path.display());
|
||||||
|
}
|
||||||
|
if verity_flag {
|
||||||
|
warn!("FLAG_HASH_TREE was set but RealmFS image file length matched metainfo length");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_sealed(&self) -> bool {
|
||||||
|
!self.metainfo.verity_root().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_verity(&self) -> Result<()> {
|
||||||
|
self.truncate_verity()?;
|
||||||
|
verity::generate_image_hashtree(&self.path, &self.metainfo)?;
|
||||||
|
self.header.set_flag(ImageHeader::FLAG_HASH_TREE);
|
||||||
|
self.header.write_header_to(&self.path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_overlay(&self, basedir: &Path) -> Result<PathBuf> {
|
||||||
|
if !self.is_mounted() {
|
||||||
|
bail!("Cannot create overlay until realmfs is mounted");
|
||||||
|
}
|
||||||
|
let workdir = basedir.join("workdir");
|
||||||
|
let upperdir = basedir.join("upperdir");
|
||||||
|
let mountpoint = basedir.join("mountpoint");
|
||||||
|
fs::create_dir_all(&workdir)?;
|
||||||
|
fs::create_dir_all(&upperdir)?;
|
||||||
|
fs::create_dir_all(&mountpoint)?;
|
||||||
|
let args = format!("-t overlay realmfs-overlay -olowerdir={},upperdir={},workdir={} {}",
|
||||||
|
self.mountpoint.display(),
|
||||||
|
upperdir.display(),
|
||||||
|
workdir.display(),
|
||||||
|
mountpoint.display());
|
||||||
|
|
||||||
|
util::exec_cmdline("/usr/bin/mount", args)?;
|
||||||
|
Ok(mountpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mountpoint(&self) -> &Path {
|
||||||
|
&self.mountpoint
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user