From c9d36aca59fd6e7300570fce621780a1e85c69b1 Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Thu, 2 Jul 2020 09:34:09 -0400 Subject: [PATCH] Refactor of error handling to replace 'failure' and to display more context for some errors. --- Cargo.lock | 2 - citadel-realms/src/realm/config_realm.rs | 2 +- citadel-realms/src/terminal.rs | 10 +- citadel-tool/Cargo.toml | 1 - citadel-tool/src/boot/disks.rs | 7 +- citadel-tool/src/boot/live.rs | 55 +++---- citadel-tool/src/boot/mod.rs | 25 ++-- citadel-tool/src/boot/rootfs.rs | 13 +- citadel-tool/src/image/mod.rs | 17 +-- citadel-tool/src/install/cli.rs | 22 +-- citadel-tool/src/install/disk.rs | 21 +-- citadel-tool/src/install/installer.rs | 119 ++++++++------- citadel-tool/src/install/mod.rs | 4 +- citadel-tool/src/mkimage/build.rs | 59 ++++---- citadel-tool/src/mkimage/config.rs | 16 +-- citadel-tool/src/realmfs/mod.rs | 16 +-- citadel-tool/src/sync/desktop_file.rs | 12 +- citadel-tool/src/sync/desktop_sync.rs | 37 +++-- citadel-tool/src/sync/icon_cache.rs | 9 +- citadel-tool/src/sync/icons.rs | 34 ++--- citadel-tool/src/sync/parser.rs | 27 ++-- citadel-tool/src/update/kernel.rs | 29 ++-- citadel-tool/src/update/mod.rs | 61 ++++---- libcitadel/Cargo.toml | 1 - libcitadel/src/blockdev.rs | 30 ++-- libcitadel/src/cmdline.rs | 6 +- libcitadel/src/config.rs | 10 +- libcitadel/src/error.rs | 80 +++++++++++ libcitadel/src/exec.rs | 33 +++-- libcitadel/src/header.rs | 64 +++++---- libcitadel/src/keyring.rs | 49 ++++--- libcitadel/src/keys.rs | 14 +- libcitadel/src/lib.rs | 18 +-- libcitadel/src/log.rs | 6 +- libcitadel/src/partition.rs | 37 +++-- libcitadel/src/realm/config.rs | 19 ++- libcitadel/src/realm/create.rs | 41 ++---- libcitadel/src/realm/events.rs | 29 ++-- libcitadel/src/realm/launcher.rs | 40 ++---- libcitadel/src/realm/manager.rs | 20 ++- libcitadel/src/realm/network.rs | 26 ++-- libcitadel/src/realm/overlay.rs | 15 +- libcitadel/src/realm/realm.rs | 13 +- libcitadel/src/realm/realms.rs | 13 +- libcitadel/src/realm/systemd.rs | 41 +++--- libcitadel/src/realmfs/mountpoint.rs | 15 +- libcitadel/src/realmfs/realmfs.rs | 18 +-- libcitadel/src/realmfs/realmfs_set.rs | 17 ++- libcitadel/src/realmfs/resizer.rs | 10 +- libcitadel/src/realmfs/update.rs | 44 +++--- libcitadel/src/resource.rs | 61 ++++---- libcitadel/src/symlink.rs | 19 +-- libcitadel/src/system/lock.rs | 14 +- libcitadel/src/system/loopdev.rs | 5 +- libcitadel/src/system/mounts.rs | 5 +- libcitadel/src/terminal/ansi.rs | 31 ++-- libcitadel/src/terminal/base16.rs | 22 +-- libcitadel/src/terminal/base16_shell.rs | 7 +- libcitadel/src/terminal/color.rs | 10 +- libcitadel/src/terminal/gnome.rs | 2 +- libcitadel/src/terminal/raw.rs | 10 +- libcitadel/src/terminal/restorer.rs | 12 +- libcitadel/src/util.rs | 176 ++++++++++++++++++----- libcitadel/src/verity.rs | 24 ++-- 64 files changed, 954 insertions(+), 751 deletions(-) create mode 100644 libcitadel/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 46a6261..a815a67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,7 +178,6 @@ version = "0.1.0" dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libcitadel 0.1.0", @@ -635,7 +634,6 @@ dependencies = [ "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "dbus 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "inotify 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/citadel-realms/src/realm/config_realm.rs b/citadel-realms/src/realm/config_realm.rs index e4a3a61..50606fd 100644 --- a/citadel-realms/src/realm/config_realm.rs +++ b/citadel-realms/src/realm/config_realm.rs @@ -207,7 +207,7 @@ impl ConfigDialog { let path = self.realm.base_path_file("config"); - if let Err(e) = self.realm.config().write_config(&path) { + if let Err(e) = self.realm.config().write_to(&path) { warn!("Error writing config file {}: {}", path.display(), e); } info!("Config file written to {}", path.display()); diff --git a/citadel-realms/src/terminal.rs b/citadel-realms/src/terminal.rs index 1efa1a5..6884aa7 100644 --- a/citadel-realms/src/terminal.rs +++ b/citadel-realms/src/terminal.rs @@ -54,19 +54,19 @@ impl TerminalTools { let mut t = self.terminal()?; let mut palette = TerminalPalette::default(); palette.load(&mut t) - .map_err(|e| format_err!("error reading palette colors from terminal: {}", e))?; + .map_err(context!("error reading palette colors from terminal"))?; Ok(palette) } fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> { let mut t = self.terminal()?; palette.apply(&mut t) - .map_err(|e| format_err!("error setting palette on terminal: {}", e)) + .map_err(context!("error setting palette on terminal")) } fn terminal(&self) -> Result { AnsiTerminal::new() - .map_err(|e| format_err!("failed to create AnsiTerminal: {}", e)) + .map_err(context!("failed to create AnsiTerminal")) } pub fn apply_base16_by_slug>(&self, slug: S) { @@ -84,9 +84,9 @@ impl TerminalTools { fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> { let mut t = self.terminal()?; t.apply_base16(scheme) - .map_err(|e| format_err!("error setting base16 palette colors: {}", e))?; + .map_err(context!("error setting base16 palette colors"))?; t.clear_screen() - .map_err(|e| format_err!("error clearing screen: {}", e)) + .map_err(context!("error clearing screen")) } } \ No newline at end of file diff --git a/citadel-tool/Cargo.toml b/citadel-tool/Cargo.toml index 912c9bc..f145da2 100644 --- a/citadel-tool/Cargo.toml +++ b/citadel-tool/Cargo.toml @@ -6,7 +6,6 @@ edition = "2018" [dependencies] libcitadel = { path = "../libcitadel" } -failure = "0.1" rpassword = "4.0" clap = "2.33" lazy_static = "1.4" diff --git a/citadel-tool/src/boot/disks.rs b/citadel-tool/src/boot/disks.rs index 301361c..7ab2734 100644 --- a/citadel-tool/src/boot/disks.rs +++ b/citadel-tool/src/boot/disks.rs @@ -1,7 +1,6 @@ use std::path::{Path, PathBuf}; -use std::fs; -use libcitadel::Result; +use libcitadel::{Result, util}; /// /// Represents a disk partition device on the system @@ -21,12 +20,12 @@ impl DiskPartition { /// Return list of all vfat partitions on the system as a `Vec` pub fn boot_partitions(check_guid: bool) -> Result> { - let pp = fs::read_to_string("/proc/partitions")?; + let pp = util::read_to_string("/proc/partitions")?; let mut v = Vec::new(); for line in pp.lines().skip(2) { let part = DiskPartition::from_proc_line(&line) - .map_err(|e| format_err!("Failed to parse line '{}': {}", line, e))?; + .map_err(context!("failed to parse line '{}'", line))?; if part.is_boot_partition(check_guid)? { v.push(part); diff --git a/citadel-tool/src/boot/live.rs b/citadel-tool/src/boot/live.rs index daad80b..5dc2671 100644 --- a/citadel-tool/src/boot/live.rs +++ b/citadel-tool/src/boot/live.rs @@ -1,12 +1,12 @@ - use std::path::Path; use std::ffi::OsStr; use std::fs; use std::thread::{self,JoinHandle}; use std::time::{self,Instant}; -use libcitadel::{Result, UtsName}; +use libcitadel::{Result, UtsName, util}; use libcitadel::ResourceImage; + use crate::boot::disks; use crate::boot::rootfs::setup_rootfs_resource; use crate::install::installer::Installer; @@ -36,7 +36,7 @@ fn copy_artifacts() -> Result<()> { info!("Failed to find partition with images, trying again in 2 seconds"); thread::sleep(time::Duration::from_secs(2)); } - Err(format_err!("Could not find partition containing resource images")) + bail!("could not find partition containing resource images") } @@ -69,24 +69,23 @@ fn kernel_version() -> String { fn deploy_artifacts() -> Result<()> { let run_images = Path::new(IMAGE_DIRECTORY); if !run_images.exists() { - fs::create_dir_all(run_images)?; + util::create_dir(run_images)?; cmd!("/bin/mount", "-t tmpfs -o size=4g images /run/citadel/images")?; } - for entry in fs::read_dir("/boot/images")? { - let entry = entry?; - println!("Copying {:?} from /boot/images to /run/citadel/images", entry.file_name()); - fs::copy(entry.path(), run_images.join(entry.file_name()))?; - } + util::read_directory("/boot/images", |dent| { + println!("Copying {:?} from /boot/images to /run/citadel/images", dent.file_name()); + util::copy_file(dent.path(), run_images.join(dent.file_name())) + })?; let kv = kernel_version(); println!("Copying bzImage-{} to /run/citadel/images", kv); let from = format!("/boot/bzImage-{}", kv); let to = format!("/run/citadel/images/bzImage-{}", kv); - fs::copy(from, to)?; + util::copy_file(&from, &to)?; println!("Copying bootx64.efi to /run/citadel/images"); - fs::copy("/boot/EFI/BOOT/bootx64.efi", "/run/citadel/images/bootx64.efi")?; + util::copy_file("/boot/EFI/BOOT/bootx64.efi", "/run/citadel/images/bootx64.efi")?; deploy_syslinux_artifacts()?; @@ -104,21 +103,23 @@ fn deploy_syslinux_artifacts() -> Result<()> { println!("Copying contents of /boot/syslinux to /run/citadel/images/syslinux"); let run_images_syslinux = Path::new("/run/citadel/images/syslinux"); - fs::create_dir_all(run_images_syslinux)?; - for entry in fs::read_dir(boot_syslinux)? { - let entry = entry?; - if let Some(ext) = entry.path().extension() { + util::create_dir(run_images_syslinux)?; + + util::read_directory(boot_syslinux, |dent| { + if let Some(ext) = dent.path().extension() { if ext == "c32" || ext == "bin" { - fs::copy(entry.path(), run_images_syslinux.join(entry.file_name()))?; + util::copy_file(dent.path(), run_images_syslinux.join(dent.file_name()))?; } } - } - Ok(()) + Ok(()) + }) } fn find_rootfs_image() -> Result { - for entry in fs::read_dir(IMAGE_DIRECTORY)? { - let entry = entry?; + let entries = fs::read_dir(IMAGE_DIRECTORY) + .map_err(context!("error reading directory {}", IMAGE_DIRECTORY))?; + for entry in entries { + let entry = entry.map_err(context!("error reading directory entry"))?; if entry.path().extension() == Some(OsStr::new("img")) { if let Ok(image) = ResourceImage::from_path(&entry.path()) { if image.metainfo().image_type() == "rootfs" { @@ -127,23 +128,23 @@ fn find_rootfs_image() -> Result { } } } - Err(format_err!("Unable to find rootfs resource image in {}", IMAGE_DIRECTORY)) - + bail!("unable to find rootfs resource image in {}", IMAGE_DIRECTORY) } fn decompress_images() -> Result<()> { info!("Decompressing images"); let mut threads = Vec::new(); - for entry in fs::read_dir("/run/citadel/images")? { - let entry = entry?; - if entry.path().extension() == Some(OsStr::new("img")) { - if let Ok(image) = ResourceImage::from_path(&entry.path()) { + util::read_directory("/run/citadel/images", |dent| { + if dent.path().extension() == Some(OsStr::new("img")) { + if let Ok(image) = ResourceImage::from_path(&dent.path()) { if image.is_compressed() { threads.push(decompress_one_image(image)); } } } - } + Ok(()) + })?; + for t in threads { t.join().unwrap()?; } diff --git a/citadel-tool/src/boot/mod.rs b/citadel-tool/src/boot/mod.rs index efbdc27..4494f7e 100644 --- a/citadel-tool/src/boot/mod.rs +++ b/citadel-tool/src/boot/mod.rs @@ -1,7 +1,7 @@ use std::fs; use std::process::exit; -use libcitadel::{Result,ResourceImage,CommandLine,format_error,KeyRing,LogLevel,Logger}; +use libcitadel::{Result, ResourceImage, CommandLine, KeyRing, LogLevel, Logger, util}; use libcitadel::RealmManager; use crate::boot::disks::DiskPartition; use std::path::Path; @@ -21,11 +21,11 @@ pub fn main(args: Vec) { Some(s) if s == "rootfs" => do_rootfs(), Some(s) if s == "setup" => do_setup(), Some(s) if s == "start-realms" => do_start_realms(), - _ => Err(format_err!("Bad or missing argument")), + _ => Err(format_err!("Bad or missing argument").into()), }; if let Err(ref e) = result { - warn!("Failed: {}", format_error(e)); + warn!("Failed: {}", e); exit(1); } } @@ -69,21 +69,21 @@ fn mount_overlay() -> Result<()> { info!("Creating rootfs overlay"); info!("Moving /sysroot mount to /rootfs.ro"); - fs::create_dir_all("/rootfs.ro")?; + util::create_dir("/rootfs.ro")?; cmd!("/usr/bin/mount", "--make-private /")?; cmd!("/usr/bin/mount", "--move /sysroot /rootfs.ro")?; info!("Mounting tmpfs on /rootfs.rw"); - fs::create_dir_all("/rootfs.rw")?; + util::create_dir("/rootfs.rw")?; cmd!("/usr/bin/mount", "-t tmpfs -orw,noatime,mode=755 rootfs.rw /rootfs.rw")?; info!("Creating /rootfs.rw/work /rootfs.rw/upperdir"); - fs::create_dir_all("/rootfs.rw/upperdir")?; - fs::create_dir_all("/rootfs.rw/work")?; + util::create_dir("/rootfs.rw/upperdir")?; + util::create_dir("/rootfs.rw/work")?; info!("Mounting overlay on /sysroot"); cmd!("/usr/bin/mount", "-t overlay overlay -olowerdir=/rootfs.ro,upperdir=/rootfs.rw/upperdir,workdir=/rootfs.rw/work /sysroot")?; info!("Moving /rootfs.ro and /rootfs.rw to new root"); - fs::create_dir_all("/sysroot/rootfs.ro")?; - fs::create_dir_all("/sysroot/rootfs.rw")?; + util::create_dir("/sysroot/rootfs.ro")?; + util::create_dir("/sysroot/rootfs.rw")?; cmd!("/usr/bin/mount", "--move /rootfs.ro /sysroot/rootfs.ro")?; cmd!("/usr/bin/mount", "--move /rootfs.rw /sysroot/rootfs.rw")?; Ok(()) @@ -134,7 +134,8 @@ const LOADER_EFI_VAR_PATH: &str = fn read_loader_dev_efi_var() -> Result> { let efi_var = Path::new(LOADER_EFI_VAR_PATH); if efi_var.exists() { - let s = fs::read(efi_var)? + let s = fs::read(efi_var) + .map_err(context!("could not read {:?}", efi_var))? .into_iter().skip(4) // u32 'attribute' .filter(|b| *b != 0) // string is utf16 ascii .map(|b| (b as char).to_ascii_lowercase()) @@ -150,8 +151,8 @@ pub fn write_automount_units(partition: &DiskPartition) -> Result<()> { let dev = partition.path().display().to_string(); info!("Writing /boot automount units to /run/systemd/system for {}", dev); let mount_unit = BOOT_MOUNT_UNIT.replace("$PARTITION", &dev); - fs::write("/run/systemd/system/boot.mount", mount_unit)?; - fs::write("/run/systemd/system/boot.automount", BOOT_AUTOMOUNT_UNIT)?; + util::write_file("/run/systemd/system/boot.mount", &mount_unit)?; + util::write_file("/run/systemd/system/boot.automount", BOOT_AUTOMOUNT_UNIT)?; info!("Starting /boot automount service"); cmd!("/usr/bin/systemctl", "start boot.automount")?; Ok(()) diff --git a/citadel-tool/src/boot/rootfs.rs b/citadel-tool/src/boot/rootfs.rs index 1cfed24..0c2f5e7 100644 --- a/citadel-tool/src/boot/rootfs.rs +++ b/citadel-tool/src/boot/rootfs.rs @@ -1,8 +1,7 @@ -use std::process::Command; +use std::path::Path; +use std::process::{Command,Stdio}; use libcitadel::{BlockDev, ResourceImage, CommandLine, ImageHeader, Partition, Result, LoopDevice}; -use std::path::Path; -use std::process::Stdio; use libcitadel::verity::Verity; pub fn setup_rootfs() -> Result<()> { @@ -45,11 +44,11 @@ fn setup_partition_verified(p: &mut Partition) -> Result<()> { info!("Creating /dev/mapper/rootfs dm-verity device"); if !CommandLine::nosignatures() { if !p.has_public_key() { - bail!("No public key available for channel {}", p.metainfo().channel()) + bail!("no public key available for channel {}", p.metainfo().channel()) } if !p.is_signature_valid() { p.write_status(ImageHeader::STATUS_BAD_SIG)?; - bail!("Signature verification failed on partition"); + bail!("signature verification failed on partition"); } info!("Image signature is valid for channel {}", p.metainfo().channel()); } @@ -71,7 +70,7 @@ fn setup_linear_mapping(blockdev: &Path) -> Result<()> { .success(); if !ok { - bail!("Failed to set up linear identity mapping with /usr/sbin/dmsetup"); + bail!("failed to set up linear identity mapping with /usr/sbin/dmsetup"); } Ok(()) } @@ -89,7 +88,7 @@ fn choose_boot_partiton(scan: bool) -> Result { for p in partitions { best = compare_boot_partitions(best, p); } - best.ok_or_else(|| format_err!("No partition found to boot from")) + best.ok_or_else(|| format_err!("No partition found to boot from").into()) } fn compare_boot_partitions(a: Option, b: Partition) -> Option { diff --git a/citadel-tool/src/image/mod.rs b/citadel-tool/src/image/mod.rs index 57a70e0..46de9e4 100644 --- a/citadel-tool/src/image/mod.rs +++ b/citadel-tool/src/image/mod.rs @@ -3,8 +3,7 @@ use std::process::exit; use clap::{App,Arg,SubCommand,ArgMatches}; use clap::AppSettings::*; -use libcitadel::{Result,ResourceImage,Logger,LogLevel,format_error,Partition,KeyPair,ImageHeader}; -use std::fs; +use libcitadel::{Result, ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util}; use hex; pub fn main(args: Vec) { @@ -89,7 +88,7 @@ pub fn main(args: Vec) { }; if let Err(ref e) = result { - println!("Error: {}", format_error(e)); + println!("Error: {}", e); exit(1); } } @@ -252,8 +251,7 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> { if image_dest.exists() { rotate(&image_dest)?; } - fs::rename(source,image_dest)?; - Ok(()) + util::rename(source, &image_dest) } fn rotate(path: &Path) -> Result<()> { @@ -262,11 +260,8 @@ fn rotate(path: &Path) -> Result<()> { } let filename = path.file_name().unwrap(); let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy())); - if dot_zero.exists() { - fs::remove_file(&dot_zero)?; - } - fs::rename(path, &dot_zero)?; - Ok(()) + util::remove_file(&dot_zero)?; + util::rename(path, &dot_zero) } fn genkeys() -> Result<()> { @@ -334,5 +329,5 @@ fn choose_install_partition(verbose: bool) -> Result { return Ok(p.clone()) } } - Err(format_err!("No suitable install partition found")) + bail!("No suitable install partition found") } diff --git a/citadel-tool/src/install/cli.rs b/citadel-tool/src/install/cli.rs index bb1d1d8..b032a62 100644 --- a/citadel-tool/src/install/cli.rs +++ b/citadel-tool/src/install/cli.rs @@ -13,7 +13,7 @@ pub fn run_cli_install() -> Result { display_disk(&disk); - let passphrase = match read_passphrase()? { + let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? { Some(passphrase) => passphrase, None => return Ok(false), }; @@ -29,7 +29,7 @@ pub fn run_cli_install_with>(target: P) -> Result { let disk = find_disk_by_path(target.as_ref())?; display_disk(&disk); - let passphrase = match read_passphrase()? { + let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? { Some(passphrase) => passphrase, None => return Ok(false), }; @@ -66,17 +66,17 @@ fn find_disk_by_path(path: &Path) -> Result { return Ok(disk.clone()); } } - Err(format_err!("Installation target {} is not a valid disk", path.display())) + bail!("installation target {} is not a valid disk", path.display()) } fn choose_disk() -> Result> { let disks = Disk::probe_all()?; if disks.is_empty() { - bail!("No disks found."); + bail!("no disks found."); } loop { - prompt_choose_disk(&disks)?; + prompt_choose_disk(&disks); let line = read_line()?; if line == "q" || line == "Q" { return Ok(None); @@ -89,26 +89,26 @@ fn choose_disk() -> Result> { } } -fn prompt_choose_disk(disks: &[Disk]) -> Result<()> { +fn prompt_choose_disk(disks: &[Disk]) { println!("Available disks:\n"); for (idx,disk) in disks.iter().enumerate() { println!(" [{}]: {} Size: {} Model: {}", idx + 1, disk.path().display(), disk.size_str(), disk.model()); } print!("\nChoose a disk to install to (q to quit): "); - io::stdout().flush()?; - Ok(()) + let _ = io::stdout().flush(); } fn read_line() -> Result { let mut input = String::new(); - io::stdin().read_line(&mut input)?; + io::stdin().read_line(&mut input) + .map_err(context!("error reading line from stdin"))?; if input.ends_with('\n') { input.pop(); } Ok(input) } -fn read_passphrase() -> Result> { +fn read_passphrase() -> io::Result> { loop { println!("Enter a disk encryption passphrase (or 'q' to quit)"); println!(); @@ -141,7 +141,7 @@ fn confirm_install(disk: &Disk) -> Result { println!(" Model: {}", disk.model()); println!(); print!("Type YES (uppercase) to continue with install: "); - io::stdout().flush()?; + let _ = io::stdout().flush(); let answer = read_line()?; Ok(answer == "YES") } diff --git a/citadel-tool/src/install/disk.rs b/citadel-tool/src/install/disk.rs index 4549bce..22d508d 100644 --- a/citadel-tool/src/install/disk.rs +++ b/citadel-tool/src/install/disk.rs @@ -1,7 +1,7 @@ use std::path::{Path,PathBuf}; use std::fs; -use libcitadel::Result; +use libcitadel::{Result, util}; #[derive(Debug, Clone)] @@ -15,13 +15,15 @@ pub struct Disk { impl Disk { pub fn probe_all() -> Result> { let mut v = Vec::new(); - for entry in fs::read_dir("/sys/block")? { - let path = entry?.path(); + util::read_directory("/sys/block", |dent| { + let path = dent.path(); if Disk::is_disk_device(&path) { let disk = Disk::read_device(&path)?; v.push(disk); } - } + Ok(()) + })?; + Ok(v) } @@ -32,18 +34,20 @@ impl Disk { fn read_device(device: &Path) -> Result { let path = Path::new("/dev/").join(device.file_name().unwrap()); - let size = fs::read_to_string(device.join("size"))? + let size = fs::read_to_string(device.join("size")) + .map_err(context!("failed to read device size for {:?}", device))? .trim() - .parse::()?; + .parse::() + .map_err(context!("error parsing device size for {:?}", device))?; let size_str = format!("{}G", size >> 21); - let model = fs::read_to_string(device.join("device/model"))? + let model = fs::read_to_string(device.join("device/model")) + .map_err(context!("failed to read device/model for {:?}", device))? .trim() .to_string(); Ok(Disk { path, size, size_str, model }) - } pub fn path(&self) -> &Path { @@ -57,5 +61,4 @@ impl Disk { pub fn model(&self) -> &str { &self.model } - } diff --git a/citadel-tool/src/install/installer.rs b/citadel-tool/src/install/installer.rs index 125af55..547e155 100644 --- a/citadel-tool/src/install/installer.rs +++ b/citadel-tool/src/install/installer.rs @@ -1,7 +1,6 @@ use std::cell::RefCell; use std::fs::{self,File}; use std::io::{self,Write}; -use std::os::unix::fs as unixfs; use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::Command; @@ -183,12 +182,12 @@ impl Installer { ]; if !self.target().exists() { - bail!("Target device {} does not exist", self.target().display()); + bail!("target device {:?} does not exist", self.target()); } for a in artifacts { if !self.artifact_path(a).exists() { - bail!("Required install artifact {} does not exist in {}", a, self.artifact_directory); + bail!("required install artifact {} does not exist in {}", a, self.artifact_directory); } } @@ -222,10 +221,11 @@ impl Installer { "/bin/mount -t tmpfs storage-tmpfs /sysroot/storage", ], &[])?; - fs::create_dir_all("/sysroot/storage/realms")?; + util::create_dir("/sysroot/storage/realms")?; + self.cmd("/bin/mount --bind /sysroot/storage/realms /sysroot/realms")?; - let cmdline = fs::read_to_string("/proc/cmdline")?; + let cmdline = util::read_to_string("/proc/cmdline")?; if cmdline.contains("citadel.live") { self.setup_live_realm()?; } @@ -238,10 +238,10 @@ impl Installer { let base_realmfs = realmfs_dir.join("base-realmfs.img"); self.info(format!("creating directory {}", realmfs_dir.display()))?; - fs::create_dir_all(&realmfs_dir)?; + util::create_dir(&realmfs_dir)?; self.info(format!("creating symlink {} -> {}", base_realmfs.display(), "/run/citadel/images/base-realmfs.img"))?; - unixfs::symlink("/run/citadel/images/base-realmfs.img", &base_realmfs)?; + util::symlink("/run/citadel/images/base-realmfs.img", &base_realmfs)?; let realmfs = RealmFS::load_from_path("/run/citadel/images/base-realmfs.img")?; realmfs.activate()?; @@ -260,8 +260,9 @@ impl Installer { fn setup_luks(&self) -> Result<()> { self.header("Setting up LUKS disk encryption")?; - fs::create_dir_all(INSTALL_MOUNT)?; - fs::write(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?; + util::create_dir(INSTALL_MOUNT)?; + util::write_file(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?; + let luks_partition = self.target_partition(2); self.cmd_list(LUKS_COMMANDS, &[ @@ -270,8 +271,7 @@ impl Installer { ("$LUKS_PASSFILE", LUKS_PASSPHRASE_FILE), ])?; - fs::remove_file(LUKS_PASSPHRASE_FILE)?; - Ok(()) + util::remove_file(LUKS_PASSPHRASE_FILE) } fn setup_lvm(&self) -> Result<()> { @@ -286,17 +286,16 @@ impl Installer { self.cmd(format!("/bin/mount {} {}", boot_partition, INSTALL_MOUNT))?; - fs::create_dir_all(format!("{}/loader/entries", INSTALL_MOUNT))?; + util::create_dir(format!("{}/loader/entries", INSTALL_MOUNT))?; self.info("Writing /boot/loader/loader.conf")?; - fs::write(format!("{}/loader/loader.conf", INSTALL_MOUNT), LOADER_CONF)?; + util::write_file(format!("{}/loader/loader.conf", INSTALL_MOUNT), LOADER_CONF)?; let kernel_version = self.kernel_version(); self.info("Writing /boot/entries/boot.conf")?; - fs::write(format!("{}/loader/entries/boot.conf", INSTALL_MOUNT), BOOT_CONF + util::write_file(format!("{}/loader/entries/boot.conf", INSTALL_MOUNT), BOOT_CONF .replace("$KERNEL_CMDLINE", KERNEL_CMDLINE) - .replace("$KERNEL_VERSION", &kernel_version) - )?; + .replace("$KERNEL_VERSION", &kernel_version))?; let kernel_bzimage = format!("bzImage-{}", kernel_version); self.copy_artifact(&kernel_bzimage, INSTALL_MOUNT)?; @@ -318,26 +317,26 @@ impl Installer { self.header("Installing syslinux")?; let syslinux_src = self.artifact_path("syslinux"); if !syslinux_src.exists() { - bail!("No syslinux directory found in artifact directory, cannot install syslinux"); + bail!("no syslinux directory found in artifact directory, cannot install syslinux"); } let dst = Path::new(INSTALL_MOUNT).join("syslinux"); - fs::create_dir_all(&dst)?; + util::create_dir(&dst)?; + self.info("Copying syslinux files to /boot/syslinux")?; - for entry in fs::read_dir(&syslinux_src)? { - let entry = entry?; - fs::copy(entry.path(), dst.join(entry.file_name()))?; - } + util::read_directory(&syslinux_src, |dent| { + util::copy_file(dent.path(), dst.join(dent.file_name())) + })?; + self.info("Writing syslinux.cfg")?; - fs::write(dst.join("syslinux.cfg"), + util::write_file(dst.join("syslinux.cfg"), SYSLINUX_CONF.replace("$KERNEL_CMDLINE", KERNEL_CMDLINE))?; - self.cmd(format!("/sbin/extlinux --install {}", dst.display()))?; - Ok(()) + self.cmd(format!("/sbin/extlinux --install {}", dst.display())) } fn setup_syslinux_post_umount(&self) -> Result<()> { let mbrbin = self.artifact_path("syslinux/gptmbr.bin"); if !mbrbin.exists() { - bail!("Could not find MBR image: {}", mbrbin.display()); + bail!("could not find MBR image: {:?}", mbrbin); } self.cmd(format!("/bin/dd bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target().display()))?; self.cmd(format!("/sbin/parted -s {} set 1 legacy_boot on", self.target_str())) @@ -351,8 +350,7 @@ impl Installer { &[("$INSTALL_MOUNT", INSTALL_MOUNT)])?; self.setup_storage()?; - self.cmd(format!("/bin/umount {}", INSTALL_MOUNT))?; - Ok(()) + self.cmd(format!("/bin/umount {}", INSTALL_MOUNT)) } fn setup_storage(&self) -> Result<()> { @@ -367,12 +365,12 @@ impl Installer { self.setup_apt_cacher_realm()?; self.info("Creating global realm config file")?; - fs::write(self.storage().join("realms/config"), self.global_realm_config())?; + util::write_file(self.storage().join("realms/config"), self.global_realm_config())?; self.info("Creating /Shared realms directory")?; let shared = self.storage().join("realms/Shared"); - fs::create_dir_all(&shared)?; + util::create_dir(&shared)?; util::chown_user(&shared)?; Ok(()) @@ -386,17 +384,20 @@ impl Installer { fn setup_base_realmfs(&self) -> Result<()> { let realmfs_dir = self.storage().join("realms/realmfs-images"); - fs::create_dir_all(&realmfs_dir)?; + util::create_dir(&realmfs_dir)?; self.sparse_copy_artifact("base-realmfs.img", &realmfs_dir)?; - self.cmd(format!("/usr/bin/citadel-image decompress {}/base-realmfs.img", realmfs_dir.display()))?; - - Ok(()) + self.cmd(format!("/usr/bin/citadel-image decompress {}/base-realmfs.img", realmfs_dir.display())) } fn setup_realm_skel(&self) -> Result<()> { let realm_skel = self.storage().join("realms/skel"); - fs::create_dir_all(&realm_skel)?; - util::copy_tree_with_chown(&self.skel(), &realm_skel, (1000,1000))?; + util::create_dir(&realm_skel)?; + util::copy_tree_with_chown(&self.skel(), &realm_skel, (1000,1000)) + } + + fn create_realmlock(&self, dir: &Path) -> Result<()> { + fs::File::create(dir.join(".realmlock")) + .map_err(context!("failed to create {:?}/.realmlock file", dir))?; Ok(()) } @@ -407,7 +408,7 @@ impl Installer { self.info("Creating home directory /realms/realm-main/home")?; let home = realm.join("home"); - fs::create_dir_all(&home)?; + util::create_dir(&home)?; util::chown_user(&home)?; self.info("Copying /realms/skel into home diectory")?; @@ -415,16 +416,14 @@ impl Installer { if let Some(scheme) = Base16Scheme::by_name(MAIN_TERMINAL_SCHEME) { scheme.write_realm_files(&home)?; - fs::write(realm.join("config"), MAIN_CONFIG.replace("$SCHEME", MAIN_TERMINAL_SCHEME))?; + util::write_file(realm.join("config"), MAIN_CONFIG.replace("$SCHEME", MAIN_TERMINAL_SCHEME))?; } util::chown_tree(&home, (1000,1000), false)?; self.info("Creating default.realm symlink")?; - unixfs::symlink("/realms/realm-main", self.storage().join("realms/default.realm"))?; + util::symlink("/realms/realm-main", self.storage().join("realms/default.realm"))?; - fs::File::create(realm.join(".realmlock"))?; - - Ok(()) + self.create_realmlock(&realm) } fn setup_apt_cacher_realm(&self) -> Result<()> { @@ -433,19 +432,18 @@ impl Installer { self.info("Creating home directory /realms/realm-apt-cacher/home")?; let home = realm_base.join("home"); - fs::create_dir_all(&home)?; + util::create_dir(&home)?; util::chown_user(&home)?; let path = home.join("apt-cacher-ng"); - fs::create_dir_all(&path)?; + util::create_dir(&path)?; util::chown_user(&path)?; self.info("Copying /realms/skel into home diectory")?; util::copy_tree(&self.storage().join("realms/skel"), &home)?; self.info("Creating apt-cacher config file")?; - fs::write(realm_base.join("config"), APT_CACHER_CONFIG)?; - fs::File::create(realm_base.join(".realmlock"))?; - Ok(()) + util::write_file(realm_base.join("config"), APT_CACHER_CONFIG)?; + self.create_realmlock(&realm_base) } fn setup_storage_resources(&self) -> Result<()> { @@ -454,22 +452,19 @@ impl Installer { None => "dev", }; let resources = self.storage().join("resources").join(channel); - fs::create_dir_all(&resources)?; + util::create_dir(&resources)?; self.sparse_copy_artifact(EXTRA_IMAGE_NAME, &resources)?; let kernel_img = self.kernel_imagename(); - self.sparse_copy_artifact(&kernel_img, &resources)?; - - Ok(()) + self.sparse_copy_artifact(&kernel_img, &resources) } fn install_rootfs_partitions(&self) -> Result<()> { self.header("Installing rootfs partitions")?; let rootfs = self.artifact_path("citadel-rootfs.img"); self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha {}", rootfs.display()))?; - self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha --no-prefer {}", rootfs.display()))?; - Ok(()) + self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha --no-prefer {}", rootfs.display())) } fn finish_install(&self) -> Result<()> { @@ -522,14 +517,12 @@ impl Installer { self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?; let src = self.artifact_path(filename); let target = target.as_ref(); - if !target.exists() { - fs::create_dir_all(target)?; - } + util::create_dir(target)?; let dst = target.join(filename); if sparse { self.cmd(format!("/bin/cp --sparse=always {} {}", src.display(), dst.display()))?; } else { - fs::copy(src, dst)?; + util::copy_file(src, dst)?; } Ok(()) } @@ -542,12 +535,17 @@ impl Installer { self.output(format!(" [>] {}", s.as_ref())) } + fn output>(&self, s: S) -> Result<()> { - println!("{}", s.as_ref()); + self.write_output(s.as_ref()).map_err(context!("error writing output")) + } + + fn write_output(&self, s: &str) -> io::Result<()> { + println!("{}", s); io::stdout().flush()?; if let Some(ref file) = self.logfile { - writeln!(file.borrow_mut(), "{}", s.as_ref())?; + writeln!(file.borrow_mut(), "{}", s)?; file.borrow_mut().flush()?; } Ok(()) @@ -580,7 +578,8 @@ impl Installer { command.args(&args[1..]); - let result = command.output()?; + let result = command.output() + .map_err(context!("error running command {}", args[0]))?; for line in String::from_utf8_lossy(&result.stdout).lines() { self.output(format!(" {}", line))?; diff --git a/citadel-tool/src/install/mod.rs b/citadel-tool/src/install/mod.rs index 8b64f39..5a39656 100644 --- a/citadel-tool/src/install/mod.rs +++ b/citadel-tool/src/install/mod.rs @@ -4,8 +4,6 @@ pub(crate) mod installer; mod cli; mod disk; -use libcitadel::format_error; - pub fn main(args: Vec) { let mut args = args.iter().skip(1); let result = if let Some(dev) = args.next() { @@ -17,7 +15,7 @@ pub fn main(args: Vec) { let ok = match result { Ok(ok) => ok, Err(ref err) => { - println!("Install failed: {}", format_error(err)); + println!("Install failed: {}", err); exit(1); }, }; diff --git a/citadel-tool/src/mkimage/build.rs b/citadel-tool/src/mkimage/build.rs index 4130431..214fab1 100644 --- a/citadel-tool/src/mkimage/build.rs +++ b/citadel-tool/src/mkimage/build.rs @@ -3,8 +3,7 @@ use std::fs::OpenOptions; use std::fs::{self,File}; use std::io::{self,Write}; -use failure::ResultExt; -use libcitadel::{Result,ImageHeader,devkeys}; +use libcitadel::{Result, ImageHeader, devkeys, util}; use super::config::BuildConfig; use std::path::Path; @@ -52,13 +51,13 @@ impl UpdateBuilder { pub fn build(&mut self) -> Result<()> { info!("Copying source file to {}", self.image_data.display()); - fs::copy(self.config.source(), &self.image_data)?; + util::copy_file(self.config.source(), &self.image_data)?; self.pad_image() - .context("failed writing padding to image")?; - + .map_err(context!("failed writing padding to image"))?; + self.generate_verity() - .context("failed generating dm-verity hash tree")?; + .map_err(context!("failed generating dm-verity hash tree"))?; self.calculate_shasum()?; @@ -66,8 +65,7 @@ impl UpdateBuilder { self.compress_image()?; - self.write_final_image() - .context("failed to write final image file")?; + self.write_final_image()?; Ok(()) } @@ -77,10 +75,11 @@ impl UpdateBuilder { } fn pad_image(&mut self) -> Result<()> { - let meta = self.image().metadata()?; + let meta = self.image().metadata() + .map_err(context!("failed to read metadata from {:?}", self.image()))?; let len = meta.len() as usize; if len % 512 != 0 { - bail!("Image file size is not a multiple of sector size (512 bytes)"); + bail!("image file size is not a multiple of sector size (512 bytes)"); } let padlen = align(len, BLOCK_SIZE) - len; @@ -89,8 +88,11 @@ impl UpdateBuilder { let zeros = vec![0u8; padlen]; let mut file = OpenOptions::new() .append(true) - .open(self.image())?; - file.write_all(&zeros)?; + .open(self.image()) + .map_err(context!("failed to open file {:?}", self.image()))?; + + file.write_all(&zeros) + .map_err(context!("error writing image file"))?; } let nblocks = (len + padlen) / 4096; @@ -102,7 +104,7 @@ impl UpdateBuilder { fn calculate_shasum(&mut self) -> Result<()> { let output = cmd_with_output!("sha256sum", "{}", self.image().display()) - .context(format!("failed to calculate sha256 on {}", self.image().display()))?; + .map_err(context!("failed to calculate sha256 on {:?}", self.image()))?; let v: Vec<&str> = output.split_whitespace().collect(); let shasum = v[0].trim().to_owned(); info!("Sha256 of image data is {}", shasum); @@ -113,7 +115,7 @@ impl UpdateBuilder { fn prepend_empty_block(&mut self) -> Result<()> { let tmpfile = self.image().with_extension("tmp"); cmd!("/bin/dd", "if={} of={} bs=4096 seek=1 conv=sparse", self.image().display(), tmpfile.display())?; - fs::rename(tmpfile, self.image())?; + util::rename(tmpfile, self.image())?; Ok(()) } @@ -124,8 +126,9 @@ impl UpdateBuilder { let verity = Verity::new(self.image())?; let output = verity.generate_initial_hashtree(&hashfile)?; - fs::write(outfile, output.output()) - .context("failed to write veritysetup command output to a file")?; + if let Err(err) = fs::write(outfile, output.output()) { + bail!("Failed to write veritysetup command output to a file: {}", err); + } let root = match output.root_hash() { Some(s) => s.to_owned(), @@ -148,10 +151,11 @@ impl UpdateBuilder { fn compress_image(&self) -> Result<()> { if self.config.compress() { info!("Compressing image data"); - cmd!("xz", "-T0 {}", self.image().display()) - .context(format!("failed to compress {}", self.image().display()))?; + if let Err(err) = cmd!("xz", "-T0 {}", self.image().display()) { + bail!("failed to compress {:?}: {}", self.image(), err); + } // Rename back to original image_data filename - fs::rename(self.image().with_extension("xz"), self.image())?; + util::rename(self.image().with_extension("xz"), self.image())?; } Ok(()) } @@ -161,14 +165,17 @@ impl UpdateBuilder { let target = self.config.workdir_path(self.target_filename()); let mut out = File::create(&target) - .context(format!("could not open output file {}", target.display()))?; + .map_err(context!("could not open output file {:?}", target))?; - header.write_header(&out)?; + header.write_header(&out) + .map_err(context!("error writing header to {:?}", target))?; let mut data = File::open(&self.image()) - .context(format!("could not open image data file {}", self.image().display()))?; + .map_err(context!("could not open image data file {:?}", self.image()))?; + io::copy(&mut data, &mut out) - .context("error copying image data to output file")?; + .map_err(context!("error copying image data to output file"))?; + Ok(()) } @@ -180,12 +187,12 @@ impl UpdateBuilder { } let metainfo = self.generate_metainfo(); - fs::write(self.config.workdir_path("metainfo"), &metainfo)?; + util::write_file(self.config.workdir_path("metainfo"), &metainfo)?; hdr.set_metainfo_bytes(&metainfo)?; if self.config.channel() == "dev" { let sig = devkeys().sign(&metainfo); - hdr.set_signature(sig.to_bytes())?; + hdr.set_signature(sig.to_bytes()); } Ok(hdr) } @@ -195,7 +202,7 @@ impl UpdateBuilder { self._generate_metainfo().unwrap() } - fn _generate_metainfo(&self) -> Result> { + fn _generate_metainfo(&self) -> io::Result> { assert!(self.verity_salt.is_some() && self.verity_root.is_some(), "no verity-salt/verity-root in generate_metainfo()"); diff --git a/citadel-tool/src/mkimage/config.rs b/citadel-tool/src/mkimage/config.rs index 03700b0..7ff2e09 100644 --- a/citadel-tool/src/mkimage/config.rs +++ b/citadel-tool/src/mkimage/config.rs @@ -1,10 +1,8 @@ -use std::fs::File; -use std::io::Read; use std::path::{Path, PathBuf}; use toml; -use libcitadel::Result; +use libcitadel::{Result, util}; #[derive(Deserialize)] pub struct BuildConfig { @@ -39,10 +37,7 @@ impl BuildConfig { path.push("mkimage.conf"); } - let mut config = match BuildConfig::from_path(&path) { - Ok(config) => config, - Err(e) => bail!("Failed to load config file {}: {}", path.display(), e), - }; + let mut config = BuildConfig::from_path(&path)?; path.pop(); config.basedir = path; @@ -55,10 +50,9 @@ impl BuildConfig { } fn from_path(path: &Path) -> Result { - let mut f = File::open(path)?; - let mut s = String::new(); - f.read_to_string(&mut s)?; - let config = toml::from_str::(&s)?; + let s = util::read_to_string(path)?; + let config = toml::from_str::(&s) + .map_err(context!("Failed to parse build config file {:?}", path))?; config.validate()?; Ok(config) } diff --git a/citadel-tool/src/realmfs/mod.rs b/citadel-tool/src/realmfs/mod.rs index c750b44..376b04b 100644 --- a/citadel-tool/src/realmfs/mod.rs +++ b/citadel-tool/src/realmfs/mod.rs @@ -7,7 +7,6 @@ use clap::SubCommand; use clap::AppSettings::*; use clap::Arg; use libcitadel::ResizeSize; -use libcitadel::format_error; use std::process::exit; pub fn main(args: Vec) { @@ -87,7 +86,7 @@ is the final absolute size of the image.") }; if let Err(ref e) = result { - eprintln!("Error: {}", format_error(e)); + eprintln!("Error: {}", e); exit(1); } } @@ -125,12 +124,13 @@ fn parse_resize_size(s: &str) -> Result { .parse::() .map_err(|_| format_err!("Unable to parse size value '{}'",s))?; - match unit { - Some('g') | Some('G') => Ok(ResizeSize::gigs(size)), - Some('m') | Some('M') => Ok(ResizeSize::megs(size)), - Some(c) => Err(format_err!("Unknown size unit '{}'", c)), - None => Ok(ResizeSize::blocks(size)), - } + let sz = match unit { + Some('g') | Some('G') => ResizeSize::gigs(size), + Some('m') | Some('M') => ResizeSize::megs(size), + Some(c) => bail!("Unknown size unit '{}'", c), + None => ResizeSize::blocks(size), + }; + Ok(sz) } fn resize(arg_matches: &ArgMatches) -> Result<()> { diff --git a/citadel-tool/src/sync/desktop_file.rs b/citadel-tool/src/sync/desktop_file.rs index 34dffc2..9743bff 100644 --- a/citadel-tool/src/sync/desktop_file.rs +++ b/citadel-tool/src/sync/desktop_file.rs @@ -4,6 +4,7 @@ use std::path::Path; use std::collections::HashMap; use libcitadel::Result; +use std::io; pub struct DesktopFile { @@ -20,14 +21,17 @@ impl DesktopFile { pub fn write_to_dir>(&self, directory: P) -> Result<()> { let path = directory.as_ref().join(&self.filename); - let f = File::create(&path)?; - self.write_to(f)?; + let f = File::create(&path) + .map_err(context!("failed to open desktop file {:?}", path))?; + self.write_to(f) + .map_err(context!("error writing to desktop file {:?}", path))?; Ok(()) } pub fn write_to(&self, mut w: W) -> Result<()> { for line in &self.lines { - line.write_to(&mut w)?; + line.write_to(&mut w) + .map_err(context!("error writing line ({:?}) to desktop file", line))?; } Ok(()) } @@ -173,7 +177,7 @@ impl Line { } } - fn write_to(&self, mut w: W) -> Result<()> { + fn write_to(&self, mut w: W) -> io::Result<()> { match *self { Line::Empty => writeln!(w)?, Line::Comment(ref s) => writeln!(w, "#{}", s)?, diff --git a/citadel-tool/src/sync/desktop_sync.rs b/citadel-tool/src/sync/desktop_sync.rs index d7815df..ed916e7 100644 --- a/citadel-tool/src/sync/desktop_sync.rs +++ b/citadel-tool/src/sync/desktop_sync.rs @@ -1,10 +1,9 @@ use std::collections::HashSet; use std::ffi::{OsStr,OsString}; -use std::fs; use std::path::{Path,PathBuf}; use std::time::SystemTime; -use libcitadel::{Realm,Realms,Result}; +use libcitadel::{Realm, Realms, Result, util}; use crate::sync::parser::DesktopFileParser; use std::fs::DirEntry; use crate::sync::icons::IconSync; @@ -69,9 +68,7 @@ impl DesktopFileSync { let target = Path::new(Self::CITADEL_APPLICATIONS); - if !target.exists() { - fs::create_dir_all(&target)?; - } + util::create_dir(&target)?; if clear { Self::clear_target_files()?; @@ -89,14 +86,15 @@ impl DesktopFileSync { fn collect_source_files(&mut self, directory: impl AsRef) -> Result<()> { let directory = Realms::current_realm_symlink().join(directory.as_ref()); if directory.exists() { - for entry in fs::read_dir(directory)? { - self.process_source_entry(entry?); - } + util::read_directory(&directory, |dent| { + self.process_source_entry(dent); + Ok(()) + })?; } Ok(()) } - fn process_source_entry(&mut self, entry: DirEntry) { + fn process_source_entry(&mut self, entry: &DirEntry) { let path = entry.path(); if path.extension() == Some(OsStr::new("desktop")) { if let Some(mtime) = Self::mtime(&path) { @@ -106,24 +104,21 @@ impl DesktopFileSync { } pub fn clear_target_files() -> Result<()> { - for entry in fs::read_dir(Self::CITADEL_APPLICATIONS)? { - let entry = entry?; - fs::remove_file(entry.path())?; - } - Ok(()) + util::read_directory(Self::CITADEL_APPLICATIONS, |dent| { + util::remove_file(dent.path()) + }) } fn remove_missing_target_files(&mut self) -> Result<()> { let sources = self.source_filenames(); - for entry in fs::read_dir(Self::CITADEL_APPLICATIONS)? { - let entry = entry?; - if !sources.contains(&entry.file_name()) { - let path = entry.path(); + util::read_directory(Self::CITADEL_APPLICATIONS, |dent| { + if !sources.contains(&dent.file_name()) { + let path = dent.path(); verbose!("Removing desktop entry that no longer exists: {:?}", path); - fs::remove_file(path)?; + util::remove_file(path)?; } - } - Ok(()) + Ok(()) + }) } fn mtime(path: &Path) -> Option { diff --git a/citadel-tool/src/sync/icon_cache.rs b/citadel-tool/src/sync/icon_cache.rs index a9de13e..ffcedd5 100644 --- a/citadel-tool/src/sync/icon_cache.rs +++ b/citadel-tool/src/sync/icon_cache.rs @@ -13,7 +13,8 @@ pub struct IconCache { impl IconCache { pub fn open>(path: P) -> Result { - let file = File::open(path.as_ref())?; + let path = path.as_ref(); + let file = File::open(path).map_err(context!("failed to open {:?}", path))?; Ok(IconCache { file }) } @@ -46,7 +47,8 @@ impl IconCache { let mut output = String::new(); let mut nread = 0; loop { - let n = self.file.read_at(&mut buf, (offset + nread)as u64 )?; + let n = self.file.read_at(&mut buf, (offset + nread)as u64 ) + .map_err(context!("failed to read from icon cache at offset {}", (offset + nread)))?; if n == 0 { return Ok(output); } @@ -80,7 +82,8 @@ impl IconCache { fn read_exact_at(&self, buf: &mut [u8], offset: usize) -> Result<()> { let mut nread = 0; while nread < buf.len() { - let sz = self.file.read_at(&mut buf[nread..], (offset + nread) as u64)?; + let sz = self.file.read_at(&mut buf[nread..], (offset + nread) as u64) + .map_err(context!("failed to read from icon cache at offset {}", (offset + nread)))?; nread += sz; if sz == 0 { bail!("bad offset"); diff --git a/citadel-tool/src/sync/icons.rs b/citadel-tool/src/sync/icons.rs index 3eded12..51c79f1 100644 --- a/citadel-tool/src/sync/icons.rs +++ b/citadel-tool/src/sync/icons.rs @@ -1,9 +1,8 @@ use crate::sync::icon_cache::IconCache; use std::collections::HashSet; -use std::fs; use std::path::Path; -use libcitadel::{Result, Realms}; +use libcitadel::{Result, Realms, util}; use std::cell::{RefCell, Cell}; pub struct IconSync { @@ -57,14 +56,14 @@ impl IconSync { let mut names: Vec = self.known.borrow().iter().map(|s| s.to_string()).collect(); names.sort_unstable(); let out = names.join("\n") + "\n"; - fs::write(Self::KNOWN_ICONS_FILE, out)?; + util::write_file(Self::KNOWN_ICONS_FILE, out)?; Ok(()) } fn read_known_cache() -> Result> { let target = Path::new(Self::KNOWN_ICONS_FILE); if target.exists() { - let content = fs::read_to_string(target)?; + let content = util::read_to_string(target)?; Ok(content.lines().map(|s| s.to_string()).collect()) } else { Ok(HashSet::new()) @@ -77,13 +76,14 @@ impl IconSync { return Ok(false) } let mut found = false; - for entry in fs::read_dir(&base)? { - let entry = entry?; - let apps = entry.path().join("apps"); + util::read_directory(&base, |dent| { + let apps = dent.path().join("apps"); if apps.exists() && self.search_subdirectory(&base, &apps, icon_name)? { found = true; } - } + Ok(()) + })?; + if found { self.add_known(icon_name); } @@ -92,28 +92,28 @@ impl IconSync { fn search_subdirectory(&self, base: &Path, subdir: &Path, icon_name: &str) -> Result { let mut found = false; - for entry in fs::read_dir(subdir)? { - let entry = entry?; - let path = entry.path(); + util::read_directory(subdir, |dent| { + let path = dent.path(); if let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) { if stem == icon_name { self.copy_icon_file(base, &path)?; found = true; } } - } + Ok(()) + })?; + Ok(found) } fn copy_icon_file(&self, base: &Path, icon_path: &Path) -> Result<()> { verbose!("copy icon file {}", icon_path.display()); - let stripped = icon_path.strip_prefix(base)?; + let stripped = icon_path.strip_prefix(base) + .map_err(|_| format_err!("Failed to strip base path {:?} from icon path {:?}", base, icon_path))?; let target = Path::new(Self::CITADEL_ICONS).join("hicolor").join(stripped); let parent = target.parent().unwrap(); - if !parent.exists() { - fs::create_dir_all(parent)?; - } - fs::copy(icon_path, target)?; + util::create_dir(parent)?; + util::copy_file(icon_path, target)?; Ok(()) } } \ No newline at end of file diff --git a/citadel-tool/src/sync/parser.rs b/citadel-tool/src/sync/parser.rs index 8dc8333..cb5d2bd 100644 --- a/citadel-tool/src/sync/parser.rs +++ b/citadel-tool/src/sync/parser.rs @@ -1,9 +1,7 @@ -use std::io::Read; -use std::fs::File; use std::path::Path; use std::collections::HashSet; -use libcitadel::Result; +use libcitadel::{Result, util}; use crate::sync::desktop_file::{DesktopFile,Line}; lazy_static! { @@ -36,11 +34,11 @@ fn is_whitelisted_key(key: &str) -> bool { 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)), + None => bail!("Path {:?} has no filename component", path), }; match filename.to_str() { Some(s) => Ok(s), - None => Err(format_err!("Filename has invalid utf8 encoding")), + None => bail!("Filename has invalid utf8 encoding"), } } pub struct DesktopFileParser { @@ -66,14 +64,9 @@ impl DesktopFileParser { } pub fn parse_from_path>(path: P, exec_prefix: &str) -> Result { - let filename = filename_from_path(path.as_ref())?; - let f = File::open(path.as_ref())?; - DesktopFileParser::parse_from_reader(f, filename, exec_prefix) - } - - fn parse_from_reader(mut r: T, filename: &str, exec_prefix: &str) -> Result { - let mut buffer = String::new(); - r.read_to_string(&mut buffer)?; + let path = path.as_ref(); + let filename = filename_from_path(path)?; + let buffer = util::read_to_string(path)?; DesktopFileParser::parse_from_string(&buffer, filename, exec_prefix) } @@ -82,7 +75,7 @@ impl DesktopFileParser { 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)) + None => bail!("failed to parse line: '{}'", s) } } Ok(parser.desktop_file) @@ -92,7 +85,7 @@ impl DesktopFileParser { match line { Line::Comment(_) | Line::Empty => {}, Line::DesktopHeader => self.seen_header = true, - _ => return Err(format_err!("Missing Desktop Entry header")) + _ => bail!("missing Desktop Entry header") } self.desktop_file.add_line(line); Ok(()) @@ -119,13 +112,13 @@ impl DesktopFileParser { 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::DesktopHeader => bail!("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)) + bail!("desktop Action header with undecleared action: {}", action) } }, Line::GroupHeader(_) => { diff --git a/citadel-tool/src/update/kernel.rs b/citadel-tool/src/update/kernel.rs index 1c4d212..300ba3a 100644 --- a/citadel-tool/src/update/kernel.rs +++ b/citadel-tool/src/update/kernel.rs @@ -1,4 +1,3 @@ -use std::fs; use std::fmt::{self,Write}; use std::path::{Path,PathBuf}; @@ -48,7 +47,7 @@ impl KernelInstaller { pub fn install(&mut self) -> Result { let install_path = self.install_kernel_path()?; info!("Copying kernel bzImage to {}", install_path.display()); - fs::copy(&self.new_kernel.path, &install_path)?; + util::copy_file(&self.new_kernel.path, &install_path)?; self.boot_entries.rotate()?; @@ -61,9 +60,6 @@ impl KernelInstaller { e.remove()?; } - - - // 0) if boot.conf does not exist, just write it. done. // 1) if current boot.conf is not verified, just replace it. done. // 2) rotate boot.conf to boot.1.conf @@ -200,13 +196,12 @@ impl BootEntries { if !base_path.exists() { return Ok(()) } - for dirent in fs::read_dir(base_path)? { - let dirent = dirent?; - if let Some(fname) = dirent.file_name().to_str() { + util::read_directory(base_path, |dent| { + if let Some(fname) = dent.file_name().to_str() { self.load_filename(fname); } - } - Ok(()) + Ok(()) + }) } fn load_filename(&mut self, fname: &str) { @@ -250,7 +245,7 @@ impl BootEntries { fn _rotate(&mut self) -> Result<()> { for entry in self.0.iter_mut().rev() { if !entry.rotate()? { - bail!("Failed to rotate boot entry {} because next index already exists", entry.path().display()); + bail!("failed to rotate boot entry {} because next index already exists", entry.path().display()); } } Ok(()) @@ -333,8 +328,7 @@ impl BootEntry { writeln!(&mut buffer, "title {}", self.title)?; writeln!(&mut buffer, "linux /{}", kernel)?; writeln!(&mut buffer, "options {}", self.options)?; - fs::write(self.path(), buffer)?; - Ok(()) + util::write_file(self.path(), buffer) } fn is_good(&self) -> bool { @@ -351,7 +345,7 @@ impl BootEntry { fn load(&mut self) -> Result<()> { let path = self.path(); - for line in fs::read_to_string(&path)?.lines() { + for line in util::read_to_string(&path)?.lines() { if line.starts_with("title ") { self.title = line.trim_start_matches("title ").to_owned(); } else if line.starts_with("linux /") { @@ -408,7 +402,7 @@ impl BootEntry { return Ok(false); } verbose!("Rotating boot entry {} to {}", old_path.display(), new_path.display()); - fs::rename(old_path, new_path)?; + util::rename(old_path, new_path)?; Ok(true) } @@ -418,7 +412,7 @@ impl BootEntry { bzimage.remove_file()?; self.bzimage = None; } - fs::remove_file(self.path())?; + util::remove_file(self.path())?; Ok(()) } } @@ -447,8 +441,7 @@ impl KernelBzImage { } fn remove_file(&self) -> Result<()> { - fs::remove_file(&self.path)?; - Ok(()) + util::remove_file(&self.path) } } diff --git a/citadel-tool/src/update/mod.rs b/citadel-tool/src/update/mod.rs index 2ab881b..fb0333f 100644 --- a/citadel-tool/src/update/mod.rs +++ b/citadel-tool/src/update/mod.rs @@ -1,7 +1,6 @@ use std::path::{Path, PathBuf}; -use std::fs; -use libcitadel::{Result, Partition, ResourceImage, ImageHeader, LogLevel, Logger}; +use libcitadel::{Result, Partition, ResourceImage, ImageHeader, LogLevel, Logger, util}; use crate::update::kernel::{KernelInstaller, KernelVersion}; use std::collections::HashSet; use std::fs::DirEntry; @@ -57,16 +56,15 @@ fn detect_duplicates(image: &ResourceImage) -> Result<()> { return Ok(()) } - for dirent in fs::read_dir(resource_dir)? { - let dirent = dirent?; - match ResourceImage::from_path(dirent.path()) { + util::read_directory(&resource_dir, |dent| { + match ResourceImage::from_path(dent.path()) { Ok(img) => if img.metainfo().shasum() == shasum { bail!("A duplicate image file with the same shasum already exists at {}", img.path().display()); }, Err(err) => warn!("{}", err), } - } - Ok(()) + Ok(()) + }) } fn install_image(path: &Path, flags: u32) -> Result<()> { @@ -118,12 +116,10 @@ fn remove_old_extra_images(image: &ResourceImage) -> Result<()> { let new_meta = image.header().metainfo(); let shasum = new_meta.shasum(); let target_dir = target_directory(image)?; - for dirent in fs::read_dir(target_dir)? { - let dirent = dirent?; - let path = dirent.path(); - maybe_remove_old_extra_image(&path, shasum)?; - } - Ok(()) + util::read_directory(&target_dir, |dent| { + let path = dent.path(); + maybe_remove_old_extra_image(&path, shasum) + }) } fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> { @@ -137,13 +133,11 @@ fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> { } if meta.shasum() != shasum { info!("Removing old extra resource image {}", path.display()); - fs::remove_file(&path)?; + util::remove_file(&path)?; } Ok(()) } - - fn install_kernel_image(image: &mut ResourceImage) -> Result<()> { if !Path::new("/boot/loader/loader.conf").exists() { bail!("failed to automount /boot partition. Please manually mount correct partition."); @@ -153,7 +147,7 @@ fn install_kernel_image(image: &mut ResourceImage) -> Result<()> { let version = metainfo.version(); let kernel_version = match metainfo.kernel_version() { Some(kv) => kv, - None => bail!("Kernel image does not have kernel version field"), + None => bail!("kernel image does not have kernel version field"), }; info!("kernel version is {}", kernel_version); install_kernel_file(image, &kernel_version)?; @@ -164,16 +158,16 @@ fn install_kernel_image(image: &mut ResourceImage) -> Result<()> { let all_versions = all_boot_kernel_versions()?; let image_dir = target_directory(image)?; let mut remove_paths = Vec::new(); - for dirent in fs::read_dir(image_dir)? { - let dirent = dirent?; - let path = dirent.path(); + util::read_directory(&image_dir, |dent| { + let path = dent.path(); if is_unused_kernel_image(&path, &all_versions)? { remove_paths.push(path); } - } + Ok(()) + })?; for p in remove_paths { - fs::remove_file(p)?; + util::remove_file(p)?; } Ok(()) } @@ -216,14 +210,15 @@ fn install_kernel_file(image: &mut ResourceImage, kernel_version: &str) -> Resul fn all_boot_kernel_versions() -> Result> { let mut result = HashSet::new(); - for dirent in fs::read_dir("/boot")? { - let dirent = dirent?; - if is_kernel_dirent(&dirent) { - if let Some(kv) = KernelVersion::parse_from_path(&dirent.path()) { + util::read_directory("/boot", |dent| { + if is_kernel_dirent(&dent) { + if let Some(kv) = KernelVersion::parse_from_path(&dent.path()) { result.insert(kv.version()); } } - } + Ok(()) + })?; + Ok(result) } @@ -242,7 +237,7 @@ fn install_image_file(image: &ResourceImage, filename: &str) -> Result<()> { rotate(&image_dest)?; } info!("installing image file by moving from {} to {}", image.path().display(), image_dest.display()); - fs::rename(image.path(), image_dest)?; + util::rename(image.path(), image_dest)?; Ok(()) } @@ -259,16 +254,14 @@ fn rotate(path: &Path) -> Result<()> { } let filename = path.file_name().unwrap(); let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy())); - if dot_zero.exists() { - fs::remove_file(&dot_zero)?; - } - fs::rename(path, &dot_zero)?; + util::remove_file(&dot_zero)?; + util::rename(path, &dot_zero)?; Ok(()) } fn validate_channel_name(channel: &str) -> Result<()> { if !channel.chars().all(|c| c.is_ascii_lowercase()) { - bail!("Image has invalid channel name '{}'", channel); + bail!("image has invalid channel name '{}'", channel); } Ok(()) } @@ -334,5 +327,5 @@ fn choose_install_partition(verbose: bool) -> Result { return Ok(p.clone()) } } - Err(format_err!("No suitable install partition found")) + bail!("no suitable install partition found") } diff --git a/libcitadel/Cargo.toml b/libcitadel/Cargo.toml index a8886b9..f32719c 100644 --- a/libcitadel/Cargo.toml +++ b/libcitadel/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" [dependencies] libc = "0.2" nix = "0.17.0" -failure = "0.1.3" toml = "0.5" serde = "1.0" serde_derive = "1.0" diff --git a/libcitadel/src/blockdev.rs b/libcitadel/src/blockdev.rs index 300cd84..b3b01f2 100644 --- a/libcitadel/src/blockdev.rs +++ b/libcitadel/src/blockdev.rs @@ -1,9 +1,9 @@ -use std::path::Path; -use std::fs::File; +use std::fs::{File,OpenOptions}; use std::io::{Read,Write,Seek,SeekFrom}; use std::os::unix::io::AsRawFd; -use std::fs::OpenOptions; use std::os::unix::fs::OpenOptionsExt; +use std::path::Path; + use libc; use crate::Result; @@ -112,7 +112,8 @@ impl BlockDev { oo.write(true); } let file = oo.open(path) - .map_err(|e| format_err!("Failed to open block device {}: {}", path.display(), e))?; + .map_err(context!("failed to open block device {:?}", path))?; + Ok(BlockDev{file}) } @@ -121,7 +122,7 @@ impl BlockDev { 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))?; + .map_err(context!("error calling getsize ioctl on block device"))?; } Ok(sz) } @@ -138,16 +139,18 @@ impl BlockDev { 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); + bail!("block device I/O attempted with incorrectly aligned buffer address: {:#x}", addr); } if buffer.len() % SECTOR_SIZE != 0 { - bail!("buffer length {} is not a multiple of sector size", buffer.len()); + 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()); + bail!("block device sector i/o ({},{}) is past end of device", offset, buffer.len()); } - self.file.seek(SeekFrom::Start((offset * SECTOR_SIZE) as u64))?; + self.file.seek(SeekFrom::Start((offset * SECTOR_SIZE) as u64)) + .map_err(context!("I/O error accessing block device"))?; + Ok(()) } @@ -155,16 +158,15 @@ impl BlockDev { /// The buffer must be a multiple of sector size (512 bytes). pub fn read_sectors(&mut self, offset: usize, buffer: &mut [u8]) -> Result<()> { self.setup_io(offset, buffer)?; - self.file.read_exact(buffer)?; - Ok(()) + self.file.read_exact(buffer) + .map_err(context!("I/O error reading from block device")) } /// Write sectors from `buffer` to device starting at sector `offset`. /// The buffer must be a multiple of sector size (512 bytes). pub fn write_sectors(&mut self, offset: usize, buffer: &[u8]) -> Result<()> { self.setup_io(offset, buffer)?; - self.file.write_all(buffer)?; - Ok(()) + self.file.write_all(buffer) + .map_err(context!("I/O error writing to block device")) } - } diff --git a/libcitadel/src/cmdline.rs b/libcitadel/src/cmdline.rs index 47710ec..b0f7070 100644 --- a/libcitadel/src/cmdline.rs +++ b/libcitadel/src/cmdline.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; -use std::fs; -use crate::Result; +use crate::{Result, util}; lazy_static! { static ref CMDLINE: CommandLine = match CommandLine::load() { @@ -109,7 +108,7 @@ impl CommandLine { } fn load() -> Result { - let s = fs::read_to_string("/proc/cmdline")?; + let s = util::read_to_string("/proc/cmdline")?; let varmap = CommandLineParser::new(s).parse(); Ok(CommandLine{varmap}) } @@ -289,7 +288,6 @@ fn foo() { let cline = CommandLine::load().unwrap(); println!("hello"); println!("cline: {:?}", cline.varmap); - } diff --git a/libcitadel/src/config.rs b/libcitadel/src/config.rs index 0e08fab..621a305 100644 --- a/libcitadel/src/config.rs +++ b/libcitadel/src/config.rs @@ -1,8 +1,7 @@ use std::path::Path; use std::collections::HashMap; -use std::fs; -use crate::Result; +use crate::{Result, util}; lazy_static! { static ref OS_RELEASE: Option = match OsRelease::load() { @@ -26,12 +25,13 @@ impl OsRelease { return OsRelease::load_file(path); } } - Err(format_err!("File not found")) + bail!("failed to find os-release file") } fn load_file(path: &Path) -> Result { let mut vars = HashMap::new(); - for line in fs::read_to_string(path)?.lines() { + let content = util::read_to_string(path)?; + for line in content.lines() { let (k,v) = OsRelease::parse_line(line)?; vars.insert(k,v); } @@ -52,7 +52,7 @@ impl OsRelease { for q in &["'", "\""] { if s.starts_with(q) { if !s.ends_with(q) || s.len() < 2 { - bail!("Unmatched quote character"); + bail!("unmatched quote character in line: {}", s); } return Ok(s[1..s.len() - 1].to_string()); } diff --git a/libcitadel/src/error.rs b/libcitadel/src/error.rs new file mode 100644 index 0000000..7568d0a --- /dev/null +++ b/libcitadel/src/error.rs @@ -0,0 +1,80 @@ +use std::{result, fmt, error}; +use std::fmt::Display; + +pub type Result = result::Result; + +/// Return an `Error` from a function. +/// +/// `bail!("something went wrong")` is equivalent to: +/// +/// ```rust.ignore +/// return Err(Errror::message(format!("something went wrong", ))); +/// ``` +/// +#[macro_export] +macro_rules! bail { + ($e:expr) => { + return Err($crate::error::Error::message($e)); + }; + ($fmt:expr, $($arg:tt)*) => { + return Err($crate::error::Error::message(format!($fmt, $($arg)*))); + }; +} + +/// Create an `Error::Message` instance by formatting a string +#[macro_export] +macro_rules! format_err { + ($($arg:tt)*) => { + $crate::error::Error::message(format!($($arg)*)) + } +} + +/// for use in map_err() +/// +/// ``` +/// map_err(context!("something went wrong with {:?}", path)) +/// ``` +/// +/// is the same as +/// +/// ``` +/// map_err(|e| format_err!("something went wrong with {:?}: {}", path, e)) +/// ``` +/// +#[macro_export] +macro_rules! context { + ($($arg:tt)*) => { |e| + $crate::Error::with_error(format!($($arg)*), e) + } +} + +#[derive(Debug)] +pub enum Error { + Message(String), +} + +impl Error { + pub fn message>(msg: S) -> Self { + Error::Message(msg.into()) + } + + pub fn with_error(msg: S, err: D) -> Self + where + S: Into, + D: Display, + { + let msg = msg.into(); + Self::message(format!("{}: {}", msg, err)) + } + +} + +impl error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Message(msg) => msg.fmt(f), + } + } +} diff --git a/libcitadel/src/exec.rs b/libcitadel/src/exec.rs index 13a139e..55313b6 100644 --- a/libcitadel/src/exec.rs +++ b/libcitadel/src/exec.rs @@ -50,10 +50,11 @@ impl Exec { let args: Vec<&str> = args.as_ref().split_whitespace().collect(); let result = self.cmd .args(args) - .output()?; + .output() + .map_err(context!("failed to execute command {}", self.cmd_name))?; for line in BufReader::new(result.stderr.as_slice()).lines() { - verbose!(" {}", line?); + verbose!(" {}", line.unwrap()); } self.check_cmd_status(result.status) } @@ -64,7 +65,8 @@ impl Exec { let args: Vec<&str> = args.as_ref().split_whitespace().collect(); let status = self.cmd .args(args) - .status()?; + .status() + .map_err(context!("failed to execute command {}", self.cmd_name))?; Ok(status.success()) } @@ -72,7 +74,9 @@ impl Exec { pub fn output(&mut self, args: impl AsRef) -> Result { self.ensure_command_exists()?; self.add_args(args.as_ref()); - let result = self.cmd.stderr(Stdio::inherit()).output()?; + let result = self.cmd.stderr(Stdio::inherit()) + .output() + .map_err(context!("failed to execute command {}", self.cmd_name))?; self.check_cmd_status(result.status)?; Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned()) } @@ -90,11 +94,14 @@ impl Exec { .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) - .spawn()?; + .spawn() + .map_err(context!("failed to execute command {}", self.cmd_name))?; let stdin = child.stdin.as_mut().unwrap(); - io::copy(&mut r, stdin)?; - let output = child.wait_with_output()?; + io::copy(&mut r, stdin) + .map_err(context!("error writing to stdin of command {}", self.cmd_name))?; + let output = child.wait_with_output() + .map_err(context!("error waiting for child command {} to exit", self.cmd_name))?; Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned()) } @@ -121,18 +128,18 @@ impl Exec { } else if path.exists() { return Ok(()) } - Err(format_err!("Cannot execute '{}': command does not exist", self.cmd_name)) + bail!("cannot execute '{}': command does not exist", self.cmd_name) } fn search_path(filename: &str) -> Result { - let path_var = env::var("PATH")?; + let path_var = env::var("PATH").unwrap_or(String::new()); for mut path in env::split_paths(&path_var) { path.push(filename); if path.exists() { return Ok(path); } } - Err(format_err!("Could not find {} in $PATH", filename)) + bail!("could not find {} in $PATH", filename) } } @@ -144,14 +151,16 @@ pub enum FileRange { fn ranged_reader>(path: P, range: FileRange) -> Result> { - let mut f = File::open(path.as_ref())?; + let mut f = File::open(path.as_ref()) + .map_err(context!("failed to open input file {:?}", path.as_ref()))?; let offset = match range { FileRange::All => 0, FileRange::Offset(n) => n, FileRange::Range {offset, ..} => offset, }; if offset > 0 { - f.seek(SeekFrom::Start(offset as u64))?; + f.seek(SeekFrom::Start(offset as u64)) + .map_err(context!("failed to seek to offset {} of input file {:?}", offset, path.as_ref()))?; } let r = BufReader::new(f); if let FileRange::Range {len, ..} = range { diff --git a/libcitadel/src/header.rs b/libcitadel/src/header.rs index 4c01c2c..fc31e3d 100644 --- a/libcitadel/src/header.rs +++ b/libcitadel/src/header.rs @@ -5,10 +5,11 @@ use std::path::Path; use toml; use crate::blockdev::AlignedBuffer; -use crate::{BlockDev,Result,public_key_for_channel,PublicKey}; +use crate::{Result, BlockDev, public_key_for_channel, PublicKey}; use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::atomic::{Ordering,AtomicIsize}; use std::os::unix::fs::MetadataExt; +use std::io; /// Expected magic value in header const MAGIC: &[u8] = b"SGOS"; @@ -159,6 +160,9 @@ impl ImageHeader { /// Reload header if file has changed on disk pub fn reload_if_stale>(&self, path: P) -> Result { let path = path.as_ref(); + if !path.exists() { + bail!("cannot reload header because image file {:?} is missing", path); + } let reload = self.is_stale(path)?; if reload { self.reload_file(path)?; @@ -185,9 +189,11 @@ impl ImageHeader { let path = path.as_ref(); let (size,ts) = Self::file_metadata(path)?; if size < Self::HEADER_SIZE { - bail!("Cannot load image header because {} has a size of {}", path.display(), size); + bail!("cannot load image header from {:?} because file is too short ({})", path, size); } - let mut f = File::open(path)?; + let mut f = File::open(path) + .map_err(context!("failed to load image header from {:?}", path))?; + let mut header = Self::from_reader(&mut f)?; *header.timestamp.get_mut() = ts; Ok(header) @@ -195,13 +201,15 @@ impl ImageHeader { // returns tuple of (size,mtime) fn file_metadata(path: &Path) -> Result<(usize, isize)> { - let metadata = path.metadata()?; + let metadata = path.metadata() + .map_err(context!("failed to load image header from {:?}", path))?; Ok((metadata.len() as usize, metadata.mtime() as isize)) } pub fn from_reader(r: &mut R) -> Result { let mut v = vec![0u8; Self::HEADER_SIZE]; - r.read_exact(&mut v)?; + r.read_exact(&mut v) + .map_err(context!("error reading header bytes"))?; Self::from_slice(&v) } @@ -218,12 +226,9 @@ impl ImageHeader { pub fn from_partition>(path: P) -> Result { let mut dev = BlockDev::open_ro(path.as_ref())?; let nsectors = dev.nsectors()?; - ensure!( - nsectors >= 8, - "{} is a block device bit it's too short ({} sectors)", - path.as_ref().display(), - nsectors - ); + if nsectors < 8 { + bail!("cannot load/store header from block device {:?} because it's too short ({} sectors)", path.as_ref(), nsectors); + } let mut buffer = AlignedBuffer::new(Self::HEADER_SIZE); dev.read_sectors(nsectors - 8, buffer.as_mut())?; Self::from_slice(buffer.as_ref()) @@ -232,12 +237,9 @@ impl ImageHeader { pub fn write_partition>(&self, path: P) -> Result<()> { let mut dev = BlockDev::open_rw(path.as_ref())?; let nsectors = dev.nsectors()?; - ensure!( - nsectors >= 8, - "{} is a block device bit it's too short ({} sectors)", - path.as_ref().display(), - nsectors - ); + if nsectors < 8 { + bail!("cannot load/store header from block device {:?} because it's too short ({} sectors)", path.as_ref(), nsectors); + } let lock = self.bytes(); let buffer = AlignedBuffer::from_slice(&lock.0); dev.write_sectors(nsectors - 8, buffer.as_ref())?; @@ -273,7 +275,7 @@ impl ImageHeader { let mut lock = self.metainfo.lock().unwrap(); let mb = self.metainfo_bytes(); let metainfo = MetaInfo::parse_bytes(&mb) - .ok_or_else(|| format_err!("ImageHeader has invalid metainfo"))?; + .ok_or(format_err!("image header has invalid metainfo"))?; *lock = Some(Arc::new(metainfo)); Ok(()) } @@ -335,13 +337,13 @@ impl ImageHeader { pub fn update_metainfo>(&self, metainfo_bytes: &[u8], signature: &[u8], path: P) -> Result<()> { self.set_metainfo_bytes(metainfo_bytes)?; - self.set_signature(signature)?; + self.set_signature(signature); self.write_header_to(path) } pub fn set_metainfo_bytes(&self, bytes: &[u8]) -> Result<()> { let metainfo = MetaInfo::parse_bytes(bytes) - .ok_or_else(|| format_err!("Could not parse metainfo bytes as valid metainfo document"))?; + .ok_or(format_err!("cannot parse header metainfo bytes as a valid metainfo document"))?; let mut lock = self.metainfo.lock().unwrap(); self.with_bytes_mut(|bs| { @@ -369,18 +371,15 @@ impl ImageHeader { self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH) } - pub fn set_signature(&self, signature: &[u8]) -> Result<()> { - if signature.len() != SIGNATURE_LENGTH { - bail!("Signature has invalid length: {}", signature.len()); - } + pub fn set_signature(&self, signature: &[u8]) { + assert_eq!(signature.len(), SIGNATURE_LENGTH, "Signature has invalid length"); let mlen = self.metainfo_len(); self.write_bytes(8 + mlen, signature); - Ok(()) } - pub fn clear_signature(&self) -> Result<()> { + pub fn clear_signature(&self) { let zeros = vec![0u8; SIGNATURE_LENGTH]; - self.set_signature(&zeros) + self.set_signature(&zeros); } pub fn public_key(&self) -> Result> { @@ -391,13 +390,16 @@ impl ImageHeader { pubkey.verify(&self.metainfo_bytes(), &self.signature()) } - pub fn write_header(&self, mut writer: W) -> Result<()> { - self.with_bytes(|bs| writer.write_all(&bs.0))?; - Ok(()) + pub fn write_header(&self, mut writer: W) -> io::Result<()> { + self.with_bytes(|bs| writer.write_all(&bs.0)) } pub fn write_header_to>(&self, path: P) -> Result<()> { - self.write_header(OpenOptions::new().write(true).open(path.as_ref())?) + let path = path.as_ref(); + let w = OpenOptions::new().write(true).open(path) + .map_err(context!("failed to open image file {:?} to write header", path))?; + self.write_header(w) + .map_err(context!("error writing header to image file {:?}", path)) } fn read_u8(&self, idx: usize) -> u8 { diff --git a/libcitadel/src/keyring.rs b/libcitadel/src/keyring.rs index 11af1df..6dd681e 100644 --- a/libcitadel/src/keyring.rs +++ b/libcitadel/src/keyring.rs @@ -21,7 +21,7 @@ use sodiumoxide::crypto::{ }, }; -use crate::{Result,Error,KeyPair}; +use crate::{Result, Error, KeyPair}; #[derive(Serialize,Deserialize,Debug)] pub struct KeyRing { @@ -38,9 +38,10 @@ impl KeyRing { pub fn load>(path: P, passphrase: &str) -> Result { let mut sbox = SecretBox::new(path.as_ref()); - sbox.read().map_err(|e| format_err!("Error reading keyring file: {}", e))?; + sbox.read().map_err(context!("error reading keyring file"))?; let mut bytes = sbox.open(passphrase)?; - let keyring = toml::from_slice::(&bytes)?; + let keyring = toml::from_slice::(&bytes) + .map_err(context!("failed to parse keyring file {:?}", path.as_ref()))?; bytes.iter_mut().for_each(|b| *b = 0); Ok(keyring) } @@ -73,13 +74,13 @@ impl KeyRing { info!("Found {} key with request_key", name); return Ok(key); } - Err(format_err!("kernel key '{}' not found", name)) + bail!("kernel key '{}' not found", name) } pub fn add_keys_to_kernel(&self) -> Result<()> { for (k,v) in self.keypairs.iter() { info!("Adding {} to kernel keystore", k.as_str()); - let bytes = hex::decode(v)?; + let bytes = hex::decode(v).map_err(|_| format_err!("failed to hex decode ({})", v))?; let key = KernelKey::add_key("user", k.as_str(), &bytes, KEY_SPEC_USER_KEYRING)?; key.set_perm(0x3f03_0000)?; } @@ -96,12 +97,18 @@ impl KeyRing { let salt = pwhash::gen_salt(); let nonce = secretbox::gen_nonce(); let key = SecretBox::passphrase_to_key(passphrase, &salt)?; - let bytes = toml::to_vec(self)?; + let bytes = toml::to_vec(self) + .map_err(context!("failed to serialize keyring"))?; let ciphertext = secretbox::seal(&bytes, &nonce, &key); - let mut file = fs::File::create(path.as_ref())?; - file.write_all(&salt.0)?; - file.write_all(&nonce.0)?; + Self::write_keyring(path.as_ref(), &salt.0, &nonce.0, &ciphertext) + .map_err(context!("error writing keyring file {:?}", path.as_ref())) + } + + fn write_keyring(path: &Path, salt: &[u8], nonce: &[u8], ciphertext: &[u8]) -> io::Result<()> { + let mut file = fs::File::create(path)?; + file.write_all(&salt)?; + file.write_all(&nonce)?; file.write_all(&ciphertext)?; Ok(()) } @@ -143,6 +150,12 @@ impl SecretBox { if !self.data.is_empty() { self.data.clear(); } + + self.read_keyring_file() + .map_err(context!("error reading keyring file {:?}", self.path)) + } + + fn read_keyring_file(&mut self) -> io::Result<()> { let mut file = fs::File::open(&self.path)?; file.read_exact(&mut self.salt.0)?; file.read_exact(&mut self.nonce.0)?; @@ -153,14 +166,14 @@ impl SecretBox { fn open(&self, passphrase: &str) -> Result> { let key = Self::passphrase_to_key(passphrase, &self.salt)?; let result = secretbox::open(&self.data, &self.nonce, &key) - .map_err(|_| format_err!("Failed to decrypt {}", self.path.display()))?; + .map_err(|_| format_err!("failed to decrypt {:?}", self.path))?; Ok(result) } fn passphrase_to_key(passphrase: &str, salt: &Salt) -> Result { let mut keybuf = [0; secretbox::KEYBYTES]; pwhash::derive_key(&mut keybuf, passphrase.as_bytes(), salt, pwhash::OPSLIMIT_INTERACTIVE, pwhash::MEMLIMIT_INTERACTIVE) - .map_err(|_| format_err!("Failed to derive key"))?; + .map_err(|_| format_err!("failed to derive key"))?; Ok(secretbox::Key(keybuf)) } @@ -212,7 +225,7 @@ impl KernelKey { let mut size = 0; loop { size = match self.buffer_request(KEYCTL_DESCRIBE, size) { - BufferResult::Err(err) => return Err(format_err!("Error calling KEYCTL_DESCRIBE on key: {}", err)), + BufferResult::Err(err) => bail!("error calling KEYCTL_DESCRIBE on key: {}", err), BufferResult::Ok(vec) => return Ok(String::from_utf8(vec).expect("KEYCTL_DESCRIBE returned bad utf8")), BufferResult::TooSmall(sz) => sz, } @@ -231,7 +244,7 @@ impl KernelKey { let mut size = 0; loop { size = match self.buffer_request(KEYCTL_READ, size) { - BufferResult::Err(err) => return Err(format_err!("Error reading key: {}", err)), + BufferResult::Err(err) => bail!("Error reading key: {}", err), BufferResult::Ok(buffer) => return Ok(buffer), BufferResult::TooSmall(sz) => sz + 1, } @@ -242,14 +255,14 @@ impl KernelKey { if size == 0 { return match keyctl1(command, self.id()) { Err(err) => BufferResult::Err(err), - Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctl returned bad size")), + Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctl returned bad size").into()), Ok(n) => BufferResult::TooSmall(n as usize), }; } let mut buffer = vec![0u8; size]; match keyctl3(command, self.id(), buffer.as_ptr() as u64, buffer.len() as u64) { Err(err) => BufferResult::Err(err), - Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctrl returned bad size {}", n)), + Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctrl returned bad size {}", n).into()), Ok(sz) if size >= (sz as usize) => { let sz = sz as usize; if size > sz { @@ -294,7 +307,7 @@ fn sys_keyctl(command: c_int, arg2: c_ulong, arg3: c_ulong, arg4: c_ulong, arg5: unsafe { let r = libc::syscall(libc::SYS_keyctl, command, arg2, arg3, arg4, arg5); if r == -1 { - Err(io::Error::last_os_error().into()) + bail!("error calling sys_keyctl(): {}", io::Error::last_os_error()); } else { Ok(r) } @@ -305,7 +318,7 @@ fn _request_key(key_type: *const c_char, description: *const c_char) -> Result Result { - let bytes = hex::decode(hex)?; + let bytes = hex::decode(hex) + .map_err(context!("error hex decoding public key"))?; if bytes.len() != PUBLICKEYBYTES { - bail!("Hex encoded public key has invalid length: {}", bytes.len()); + bail!("hex encoded public key has invalid length: {}", bytes.len()); } let pubkey = sign::PublicKey::from_slice(&bytes) .expect("PublicKey::from_slice() failed"); @@ -49,13 +50,14 @@ impl KeyPair { } pub fn from_hex(hex: &str) -> Result { - let bytes = hex::decode(hex)?; + let bytes = hex::decode(hex) + .map_err(context!("Error hex decoding key pair"))?; KeyPair::from_bytes(&bytes) } pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() != SEEDBYTES { - bail!("Hex encoded keypair has incorrect length"); + bail!("hex encoded keypair has incorrect length"); } let seed = sign::Seed::from_slice(&bytes).expect("Seed::from_slice() failed"); Ok(KeyPair(seed)) diff --git a/libcitadel/src/lib.rs b/libcitadel/src/lib.rs index a55c125..57ae60c 100644 --- a/libcitadel/src/lib.rs +++ b/libcitadel/src/lib.rs @@ -1,22 +1,8 @@ -#[macro_use] extern crate failure; #[macro_use] extern crate nix; #[macro_use] extern crate serde_derive; #[macro_use] extern crate lazy_static; -use std::result; -use failure::Error; - -pub fn format_error(err: &Error) -> String { - let mut output = err.to_string(); - let mut prev = err.as_fail(); - while let Some(next) = prev.cause() { - output.push_str(": "); - output.push_str(&next.to_string()); - prev = next; - } - output -} - +#[macro_use] pub mod error; #[macro_use] mod log; #[macro_use] mod exec; mod blockdev; @@ -87,7 +73,7 @@ pub fn public_key_for_channel(channel: &str) -> Result> { Ok(None) } -pub type Result = result::Result; +pub use error::{Result,Error}; pub const BLOCK_SIZE: usize = 4096; diff --git a/libcitadel/src/log.rs b/libcitadel/src/log.rs index 9bba84b..4d72819 100644 --- a/libcitadel/src/log.rs +++ b/libcitadel/src/log.rs @@ -109,8 +109,10 @@ impl LogOutput for DefaultLogOutput { let stdout = io::stdout(); let mut lock = stdout.lock(); - lock.write_all(line.as_bytes())?; - lock.flush()?; + lock.write_all(line.as_bytes()) + .map_err(context!("error writing log line to stdout"))?; + lock.flush() + .map_err(context!("error flushing stdout"))?; Ok(()) } } diff --git a/libcitadel/src/partition.rs b/libcitadel/src/partition.rs index df62715..7dc82dc 100644 --- a/libcitadel/src/partition.rs +++ b/libcitadel/src/partition.rs @@ -1,8 +1,10 @@ -use std::path::{Path,PathBuf}; use std::fs; -use crate::{Result,ImageHeader,MetaInfo,Mounts,PublicKey,public_key_for_channel}; +use std::path::{Path,PathBuf}; use std::sync::Arc; +use crate::{Result, ImageHeader, MetaInfo, Mounts, PublicKey, public_key_for_channel,util}; + + #[derive(Clone)] pub struct Partition { path: PathBuf, @@ -121,17 +123,21 @@ impl Partition { pub fn write_status(&mut self, status: u8) -> Result<()> { self.header().set_status(status); - self.header().write_partition(&self.path) + self.write_header() } pub fn set_flag_and_write(&mut self, flag: u8) -> Result<()> { self.header().set_flag(flag); - self.header().write_partition(&self.path) + self.write_header() } pub fn clear_flag_and_write(&mut self, flag: u8) -> Result<()> { self.header().clear_flag(flag); - self.header().write_partition(&self.path) + self.write_header() + } + + fn write_header(&self) -> Result<()> { + self.header().write_partition(&self.path).into() } /// Called at boot to perform various checks and possibly @@ -180,29 +186,36 @@ fn is_in_use(path: &Path) -> Result { // fn count_block_holders(path: &Path) -> Result { if !path.exists() { - bail!("Path to rootfs device does not exist: {}", path.display()); + bail!("path {:?} to rootfs device does not exist", path); } - let resolved = fs::canonicalize(path)?; + let resolved = fs::canonicalize(path) + .map_err(context!("failed to canonicalize path {:?}", path))?; let fname = match resolved.file_name() { Some(s) => s, - None => bail!("path does not have filename?"), + None => bail!("path {:?} does not have a filename", resolved), }; let holders_dir = Path::new("/sys/block") .join(fname) .join("holders"); - let count = fs::read_dir(holders_dir)?.count(); + + let count = fs::read_dir(&holders_dir) + .map_err(context!("cannot read directory {:?}", holders_dir))? + .count(); Ok(count) } fn rootfs_partition_paths() -> Result> { let mut rootfs_paths = Vec::new(); - for dent in fs::read_dir("/dev/mapper")? { - let path = dent?.path(); + + util::read_directory("/dev/mapper", |dent| { + let path = dent.path(); if is_path_rootfs(&path) { rootfs_paths.push(path); } - } + Ok(()) + + })?; Ok(rootfs_paths) } diff --git a/libcitadel/src/realm/config.rs b/libcitadel/src/realm/config.rs index caf7de5..91e21d5 100644 --- a/libcitadel/src/realm/config.rs +++ b/libcitadel/src/realm/config.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use std::fs; use std::os::unix::fs::MetadataExt; use toml; -use crate::{Result, Realms}; +use crate::{Result, Realms, util}; lazy_static! { pub static ref GLOBAL_CONFIG: RealmConfig = RealmConfig::load_global_config(); @@ -147,16 +147,14 @@ impl RealmConfig { None } - pub fn write_config>(&self, path: P) -> Result<()> { - let serialized = toml::to_string(self)?; - fs::write(path.as_ref(), serialized)?; - Ok(()) + pub fn write_to>(&self, path: P) -> Result<()> { + let serialized = toml::to_string(self) + .map_err(context!("failed to serialize realm config"))?; + util::write_file(path, serialized) } pub fn write(&self) -> Result<()> { - let serialized = toml::to_string(self)?; - fs::write(&self.path, serialized)?; - Ok(()) + self.write_to(&self.path) } fn read_mtime(&self) -> i64 { @@ -171,8 +169,9 @@ impl RealmConfig { let path = self.path.clone(); if self.path.exists() { - let s = fs::read_to_string(&self.path)?; - *self = toml::from_str(&s)?; + let s = util::read_to_string(&self.path)?; + *self = toml::from_str(&s) + .map_err(context!("Failed to parse realm config"))?; } else { *self = Self::empty(); } diff --git a/libcitadel/src/realm/create.rs b/libcitadel/src/realm/create.rs index 6e49be7..f74f2f0 100644 --- a/libcitadel/src/realm/create.rs +++ b/libcitadel/src/realm/create.rs @@ -49,26 +49,22 @@ impl RealmCreateDestroy { fn create_realm_directory(&self) -> Result<()> { self.create_home()?; - self.move_from_temp()?; - Ok(()) + self.move_from_temp() } fn create_home(&self) -> Result<()> { let home = self.temp_basepath().join("home"); - fs::create_dir_all(&home) - .map_err(|e| format_err!("failed to create directory {}: {}", home.display(), e))?; - util::chown(&home, 1000, 1000) - .map_err(|e| format_err!("failed to change ownership of {} to 1000:1000: {}", home.display(), e))?; + util::create_dir(&home)?; + util::chown(&home, 1000, 1000)?; let skel = Path::new(Realms::BASE_PATH).join("skel"); if skel.exists() { info!("Populating realm home directory with files from {}", skel.display()); util::copy_tree(&skel, &home) - .map_err(|e| format_err!("failed to copy tree of files from {} to {}: {}", skel.display(), home.display(), e))?; + .map_err(context!("failed to copy tree of files from {:?} to {:?}", skel, home))?; } - Ok(()) } @@ -78,8 +74,7 @@ impl RealmCreateDestroy { if to.exists() { bail!("Cannot move temporary directory {} to {} because the target already exists", from.display(), to.display()); } - fs::rename(from, to)?; - Ok(()) + util::rename(&from, &to) } fn move_to_temp(&self) -> Result<()> { @@ -89,14 +84,9 @@ impl RealmCreateDestroy { bail!("Cannot move realm directory {} to {} because the target already exists", from.display(), to.display()); } - if !Self::tmpdir().exists() { - fs::create_dir_all(Self::tmpdir())?; - } - - fs::rename(from, to)?; - - Ok(()) - + let tmpdir = Self::tmpdir(); + util::create_dir(&tmpdir)?; + util::rename(&from, &to) } pub fn delete_realm(&self, save_home: bool) -> Result<()> { @@ -106,22 +96,19 @@ impl RealmCreateDestroy { self.save_home_for_delete()?; } - info!("removing realm directory {}", self.temp_basepath().display()); - fs::remove_dir_all(self.temp_basepath())?; - Ok(()) - + let realmdir = self.temp_basepath(); + info!("removing realm directory {:?}", realmdir); + fs::remove_dir_all(&realmdir) + .map_err(context!("error removing realm directory {:?}", realmdir)) } fn save_home_for_delete(&self) -> Result<()> { - if !Path::new("/realms/removed").exists() { - fs::create_dir("/realms/removed")?; - } + util::create_dir("/realms/removed")?; let target = self.home_save_directory(); let home = self.temp_basepath().join("home"); - fs::rename(&home, &target) - .map_err(|e| format_err!("unable to move realm home directory to {}: {}", target.display(), e))?; + util::rename(&home, &target)?; info!("home directory been moved to {}, delete it at your leisure", target.display()); Ok(()) } diff --git a/libcitadel/src/realm/events.rs b/libcitadel/src/realm/events.rs index 62e2f8f..0061bfe 100644 --- a/libcitadel/src/realm/events.rs +++ b/libcitadel/src/realm/events.rs @@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::thread::{self,JoinHandle}; use std::path; -use crate::{RealmManager, Result, Realm}; +use crate::{RealmManager, Result, Realm, util}; use super::realms::HasCurrentChanged; use dbus::{Connection, BusType, ConnectionItem, Message, Path}; use inotify::{Inotify, WatchMask, WatchDescriptor, Event}; @@ -212,8 +212,10 @@ impl DbusEventListener { } fn dbus_event_loop(&self) -> Result<()> { - let connection = Connection::get_private(BusType::System)?; - connection.add_match("interface='org.freedesktop.machine1.Manager',type='signal'")?; + let connection = Connection::get_private(BusType::System) + .map_err(|e| format_err!("Failed to connect to DBUS system bus: {}", e))?; + connection.add_match("interface='org.freedesktop.machine1.Manager',type='signal'") + .map_err(|e| format_err!("DBUS error adding match rule: {}", e))?; for item in connection.iter(1000) { if self.inner().quit_flag() { break; @@ -244,7 +246,8 @@ impl DbusEventListener { let member = message.member() .ok_or_else(|| format_err!("invalid signal"))?; - let (name, _path): (String, Path) = message.read2()?; + let (name, _path): (String, Path) = message.read2() + .map_err(|e| format_err!("Failed to read dbus signal: {}", e))?; if let (Some(interface),Some(member)) = (message.interface(),message.member()) { verbose!("DBUS: {}:[{}({})]", interface, member,name); } @@ -286,18 +289,21 @@ struct InotifyEventListener { impl InotifyEventListener { fn create(inner: Arc>) -> Result { - let mut inotify = Inotify::init()?; - let realms_watch = inotify.add_watch("/realms", WatchMask::MOVED_FROM|WatchMask::MOVED_TO)?; - let current_watch = inotify.add_watch("/run/citadel/realms/current", WatchMask::CREATE|WatchMask::MOVED_TO)?; + let mut inotify = Inotify::init() + .map_err(context!("inotify initialization failed"))?; + let realms_watch = inotify.add_watch("/realms", WatchMask::MOVED_FROM|WatchMask::MOVED_TO) + .map_err(context!("error adding watch for /realms to inotify"))?; + let current_watch = inotify.add_watch("/run/citadel/realms/current", WatchMask::CREATE|WatchMask::MOVED_TO) + .map_err(context!("error adding watch for /run/citadel/realms/current to inotify"))?; Ok(InotifyEventListener { inner, inotify, realms_watch, current_watch, }) } fn wake_inotify() -> Result<()> { let path = "/run/citadel/realms/current/stop-events"; - fs::File::create(path)?; - fs::remove_file(path)?; - Ok(()) + fs::File::create(path) + .map_err(context!("error creating {}", path))?; + util::remove_file(path) } fn spawn(mut self) -> JoinHandle> { @@ -307,7 +313,8 @@ impl InotifyEventListener { fn inotify_event_loop(&mut self) -> Result<()> { let mut buffer = [0; 1024]; while !self.inner().quit_flag() { - let events = self.inotify.read_events_blocking(&mut buffer)?; + let events = self.inotify.read_events_blocking(&mut buffer) + .map_err(context!("error reading inotify events"))?; if !self.inner().quit_flag() { for event in events { diff --git a/libcitadel/src/realm/launcher.rs b/libcitadel/src/realm/launcher.rs index 68bb0d3..aa09860 100644 --- a/libcitadel/src/realm/launcher.rs +++ b/libcitadel/src/realm/launcher.rs @@ -1,9 +1,7 @@ -use std::fs; -use std::fmt::Write; - -use crate::{Realm,Result}; +use std::fmt::{self,Write}; use std::path::{Path, PathBuf}; -use crate::realm::network::NetworkConfig; + +use crate::{Realm, Result, util, realm::network::NetworkConfig}; const NSPAWN_FILE_TEMPLATE: &str = "\ [Exec] @@ -77,15 +75,8 @@ impl <'a> RealmLauncher <'a> { } pub fn remove_launch_config_files(&self) -> Result<()> { - let nspawn_path = self.realm_nspawn_path(); - if nspawn_path.exists() { - fs::remove_file(&nspawn_path)?; - } - let service_path = self.realm_service_path(); - if service_path.exists() { - fs::remove_file(&service_path)?; - } - Ok(()) + util::remove_file(self.realm_nspawn_path())?; + util::remove_file(self.realm_service_path()) } pub fn write_launch_config_files(&mut self, rootfs: &Path, netconfig: &mut NetworkConfig) -> Result<()> { @@ -94,15 +85,11 @@ impl <'a> RealmLauncher <'a> { } let nspawn_path = self.realm_nspawn_path(); let nspawn_content = self.generate_nspawn_file(netconfig)?; - self.write_launch_config_file(&nspawn_path, &nspawn_content) - .map_err(|e| format_err!("failed to write nspawn config file {}: {}", nspawn_path.display(), e))?; + self.write_launch_config_file(&nspawn_path, &nspawn_content)?; let service_path = self.realm_service_path(); let service_content = self.generate_service_file(rootfs); self.write_launch_config_file(&service_path, &service_content) - .map_err(|e| format_err!("failed to write service config file {}: {}", service_path.display(), e))?; - - Ok(()) } pub fn realm_service_name(&self) -> &str { @@ -113,15 +100,10 @@ impl <'a> RealmLauncher <'a> { /// not already exist, create it. fn write_launch_config_file(&self, path: &Path, content: &str) -> Result<()> { match path.parent() { - Some(parent) => { - if !parent.exists() { - fs::create_dir_all(parent)?; - } - }, + Some(parent) => util::create_dir(parent)?, None => bail!("config file path {} has no parent?", path.display()), }; - fs::write(path, content)?; - Ok(()) + util::write_file(path, content) } fn generate_nspawn_file(&mut self, netconfig: &mut NetworkConfig) -> Result { @@ -238,4 +220,10 @@ impl <'a> RealmLauncher <'a> { fn realm_nspawn_path(&self) -> PathBuf { PathBuf::from(SYSTEMD_NSPAWN_PATH).join(format!("{}.nspawn", self.realm.name())) } +} + +impl From for crate::Error { + fn from(e: fmt::Error) -> Self { + format_err!("Error formatting string: {}", e).into() + } } \ No newline at end of file diff --git a/libcitadel/src/realm/manager.rs b/libcitadel/src/realm/manager.rs index cd44c55..9cf1df5 100644 --- a/libcitadel/src/realm/manager.rs +++ b/libcitadel/src/realm/manager.rs @@ -1,5 +1,4 @@ use std::collections::HashSet; -use std::fs; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -205,7 +204,7 @@ impl RealmManager { let home = realm.base_path_file("home"); if !home.exists() { warn!("No home directory exists at {}, creating an empty directory", home.display()); - fs::create_dir_all(&home)?; + util::create_dir(&home)?; util::chown_user(&home)?; } @@ -226,10 +225,9 @@ impl RealmManager { fn create_realm_namefile(&self, realm: &Realm) -> Result<()> { let namefile = realm.run_path_file("realm-name"); - fs::write(&namefile, realm.name())?; + util::write_file(&namefile, realm.name())?; self.systemd.machinectl_copy_to(realm, &namefile, "/run/realm-name")?; - fs::remove_file(&namefile)?; - Ok(()) + util::remove_file(&namefile) } fn start_realm_dependencies(&self, realm: &Realm, starting: &mut HashSet) -> Result<()> { @@ -331,13 +329,14 @@ impl RealmManager { // ensure that /proc/pid/root/run and /proc/pid/root/run/realm-name // are not symlinks - let run_meta = run.symlink_metadata()?; - let name_meta = realm_name.symlink_metadata()?; + let run_meta = run.symlink_metadata() + .map_err(context!("failed reading symlink metadata {:?}", run))?; + let name_meta = realm_name.symlink_metadata() + .map_err(context!("failed reading symlink metadata {:?}", realm_name))?; if !run_meta.file_type().is_dir() || !name_meta.file_type().is_file() { bail!("invalid path"); } - let bytes = fs::read(realm_name)?; - Ok(String::from_utf8(bytes)?) + util::read_to_string(&realm_name) } pub fn rescan_realms(&self) -> Result<(Vec,Vec)> { @@ -381,7 +380,6 @@ impl RealmManager { } self.inner_mut().realmfs_set.remove(realmfs.name()); info!("Removing RealmFS image file {}", realmfs.path().display()); - fs::remove_file(realmfs.path())?; - Ok(()) + util::remove_file(realmfs.path()) } } diff --git a/libcitadel/src/realm/network.rs b/libcitadel/src/realm/network.rs index ac852d6..8463de4 100644 --- a/libcitadel/src/realm/network.rs +++ b/libcitadel/src/realm/network.rs @@ -2,9 +2,9 @@ use std::path::{Path,PathBuf}; use std::net::Ipv4Addr; use std::collections::{HashSet,HashMap}; use std::io::{BufReader,BufRead,Write}; -use std::fs::{self,File}; +use std::fs::File; -use crate::Result; +use crate::{Result, util}; const REALMS_RUN_PATH: &str = "/run/citadel/realms"; @@ -90,7 +90,8 @@ impl BridgeAllocator { let (addr_str, mask_size) = match network.find('/') { Some(idx) => { let (net,bits) = network.split_at(idx); - (net.to_owned(), bits[1..].parse()?) + (net.to_owned(), bits[1..].parse() + .map_err(|_| format_err!("Failed to parse netmask size ({})", &bits[1..]))?) }, None => (network.to_owned(), 24), }; @@ -99,7 +100,7 @@ impl BridgeAllocator { } let mask = (1u32 << (32 - mask_size)) - 1; - let ip = addr_str.parse::()?; + let ip = addr_str.parse::().map_err(|_| format_err!("Failed to parse IP address ({})", addr_str))?; if (u32::from(ip) & mask) != 0 { bail!("network {} has masked bits with netmask /{}", addr_str, mask_size); @@ -199,13 +200,12 @@ impl BridgeAllocator { if !path.exists() { return Ok(()) } - let f = File::open(path)?; + let f = File::open(&path).map_err(context!("failed opening network state file {:?} for reading", path))?; let reader = BufReader::new(f); for line in reader.lines() { - let line = &line?; + let line = &line.map_err(context!("error reading from network state file"))?; self.parse_state_line(line)?; } - Ok(()) } @@ -213,7 +213,7 @@ impl BridgeAllocator { match line.find(':') { Some(idx) => { let (name,addr) = line.split_at(idx); - let ip = addr[1..].parse::()?; + let ip = addr[1..].parse::().map_err(|_| format_err!("Failed to parse IP address ({})", &addr[1..]))?; self.allocated.insert(ip); self.allocations.insert(name.to_owned(), ip); }, @@ -225,15 +225,13 @@ impl BridgeAllocator { fn write_state(&mut self) -> Result<()> { let path = self.state_file_path(); let dir = path.parent().unwrap(); - if !dir.exists() { - fs::create_dir_all(dir) - .map_err(|e| format_err!("failed to create directory {} for network allocation state file: {}", dir.display(), e))?; - } + util::create_dir(dir)?; let mut f = File::create(&path) - .map_err(|e| format_err!("failed to open network state file {} for writing: {}", path.display(), e))?; + .map_err(context!("failed to open network state file {:?} for writing", path))?; for (realm,addr) in &self.allocations { - writeln!(f, "{}:{}", realm, addr)?; + writeln!(f, "{}:{}", realm, addr) + .map_err(context!("error writing to network allocation state file"))?; } Ok(()) } diff --git a/libcitadel/src/realm/overlay.rs b/libcitadel/src/realm/overlay.rs index d5449ea..e3a4d72 100644 --- a/libcitadel/src/realm/overlay.rs +++ b/libcitadel/src/realm/overlay.rs @@ -1,8 +1,7 @@ use std::fs; -use std::os::unix; use std::path::{Path,PathBuf}; -use crate::{Realm,Result}; +use crate::{Realm, Result, util}; use crate::Exec; use crate::realm::config::OverlayType; @@ -68,7 +67,7 @@ impl RealmOverlay { } let lower = base.join("lower").read_link() - .map_err(|e| format_err!("Unable to read link to 'lower' directory of overlay: {}", e)); + .map_err(context!("unable to read link to 'lower' directory of overlay")); match self.overlay { OverlayType::TmpFS => self.remove_tmpfs(&base)?, @@ -93,14 +92,14 @@ impl RealmOverlay { fn remove_tmpfs(&self, base: &Path) -> Result<()> { fs::remove_dir_all(base) - .map_err(|e| format_err!("Could not remove overlay directory {}: {}", base.display(), e)) + .map_err(context!("failed to remove overlay directory {:?}", base)) } fn remove_btrfs(&self, base: &Path) -> Result<()> { Exec::new("/usr/bin/btrfs") .quiet() .run(format!("subvolume delete {}", base.display())) - .map_err(|e| format_err!("Could not remove btrfs subvolume {}: {}", base.display(), e)) + .map_err(context!("failed to remove btrfs subvolume {:?}", base)) } fn create_tmpfs(&self, lower: &Path) -> Result { @@ -139,7 +138,8 @@ impl RealmOverlay { let upper = self.mkdir(base, "upperdir")?; let work = self.mkdir(base, "workdir")?; let mountpoint = self.mkdir(base, "mountpoint")?; - unix::fs::symlink(lower, base.join("lower"))?; + let baselower = base.join("lower"); + util::symlink(lower, &baselower)?; cmd!("/usr/bin/mount", "-t overlay realm-{}-overlay -olowerdir={},upperdir={},workdir={} {}", self.realm, @@ -152,8 +152,7 @@ impl RealmOverlay { fn mkdir(&self, base: &Path, dirname: &str) -> Result { let path = base.join(dirname); - fs::create_dir_all(&path) - .map_err(|e| format_err!("failed to create directory {}: {}", path.display(), e))?; + util::create_dir(&path)?; Ok(path) } diff --git a/libcitadel/src/realm/realm.rs b/libcitadel/src/realm/realm.rs index db15edc..aa25b27 100644 --- a/libcitadel/src/realm/realm.rs +++ b/libcitadel/src/realm/realm.rs @@ -187,11 +187,9 @@ impl Realm { /// to order the output when listing realms. pub fn update_timestamp(&self) -> Result<()> { let tstamp = self.base_path().join(".tstamp"); - if tstamp.exists() { - fs::remove_file(&tstamp)?; - } + util::remove_file(&tstamp)?; fs::File::create(&tstamp) - .map_err(|e| format_err!("failed to create timestamp file {}: {}", tstamp.display(), e))?; + .map_err(context!("failed to create timestamp file {:?}", tstamp))?; // also load the new value self.inner_mut().timestamp = self.load_timestamp(); Ok(()) @@ -309,7 +307,7 @@ impl Realm { // not exist, create it as a fork of 'base' base.fork(default) } else { - Err(format_err!("Default RealmFS '{}' does not exist and neither does 'base'", default)) + bail!("Default RealmFS '{}' does not exist and neither does 'base'", default) } } @@ -459,11 +457,10 @@ impl Realm { let path = self.base_path_file("notes"); let notes = notes.as_ref(); if path.exists() && notes.is_empty() { - fs::remove_file(path)?; + util::remove_file(&path) } else { - fs::write(path, notes)?; + util::write_file(&path, notes) } - Ok(()) } } diff --git a/libcitadel/src/realm/realms.rs b/libcitadel/src/realm/realms.rs index 9433540..de93257 100644 --- a/libcitadel/src/realm/realms.rs +++ b/libcitadel/src/realm/realms.rs @@ -1,9 +1,9 @@ use std::collections::{HashMap, HashSet}; use std::path::{Path,PathBuf}; use std::fs; - -use crate::{Realm, Result, symlink, RealmManager,FileLock}; use std::sync::{Arc, Weak}; + +use crate::{Realm, Result, symlink, RealmManager, FileLock, util}; use super::create::RealmCreateDestroy; use crate::realm::systemd::Systemd; @@ -88,12 +88,13 @@ impl Realms { fn all_realms(mark_active: bool) -> Result> { let mut v = Vec::new(); - for entry in fs::read_dir(Realms::BASE_PATH)? { - let entry = entry?; - if let Some(realm) = Realms::entry_to_realm(&entry) { + util::read_directory(Realms::BASE_PATH, |dent| { + if let Some(realm) = Realms::entry_to_realm(dent) { v.push(realm); } - } + Ok(()) + })?; + if mark_active { Realms::mark_active_realms(&mut v)?; } diff --git a/libcitadel/src/realm/systemd.rs b/libcitadel/src/realm/systemd.rs index 74e7925..11837de 100644 --- a/libcitadel/src/realm/systemd.rs +++ b/libcitadel/src/realm/systemd.rs @@ -1,18 +1,17 @@ -use std::process::Command; -use std::path::Path; use std::env; +use std::path::Path; +use std::process::{Command,Stdio}; +use std::sync::Mutex; + +use crate::{Result,Realm}; +use crate::realm::{ + launcher::RealmLauncher, + network::NetworkConfig +}; const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl"; const MACHINECTL_PATH: &str = "/usr/bin/machinectl"; -use crate::Result; - -use crate::Realm; -use std::sync::Mutex; -use std::process::Stdio; -use crate::realm::network::NetworkConfig; -use crate::realm::launcher::RealmLauncher; - pub struct Systemd { network: Mutex, } @@ -56,7 +55,8 @@ impl Systemd { for dir in realm.config().ephemeral_persistent_dirs() { let src = home.join(&dir); if src.exists() { - let src = src.canonicalize()?; + let src = src.canonicalize() + .map_err(context!("failed to canonicalize {:?}", src))?; if src.starts_with(&home) && src.exists() { let dst = Path::new("/home/user").join(&dir); self.machinectl_bind(realm, &src, &dst)?; @@ -86,12 +86,13 @@ impl Systemd { } fn run_systemctl(&self, op: &str, name: &str) -> Result { - Command::new(SYSTEMCTL_PATH) + let ok = Command::new(SYSTEMCTL_PATH) .arg(op) .arg(name) .status() .map(|status| status.success()) - .map_err(|e| format_err!("failed to execute {}: {}", MACHINECTL_PATH, e)) + .map_err(context!("failed to execute {}", MACHINECTL_PATH))?; + Ok(ok) } pub fn machinectl_copy_to(&self, realm: &Realm, from: impl AsRef, to: &str) -> Result<()> { @@ -100,7 +101,7 @@ impl Systemd { Command::new(MACHINECTL_PATH) .args(&["copy-to", realm.name(), from, to ]) .status() - .map_err(|e| format_err!("failed to machinectl copy-to {} {} {}: {}", realm.name(), from, to, e))?; + .map_err(context!("failed to machinectl copy-to {} {} {}", realm.name(), from, to))?; Ok(()) } @@ -110,17 +111,18 @@ impl Systemd { Command::new(MACHINECTL_PATH) .args(&["--mkdir", "bind", realm.name(), from.as_str(), to.as_str() ]) .status() - .map_err(|e| format_err!("failed to machinectl bind {} {} {}: {}", realm.name(), from, to, e))?; + .map_err(context!("failed to machinectl bind {} {} {}", realm.name(), from, to))?; Ok(()) } pub fn is_active(realm: &Realm) -> Result { - Command::new(SYSTEMCTL_PATH) + let ok = Command::new(SYSTEMCTL_PATH) .args(&["--quiet", "is-active"]) .arg(format!("realm-{}", realm.name())) .status() .map(|status| status.success()) - .map_err(|e| format_err!("failed to execute {}: {}", SYSTEMCTL_PATH, e)) + .map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?; + Ok(ok) } pub fn are_realms_active(realms: &mut Vec) -> Result { @@ -132,7 +134,8 @@ impl Systemd { .arg("is-active") .args(args) .stderr(Stdio::inherit()) - .output()?; + .output() + .map_err(context!("failed to run /usr/bin/systemctl"))?; Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned()) } @@ -175,7 +178,7 @@ impl Systemd { cmd.arg(arg.as_ref()); } - cmd.status().map_err(|e| format_err!("failed to execute{}: {}", MACHINECTL_PATH, e))?; + cmd.status().map_err(context!("failed to execute {}", MACHINECTL_PATH))?; Ok(()) } } diff --git a/libcitadel/src/realmfs/mountpoint.rs b/libcitadel/src/realmfs/mountpoint.rs index 04d8472..bd257d8 100644 --- a/libcitadel/src/realmfs/mountpoint.rs +++ b/libcitadel/src/realmfs/mountpoint.rs @@ -3,7 +3,7 @@ use std::fmt; use std::fs::{self, DirEntry}; use std::path::{PathBuf, Path}; -use crate::{Result, RealmFS, CommandLine, ImageHeader}; +use crate::{Result, RealmFS, CommandLine, ImageHeader, util}; use crate::verity::Verity; @@ -28,7 +28,8 @@ impl Mountpoint { /// Read `RealmFS::RUN_DIRECTORY` to collect all current mountpoints /// and return them. pub fn all_mountpoints() -> Result> { - let all = fs::read_dir(RealmFS::RUN_DIRECTORY)? + let all = fs::read_dir(RealmFS::RUN_DIRECTORY) + .map_err(context!("error reading realmfs run directory {}", RealmFS::RUN_DIRECTORY))? .flat_map(|e| e.ok()) .map(Into::into) .filter(Mountpoint::is_valid) @@ -51,11 +52,6 @@ impl Mountpoint { self.0.exists() } - fn create_dir(&self) -> Result<()> { - fs::create_dir_all(self.path())?; - Ok(()) - } - pub fn is_mounted(&self) -> bool { // test for an arbitrary expected directory self.path().join("etc").exists() @@ -73,9 +69,8 @@ impl Mountpoint { return Ok(()) } - if !self.exists() { - self.create_dir()?; - } + util::create_dir(self.path())?; + let verity_path = self.verity_device_path(); if verity_path.exists() { warn!("dm-verity device {:?} already exists which was not expected", verity_path); diff --git a/libcitadel/src/realmfs/realmfs.rs b/libcitadel/src/realmfs/realmfs.rs index 798d5cb..35bbc62 100644 --- a/libcitadel/src/realmfs/realmfs.rs +++ b/libcitadel/src/realmfs/realmfs.rs @@ -130,11 +130,10 @@ impl RealmFS { /// Return an Error result if name is not valid. fn validate_name(name: &str) -> Result<()> { - if Self::is_valid_name(name) { - Ok(()) - } else { - Err(format_err!("Invalid realm name '{}'", name)) + if !Self::is_valid_name(name) { + bail!("Invalid realm name '{}'", name); } + Ok(()) } /// Return `true` if `name` is a valid name for a RealmFS. @@ -211,11 +210,10 @@ impl RealmFS { let path = self.path_with_extension("notes"); let notes = notes.as_ref(); if path.exists() && notes.is_empty() { - fs::remove_file(path)?; + util::remove_file(&path) } else { - fs::write(path, notes)?; + util::write_file(&path, notes) } - Ok(()) } /// Return `MetaInfo` from image header of this RealmFS. @@ -353,7 +351,8 @@ impl RealmFS { // Return the length in blocks of the actual image file on disk pub fn file_nblocks(&self) -> Result { - let meta = self.path.metadata()?; + let meta = self.path.metadata() + .map_err(context!("failed to read metadata from realmfs image file {:?}", self.path))?; let len = meta.len() as usize; if len % 4096 != 0 { bail!("realmfs image file '{}' has size which is not a multiple of block size", self.path.display()); @@ -397,7 +396,8 @@ impl RealmFS { } pub fn allocated_size_blocks(&self) -> Result { - let meta = self.path().metadata()?; + let meta = self.path().metadata() + .map_err(context!("failed to read metadata from realmfs image file {:?}", self.path()))?; Ok(meta.blocks() as usize / 8) } diff --git a/libcitadel/src/realmfs/realmfs_set.rs b/libcitadel/src/realmfs/realmfs_set.rs index 4b148ab..af65910 100644 --- a/libcitadel/src/realmfs/realmfs_set.rs +++ b/libcitadel/src/realmfs/realmfs_set.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::fs; use std::sync::Arc; -use crate::{RealmFS, RealmManager, Result}; +use crate::{RealmFS, RealmManager, Result, util}; pub struct RealmFSSet { realmfs_map: HashMap, @@ -21,12 +21,23 @@ impl RealmFSSet { fn load_all() -> Result> { let mut v = Vec::new(); - for entry in fs::read_dir(RealmFS::BASE_PATH)? { - let entry = entry?; + util::read_directory(RealmFS::BASE_PATH, |dent| { + if let Some(realmfs) = Self::entry_to_realmfs(dent) { + v.push(realmfs) + } + Ok(()) + })?; + /* + let entries = fs::read_dir(RealmFS::BASE_PATH) + .map_err(context!("error reading realmfs directory {}", RealmFS::BASE_PATH))?; + for entry in entries { + let entry = entry.map_err(context!("error reading directory entry"))?; if let Some(realmfs) = Self::entry_to_realmfs(&entry) { v.push(realmfs) } } + + */ Ok(v) } diff --git a/libcitadel/src/realmfs/resizer.rs b/libcitadel/src/realmfs/resizer.rs index 1e8583e..6b565ea 100644 --- a/libcitadel/src/realmfs/resizer.rs +++ b/libcitadel/src/realmfs/resizer.rs @@ -80,10 +80,14 @@ impl Superblock { } pub fn load(path: impl AsRef, offset: u64) -> Result { + let path = path.as_ref(); let mut sb = Self::new(); - let mut file = File::open(path.as_ref())?; - file.seek(SeekFrom::Start(1024 + offset))?; - file.read_exact(&mut sb.0)?; + let mut file = File::open(path) + .map_err(context!("failed to open image file {:?}", path))?; + file.seek(SeekFrom::Start(1024 + offset)) + .map_err(context!("failed to seek to offset {} of image file {:?}", 1024 + offset, path))?; + file.read_exact(&mut sb.0) + .map_err(context!("error reading superblock from image file {:?}", path))?; Ok(sb) } diff --git a/libcitadel/src/realmfs/update.rs b/libcitadel/src/realmfs/update.rs index 19e6b67..7f81032 100644 --- a/libcitadel/src/realmfs/update.rs +++ b/libcitadel/src/realmfs/update.rs @@ -5,7 +5,7 @@ use std::process::Command; use sodiumoxide::randombytes::randombytes; -use crate::{Result, RealmFS, FileLock, ImageHeader, LoopDevice, ResizeSize, util}; +use crate::{Result, RealmFS, FileLock, ImageHeader, LoopDevice, ResizeSize, util, Error}; use crate::realm::BridgeAllocator; use crate::util::is_euid_root; use crate::terminal::TerminalRestorer; @@ -72,7 +72,7 @@ impl <'a> Update<'a> { fn create_update_copy(&self) -> Result<()> { if self.target.exists() { info!("Update file {} already exists, removing it", self.target.display()); - fs::remove_file(&self.target)?; + util::remove_file(&self.target)?; } self.realmfs.copy_image_file(self.target())?; @@ -108,9 +108,7 @@ impl <'a> Update<'a> { if self.resize.is_some() { self.resize_device(loopdev)?; } - if !self.mountpath.exists() { - fs::create_dir_all(&self.mountpath)?; - } + util::create_dir(&self.mountpath)?; util::mount(loopdev.device_str(), &self.mountpath, Some("-orw,noatime"))?; Ok(()) }) @@ -126,7 +124,6 @@ impl <'a> Update<'a> { if self.mountpath.exists() { if let Err(err) = util::umount(&self.mountpath) { warn!("Failed to unmount directory {:?}: {}", self.mountpath, err); - } if let Err(err) = fs::remove_dir(&self.mountpath) { warn!("Failed to remove mountpoint directory {:?}: {}", self.mountpath, err); @@ -165,9 +162,11 @@ impl <'a> Update<'a> { let len = (nblocks * BLOCK_SIZE) as u64; let f = fs::OpenOptions::new() .write(true) - .open(&self.target)?; - f.set_len(len)?; - Ok(()) + .open(&self.target) + .map_err(context!("failed to open update image file {:?} to set length", self.target))?; + + f.set_len(len) + .map_err(context!("failed setting length of update image file {:?} to {}", self.target, len)) } // Remove dm-verity hash tree from update copy of image file. @@ -229,8 +228,10 @@ impl <'a> Update<'a> { }; let salt = hex::encode(randombytes(32)); - let verity = Verity::new(&self.target)?; - let output = verity.generate_image_hashtree_with_salt(&salt, nblocks)?; + let verity = Verity::new(&self.target) + .map_err(context!("failed to create verity context for realmfs update image {:?}", self.target()))?; + let output = verity.generate_image_hashtree_with_salt(&salt, nblocks) + .map_err(context!("failed to generate dm-verity hashtree for realmfs update image {:?}", self.target()))?; // XXX passes metainfo for nblocks //let output = Verity::new(&self.target).generate_image_hashtree_with_salt(&self.realmfs.metainfo(), &salt)?; let root_hash = output.root_hash() @@ -252,15 +253,21 @@ impl <'a> Update<'a> { let header = ImageHeader::new(); header.set_flag(ImageHeader::FLAG_HASH_TREE); header.update_metainfo(&metainfo_bytes, sig.to_bytes(), &self.target) + .map_err(context!("failed to write header to update image {:?}", self.target()))?; + Ok(()) + } fn prompt_user(prompt: &str, default_y: bool) -> Result { let yn = if default_y { "(Y/n)" } else { "(y/N)" }; print!("{} {} : ", prompt, yn); - io::stdout().flush()?; + io::stdout().flush() + .map_err(context!("failed to flush stdout"))?; + let mut line = String::new(); - io::stdin().read_line(&mut line)?; + io::stdin().read_line(&mut line) + .map_err(context!("failed reading line from stdin"))?; let yes = match line.trim().chars().next() { Some(c) => c == 'Y' || c == 'y', @@ -319,7 +326,7 @@ impl <'a> Update<'a> { .status() .map_err(|e| { let _ = self.cleanup(); - e + Error::with_error("failed to run systemd-nspawn", e) })?; Ok(()) } @@ -339,12 +346,13 @@ impl <'a> Update<'a> { for i in (1..NUM_BACKUPS).rev() { let from = backup(i - 1); if from.exists() { - fs::rename(from, backup(i))?; + let to = backup(i); + util::rename(&from, &to)?; } } - fs::rename(self.realmfs.path(), backup(0))?; - fs::rename(self.target(), self.realmfs.path())?; - Ok(()) + let to = backup(0); + util::rename(self.realmfs.path(), &to)?; + util::rename(self.target(), self.realmfs.path()) } } diff --git a/libcitadel/src/resource.rs b/libcitadel/src/resource.rs index 5b3f790..8a5896a 100644 --- a/libcitadel/src/resource.rs +++ b/libcitadel/src/resource.rs @@ -1,11 +1,10 @@ -use std::fs::{self,File,DirEntry}; +use std::fs::{File,DirEntry}; use std::ffi::OsStr; use std::io::{self,Seek,SeekFrom}; use std::path::{Path, PathBuf}; -use crate::{CommandLine, OsRelease, ImageHeader, MetaInfo, Result, Partition, Mounts, util, LoopDevice}; +use crate::{Result, CommandLine, OsRelease, ImageHeader, MetaInfo, Partition, Mounts, util, LoopDevice}; -use failure::ResultExt; use std::sync::Arc; use crate::UtsName; use crate::verity::Verity; @@ -49,7 +48,7 @@ impl ResourceImage { } if !Self::ensure_storage_mounted()? { - bail!("Unable to mount /storage"); + bail!("unable to mount /storage"); } let storage_path = Path::new(STORAGE_BASEDIR).join(&channel); @@ -58,7 +57,7 @@ impl ResourceImage { return Ok(image); } - Err(format_err!("Failed to find resource image of type: {}", image_type)) + bail!("failed to find resource image of type: {}", image_type) } pub fn mount_image_type(image_type: &str) -> Result<()> { @@ -70,14 +69,14 @@ impl ResourceImage { pub fn find_rootfs() -> Result { match search_directory(RUN_DIRECTORY, "rootfs", None)? { Some(image) => Ok(image), - None => Err(format_err!("Failed to find rootfs resource image")), + None => bail!("failed to find rootfs resource image"), } } pub fn from_path>(path: P) -> Result { let header = ImageHeader::from_file(path.as_ref())?; if !header.is_magic_valid() { - bail!("Image file {} does not have a valid header", path.as_ref().display()); + bail!("image file {:?} does not have a valid header", path.as_ref()); } Ok(Self::new(path.as_ref(), header )) } @@ -143,25 +142,28 @@ impl ResourceImage { return Ok(()) } info!("decompressing image file {}", self.path().display()); - let mut reader = File::open(self.path())?; - reader.seek(SeekFrom::Start(4096))?; + let mut reader = File::open(self.path()) + .map_err(context!("error opening image file {:?}", self.path()))?; + reader.seek(SeekFrom::Start(4096)) + .map_err(context!("error seeking to offset 4096 in image file {:?}", self.path()))?; let xzfile = self.path.with_extension("tmp.xz"); - let mut out = File::create(&xzfile)?; - io::copy(&mut reader, &mut out)?; + let mut out = File::create(&xzfile) + .map_err(context!("error creating temporary file {:?}", xzfile))?; + io::copy(&mut reader, &mut out) + .map_err(context!("error copying image file {:?} to temporary file", self.path()))?; util::xz_decompress(xzfile)?; - fs::rename(self.path.with_extension("tmp"), self.path())?; + let tmpfile = self.path.with_extension("tmp"); + util::rename(&tmpfile, self.path())?; self.header.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED); - self.header.write_header_to(self.path())?; - - Ok(()) + self.header.write_header_to(self.path()) } pub fn write_to_partition(&self, partition: &Partition) -> Result<()> { if self.metainfo().image_type() != "rootfs" { - bail!("Cannot write to partition, image type is not rootfs"); + bail!("cannot write to partition, image type is not rootfs"); } if !self.has_verity_hashtree() { @@ -183,7 +185,7 @@ impl ResourceImage { info!("Mounting dm-verity device to {}", mount_path.display()); - fs::create_dir_all(mount_path)?; + util::create_dir(mount_path)?; util::mount(verity_path, mount_path, None)?; Ok(ResourceMount::new_verity(mount_path, verity_dev)) @@ -195,11 +197,11 @@ impl ResourceImage { match self.header.public_key()? { Some(pubkey) => { if !self.header.verify_signature(pubkey) { - bail!("Header signature verification failed"); + bail!("header signature verification failed"); } info!("Image header signature is valid"); } - None => bail!("Cannot verify header signature because no public key for channel {} is available", self.metainfo().channel()) + None => bail!("cannot verify header signature because no public key for channel {} is available", self.metainfo().channel()) } } info!("Setting up dm-verity device for image"); @@ -240,7 +242,7 @@ impl ResourceImage { } info!("Calculating sha256 of image"); let output = util::exec_cmdline_pipe_input("sha256sum", "-", self.path(), util::FileRange::Range{offset: 4096, len: self.metainfo().nblocks() * 4096}) - .context(format!("failed to calculate sha256 on {}", self.path().display()))?; + .map_err(context!("failed to calculate sha256 on {:?}", self.path()))?; let v: Vec<&str> = output.split_whitespace().collect(); let shasum = v[0].trim().to_owned(); Ok(shasum) @@ -261,7 +263,7 @@ impl ResourceImage { info!("Loop device created: {}", loopdev); info!("Mounting to: {}", mount_path.display()); - fs::create_dir_all(mount_path)?; + util::create_dir(mount_path)?; util::mount(&loopdev.device_str(), mount_path, Some("-oro"))?; @@ -286,7 +288,8 @@ impl ResourceImage { warn!("No manifest file found for resource image: {}", self.path.display()); return Ok(()) } - let s = fs::read_to_string(manifest)?; + let s = util::read_to_string(manifest)?; + for line in s.lines() { if let Err(e) = self.process_manifest_line(&line) { warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e); @@ -416,7 +419,7 @@ fn parse_timestamp(img: &ResourceImage) -> Result { let ts = img.metainfo() .timestamp() .parse::() - .context(format!("Error parsing timestamp for resource image {}", img.path().display()))?; + .map_err(|_| format_err!("error parsing timestamp for resource image {:?}", img.path()))?; Ok(ts) } @@ -441,9 +444,10 @@ fn all_matching_images(dir: &Path, image_type: &str, channel: Option<&str>) -> R let kernel_id = OsRelease::citadel_kernel_id(); let mut v = Vec::new(); - for entry in fs::read_dir(dir)? { - maybe_add_dir_entry(entry?, image_type, channel, kv, kernel_id, &mut v)?; - } + util::read_directory(dir, |dent| { + maybe_add_dir_entry(dent, image_type, channel, kv, kernel_id, &mut v)?; + Ok(()) + })?; Ok(v) } @@ -454,7 +458,7 @@ fn all_matching_images(dir: &Path, image_type: &str, channel: Option<&str>) -> R // // If the entry is a match, then instantiate a ResourceImage and add it to // the images vector. -fn maybe_add_dir_entry(entry: DirEntry, +fn maybe_add_dir_entry(entry: &DirEntry, image_type: &str, channel: Option<&str>, kernel_version: Option<&str>, @@ -465,7 +469,8 @@ fn maybe_add_dir_entry(entry: DirEntry, if Some(OsStr::new("img")) != path.extension() { return Ok(()) } - let meta = entry.metadata()?; + let meta = entry.metadata() + .map_err(context!("failed to read metadata for {:?}", entry.path()))?; if meta.len() < ImageHeader::HEADER_SIZE as u64 { return Ok(()) } diff --git a/libcitadel/src/symlink.rs b/libcitadel/src/symlink.rs index 8105c28..41ef00a 100644 --- a/libcitadel/src/symlink.rs +++ b/libcitadel/src/symlink.rs @@ -1,8 +1,7 @@ use std::fs; use std::path::{Path,PathBuf}; -use std::os::unix; -use crate::Result; +use crate::{Result, util}; pub fn read(path: impl AsRef) -> Option { let path = path.as_ref(); @@ -29,18 +28,12 @@ pub fn write(target: impl AsRef, link: impl AsRef, tmp_in_parent: bo let tmp = write_tmp_path(link, tmp_in_parent); if let Some(parent) = link.parent() { - if !parent.exists() { - fs::create_dir_all(parent)?; - } + util::create_dir(parent)?; } - if tmp.exists() { - fs::remove_file(&tmp)?; - } - - unix::fs::symlink(target, &tmp)?; - fs::rename(&tmp, link)?; - Ok(()) + util::remove_file(&tmp)?; + util::symlink(target, &tmp)?; + util::rename(&tmp, link) } fn write_tmp_path(link: &Path, tmp_in_parent: bool) -> PathBuf { @@ -60,7 +53,7 @@ fn write_tmp_path(link: &Path, tmp_in_parent: bool) -> PathBuf { pub fn remove(path: impl AsRef) -> Result<()> { let path = path.as_ref(); if fs::symlink_metadata(path).is_ok() { - fs::remove_file(path)?; + util::remove_file(path)?; } Ok(()) } diff --git a/libcitadel/src/system/lock.rs b/libcitadel/src/system/lock.rs index 07f0d42..4070f79 100644 --- a/libcitadel/src/system/lock.rs +++ b/libcitadel/src/system/lock.rs @@ -3,7 +3,7 @@ use std::io::{Error,ErrorKind}; use std::os::unix::io::AsRawFd; use std::path::{Path,PathBuf}; -use crate::Result; +use crate::{Result, util}; /// /// Create a lockfile and acquire an exclusive lock with flock(2) @@ -45,9 +45,7 @@ impl FileLock { fn open_lockfile(path: &Path) -> Result { if let Some(parent) = path.parent() { - if !parent.exists() { - fs::create_dir_all(parent)?; - } + util::create_dir(parent)?; } // Make a few attempts just in case we try to open lockfile @@ -61,14 +59,14 @@ impl FileLock { return Ok(file); } } - Err(format_err!("unable to open lockfile {}", path.display() )) + bail!("unable to open lockfile {:?}", path) } fn try_create_lockfile(path: &Path) -> Result> { match OpenOptions::new().write(true).create_new(true).open(path) { Ok(file) => Ok(Some(file)), Err(ref e) if e.kind() == ErrorKind::AlreadyExists => Ok(None), - Err(e) => Err(e.into()), + Err(e) => bail!("failed to create lockfile {:?}: {}", path, e), } } @@ -76,7 +74,7 @@ impl FileLock { match File::open(path) { Ok(file) => Ok(Some(file)), Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(None), - Err(e) => Err(e.into()), + Err(e) => bail!("failed to open lockfile {:?}: {}", path, e), } } @@ -99,7 +97,7 @@ impl FileLock { if !block && errno == libc::EWOULDBLOCK { return Ok(false); } - return Err(Error::from_raw_os_error(errno).into()); + bail!("error calling flock(): {}", Error::from_raw_os_error(errno)); } Ok(true) } diff --git a/libcitadel/src/system/loopdev.rs b/libcitadel/src/system/loopdev.rs index 5b2ba81..5f81b7c 100644 --- a/libcitadel/src/system/loopdev.rs +++ b/libcitadel/src/system/loopdev.rs @@ -39,7 +39,7 @@ impl LoopDevice { let result = f(&loopdev); let detach_result = loopdev.detach(); let r = result?; - detach_result.map_err(|e| format_err!("error detaching loop device: {}", e))?; + detach_result.map_err(context!("error detaching loop device"))?; Ok(r) } @@ -108,8 +108,7 @@ impl LoopDevice { // mount --bind olddir newdir // mount -o remount,bind,ro olddir newdir cmd!(Self::MOUNT, "--bind {} {}", rw.display(), ro.display())?; - cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display())?; - Ok(()) + cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display()) } } diff --git a/libcitadel/src/system/mounts.rs b/libcitadel/src/system/mounts.rs index 6cd820c..c0529a1 100644 --- a/libcitadel/src/system/mounts.rs +++ b/libcitadel/src/system/mounts.rs @@ -1,8 +1,7 @@ -use std::fs; use std::collections::HashMap; use std::path::Path; -use crate::Result; +use crate::{Result, util}; pub struct Mounts { content: String, @@ -34,7 +33,7 @@ impl Mounts { } pub fn load() -> Result { - let content = fs::read_to_string("/proc/mounts")?; + let content = util::read_to_string("/proc/mounts")?; Ok(Mounts { content }) } diff --git a/libcitadel/src/terminal/ansi.rs b/libcitadel/src/terminal/ansi.rs index 0f9a440..18221cf 100644 --- a/libcitadel/src/terminal/ansi.rs +++ b/libcitadel/src/terminal/ansi.rs @@ -2,6 +2,7 @@ use crate::Result; use crate::terminal::{RawTerminal, Color, Base16Scheme}; use std::io::{self,Read,Write,Stdout}; +use std::string::FromUtf8Error; #[derive(Default)] pub struct AnsiControl(String); @@ -125,10 +126,9 @@ impl AnsiControl { io::stdout().flush().unwrap(); } - pub fn write_to(&self, mut writer: W) -> Result<()> { - writer.write_all(self.as_bytes())?; - writer.flush()?; - Ok(()) + fn write_to(&self, mut writer: W) -> Result<()> { + writer.write_all(self.as_bytes()).map_err(context!("error writing to terminal"))?; + writer.flush().map_err(context!("error calling flush() on terminal")) } } @@ -137,7 +137,6 @@ pub struct AnsiTerminal { raw: RawTerminal, } - impl AnsiTerminal { pub fn new() -> Result { let raw = RawTerminal::create(io::stdout())?; @@ -210,18 +209,24 @@ impl AnsiTerminal { } fn write_code(&mut self, sequence: AnsiControl) -> Result<()> { + self._write_code(sequence) + .map_err(context!("error writing ansi sequence to terminal")) + } + + fn _write_code(&mut self, sequence: AnsiControl) -> io::Result<()> { self.raw.write_all(sequence.as_bytes())?; - self.raw.flush()?; - Ok(()) + self.raw.flush() } fn read_response(&mut self) -> Result { let stdin = io::stdin(); let mut input = stdin.lock(); let mut buffer = Vec::new(); - input.read_to_end(&mut buffer)?; - let s = String::from_utf8(buffer)?; - Ok(s) + input.read_to_end(&mut buffer) + .map_err(context!("error reading terminal input"))?; + + String::from_utf8(buffer) + .map_err(context!("terminal input is not correct utf-8")) } pub fn apply_base16(&mut self, base16: &Base16Scheme) -> Result<()> { @@ -235,3 +240,9 @@ impl AnsiTerminal { } } + +impl From for crate::Error { + fn from(_: FromUtf8Error) -> Self { + format_err!("failed to convert string to UTF-8").into() + } +} diff --git a/libcitadel/src/terminal/base16.rs b/libcitadel/src/terminal/base16.rs index 3a8dc40..3730042 100644 --- a/libcitadel/src/terminal/base16.rs +++ b/libcitadel/src/terminal/base16.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::terminal::{Color, Base16Shell}; use crate::{Realm, Result, util, RealmManager}; use std::path::Path; -use std::fs; +use std::{fs, io}; use std::io::Write; lazy_static! { @@ -123,7 +123,7 @@ impl Base16Scheme { fn write_ephemeral_realm_files(&self, manager: &RealmManager, realm: &Realm) -> Result<()> { let skel = realm.base_path_file("skel"); - fs::create_dir_all(&skel)?; + util::create_dir(&skel)?; util::chown_user(&skel)?; self.write_realm_files(&skel)?; if realm.is_active() { @@ -144,9 +144,9 @@ impl Base16Scheme { pub fn write_realm_files>(&self, base: P) -> Result<()> { let base = base.as_ref(); self.write_shell_file(base) - .map_err(|e| format_err!("error writing {} to {}: {}", Self::BASE16_SHELL_FILE, base.display(), e))?; + .map_err(context!("error writing {} to {}", Self::BASE16_SHELL_FILE, base.display()))?; self.write_vim_file(base) - .map_err(|e| format_err!("error writing {} to {}: {}", Self::BASE16_VIM_FILE, base.display(), e))?; + .map_err(context!("error writing {} to {}", Self::BASE16_VIM_FILE, base.display()))?; Ok(()) } @@ -160,16 +160,20 @@ impl Base16Scheme { fn write_vim_file(&self, dir: &Path) -> Result<()> { let path = dir.join(Self::BASE16_VIM_FILE); - let mut file = fs::File::create(&path)?; - writeln!(&mut file, "if !exists('g:colors_name') || g:colors_name != '{}'", self.slug())?; - writeln!(&mut file, " colorscheme base16-{}", self.slug())?; - writeln!(&mut file, "endif")?; - drop(file); + self.write_vim_file_to(&path) + .map_err(context!("failed to write vim color scheme file to {:?}", path))?; util::chown_user(&path)?; debug!("Wrote base16 vim config file to {}", path.display()); Ok(()) } + fn write_vim_file_to(&self, path: &Path) -> io::Result<()> { + let mut file = fs::File::create(&path)?; + writeln!(&mut file, "if !exists('g:colors_name') || g:colors_name != '{}'", self.slug())?; + writeln!(&mut file, " colorscheme base16-{}", self.slug())?; + writeln!(&mut file, "endif")?; + Ok(()) + } } diff --git a/libcitadel/src/terminal/base16_shell.rs b/libcitadel/src/terminal/base16_shell.rs index 6f3dd14..d89ee3b 100644 --- a/libcitadel/src/terminal/base16_shell.rs +++ b/libcitadel/src/terminal/base16_shell.rs @@ -1,8 +1,7 @@ -use std::fs; use std::path::Path; use crate::terminal::Base16Scheme; -use crate::Result; +use crate::{Result, util}; const TEMPLATE: &str = r##" if [ -n "$TMUX" ]; then @@ -70,8 +69,8 @@ impl Base16Shell { pub fn write_script>(path: P, scheme: &Base16Scheme) -> Result<()> { let output = Base16Shell::new(scheme.clone()).build(); - fs::write(path.as_ref(), output)?; - Ok(()) + let path = path.as_ref(); + util::write_file(path, output) } fn new(scheme: Base16Scheme) -> Self { diff --git a/libcitadel/src/terminal/color.rs b/libcitadel/src/terminal/color.rs index bc9e386..83ad5ac 100644 --- a/libcitadel/src/terminal/color.rs +++ b/libcitadel/src/terminal/color.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::Result; +use crate::{Result, Error}; use crate::terminal::AnsiTerminal; #[derive(Copy,Clone,Default,Debug)] @@ -21,7 +21,7 @@ impl Color { return Ok(Color(r, g, b)) } } - Err(format_err!("Cannot parse '{}'", s)) + bail!("Cannot parse '{}'", s) } pub fn rgb(self) -> (u16,u16,u16) { @@ -93,3 +93,9 @@ impl TerminalPalette { } } + +impl From for crate::Error { + fn from(_: std::num::ParseIntError) -> Self { + Error::message("failed to parse integer") + } +} diff --git a/libcitadel/src/terminal/gnome.rs b/libcitadel/src/terminal/gnome.rs index b3f310a..1561eb6 100644 --- a/libcitadel/src/terminal/gnome.rs +++ b/libcitadel/src/terminal/gnome.rs @@ -78,7 +78,7 @@ pub fn spawn_citadel_gnome_terminal(command: Option) pub fn open_citadel_gnome_terminal>(command: Option) -> Result<()> { let mut cmd = build_open_terminal_command(command); - let status = cmd.status()?; + let status = cmd.status().map_err(context!("error running gnome-terminal"))?; info!("Gnome terminal exited with: {}", status); Ok(()) } \ No newline at end of file diff --git a/libcitadel/src/terminal/raw.rs b/libcitadel/src/terminal/raw.rs index e724263..95446e8 100644 --- a/libcitadel/src/terminal/raw.rs +++ b/libcitadel/src/terminal/raw.rs @@ -1,31 +1,31 @@ use std::mem; use std::io::{self,Write}; -use libc::c_int; pub use libc::termios as Termios; +use libc::c_int; use crate::Result; -fn get_terminal_attr() -> io::Result { +fn get_terminal_attr() -> Result { extern "C" { pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int; } unsafe { let mut termios = mem::zeroed(); if tcgetattr(0, &mut termios) == -1 { - return Err(io::Error::last_os_error()) + bail!("error calling tcgetattr() on terminal: {}", io::Error::last_os_error()); } Ok(termios) } } -fn set_terminal_attr(termios: &Termios) -> io::Result<()> { +fn set_terminal_attr(termios: &Termios) -> Result<()> { extern "C" { pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int; } unsafe { if tcsetattr(0, 0, termios) == -1 { - return Err(io::Error::last_os_error()) + bail!("error calling tcsetattr() on terminal: {}", io::Error::last_os_error()); } Ok(()) } diff --git a/libcitadel/src/terminal/restorer.rs b/libcitadel/src/terminal/restorer.rs index 176cd35..763d6cf 100644 --- a/libcitadel/src/terminal/restorer.rs +++ b/libcitadel/src/terminal/restorer.rs @@ -54,19 +54,20 @@ impl TerminalRestorer { let mut t = self.terminal()?; let mut palette = TerminalPalette::default(); palette.load(&mut t) - .map_err(|e| format_err!("error reading palette colors from terminal: {}", e))?; + .map_err(context!("error reading palette colors from terminal"))?; Ok(palette) } fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> { let mut t = self.terminal()?; palette.apply(&mut t) - .map_err(|e| format_err!("error setting palette on terminal: {}", e)) + .map_err(context!("error setting palette on terminal"))?; + Ok(()) } fn terminal(&self) -> Result { AnsiTerminal::new() - .map_err(|e| format_err!("failed to create AnsiTerminal: {}", e)) + .map_err(context!("failed to create AnsiTerminal")) } pub fn apply_base16_by_slug>(&self, slug: S) { @@ -84,11 +85,10 @@ impl TerminalRestorer { fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> { let mut t = self.terminal()?; t.apply_base16(scheme) - .map_err(|e| format_err!("error setting base16 palette colors: {}", e))?; + .map_err(context!("error setting base16 palette colors"))?; t.clear_screen() - .map_err(|e| format_err!("error clearing screen: {}", e)) + .map_err(context!("error clearing screen")) } - } impl Drop for TerminalRestorer { diff --git a/libcitadel/src/util.rs b/libcitadel/src/util.rs index ac75993..733d12c 100644 --- a/libcitadel/src/util.rs +++ b/libcitadel/src/util.rs @@ -2,16 +2,16 @@ use std::path::{Path,PathBuf}; use std::process::{Command,Stdio}; use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::MetadataExt; +use std::os::unix::fs as unixfs; use std::env; -use std::fs::{self,File}; +use std::fs::{self, File, DirEntry}; use std::ffi::CString; use std::io::{self, Seek, Read, BufReader, SeekFrom}; -use failure::ResultExt; use walkdir::WalkDir; use libc; -use crate::Result; +use crate::{Result, util}; pub fn is_valid_name(name: &str, maxsize: usize) -> bool { name.len() <= maxsize && @@ -36,14 +36,14 @@ pub fn is_first_char_alphabetic(s: &str) -> bool { } fn search_path(filename: &str) -> Result { - let path_var = env::var("PATH")?; + let path_var = env::var("PATH").unwrap_or("".into()); for mut path in env::split_paths(&path_var) { path.push(filename); if path.exists() { return Ok(path); } } - Err(format_err!("Could not find {} in $PATH", filename)) + bail!("could not find {} in $PATH", filename) } pub fn ensure_command_exists(cmd: &str) -> Result<()> { @@ -54,14 +54,13 @@ pub fn ensure_command_exists(cmd: &str) -> Result<()> { } else if path.exists() { return Ok(()) } - Err(format_err!("Cannot execute '{}': command does not exist", cmd)) + bail!("cannot execute '{}': command does not exist", cmd) } - pub fn sha256>(path: P) -> Result { let path = path.as_ref(); let output = cmd_with_output!("/usr/bin/sha256sum", "{}", path.display()) - .context(format!("failed to calculate sha256 on {}", path.display()))?; + .map_err(context!("failed to calculate sha256 on {:?}", path))?; let v: Vec<&str> = output.split_whitespace().collect(); Ok(v[0].trim().to_owned()) @@ -75,14 +74,17 @@ pub enum FileRange { } fn ranged_reader>(path: P, range: FileRange) -> Result> { - let mut f = File::open(path.as_ref())?; + let path = path.as_ref(); + let mut f = File::open(path) + .map_err(context!("error opening input file {:?}", path))?; let offset = match range { FileRange::All => 0, FileRange::Offset(n) => n, FileRange::Range {offset, .. } => offset, }; if offset > 0 { - f.seek(SeekFrom::Start(offset as u64))?; + f.seek(SeekFrom::Start(offset as u64)) + .map_err(context!("error seeking to offset {} in input file {:?}", offset, path))?; } let r = BufReader::new(f); if let FileRange::Range {len, ..} = range { @@ -107,26 +109,26 @@ pub fn exec_cmdline_pipe_input(cmd_path: &str, args: S, input: P, range: Fi .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .spawn() - .context(format!("unable to execute {}", cmd_path))?; + .map_err(context!("unable to execute {}", cmd_path))?; let stdin = child.stdin.as_mut().unwrap(); - io::copy(&mut r, stdin)?; - let output = child.wait_with_output()?; + io::copy(&mut r, stdin) + .map_err(context!("error copying input to stdin"))?; + let output = child.wait_with_output() + .map_err(context!("error waiting for command {} to exit", cmd_path))?; Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned()) } pub fn xz_compress>(path: P) -> Result<()> { let path = path.as_ref(); cmd!("/usr/bin/xz", "-T0 {}", path.display()) - .context(format!("failed to compress {}", path.display()))?; - Ok(()) + .map_err(context!("failed to compress {:?}", path)) } pub fn xz_decompress>(path: P) -> Result<()> { let path = path.as_ref(); cmd!("/usr/bin/xz", "-d {}", path.display()) - .context(format!("failed to decompress {}", path.display()))?; - Ok(()) + .map_err(context!("failed to decompress {:?}", path)) } pub fn mount>(source: impl AsRef, target: P, options: Option<&str>) -> Result<()> { @@ -136,39 +138,145 @@ pub fn mount>(source: impl AsRef, target: P, options: Option cmd!("/usr/bin/mount", "{} {} {}", options, source, target.display()) } else { cmd!("/usr/bin/mount", "{} {}", source, target.display()) - } + }.map_err(context!("failed to mount {} to {:?}", source, target)) } pub fn umount>(path: P) -> Result<()> { let path = path.as_ref(); cmd!("/usr/bin/umount", "{}", path.display()) + .map_err(context!("failed to unmount {:?}", path)) } -pub fn chown_user>(path: P) -> io::Result<()> { +pub fn chown_user>(path: P) -> Result<()> { chown(path.as_ref(), 1000, 1000) } -pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { - let cstr = CString::new(path.as_os_str().as_bytes())?; +pub fn chown(path: &Path, uid: u32, gid: u32) -> Result<()> { + let cstr = CString::new(path.as_os_str().as_bytes()) + .expect("path contains null byte"); unsafe { if libc::chown(cstr.as_ptr(), uid, gid) == -1 { - return Err(io::Error::last_os_error()); + let err = io::Error::last_os_error(); + bail!("failed to chown({},{}) {:?}: {}", uid, gid, path, err); } } Ok(()) } +/// Rename or move file at `from` to file path `to` +/// +/// A wrapper around `fs::rename()` which on failure returns an error indicating the source and +/// destination paths. +/// +pub fn rename(from: impl AsRef, to: impl AsRef) -> Result<()> { + let from = from.as_ref(); + let to = to.as_ref(); + fs::rename(from, to) + .map_err(context!("error renaming {:?} to {:?}", from, to)) +} + +/// Create a symlink at path `dst` which points to `src` +/// +/// A wrapper around `fs::symlink()` which on failure returns an error indicating the source and +/// destination paths. +/// +pub fn symlink(src: impl AsRef, dst: impl AsRef) -> Result<()> { + let src = src.as_ref(); + let dst = dst.as_ref(); + unixfs::symlink(src, dst) + .map_err(context!("failed to create symlink {:?} to {:?}", dst, src)) +} + +/// Read directory `dir` and call closure `f` on each `DirEntry` +pub fn read_directory(dir: impl AsRef, mut f: F) -> Result<()> +where + F: FnMut(&DirEntry) -> Result<()> +{ + let dir = dir.as_ref(); + let entries = fs::read_dir(dir) + .map_err(context!("failed to read directory {:?}", dir))?; + for dent in entries { + let dent = dent.map_err(context!("error reading entry from directory {:?}", dir))?; + f(&dent)?; + } + Ok(()) +} + +/// Remove file at `path` if it exists. +/// +/// A wrapper around `fs::remove_file()` which on failure returns an error indicating the path of +/// the file which failed to be removed. +/// +pub fn remove_file(path: impl AsRef) -> Result<()> { + let path = path.as_ref(); + if path.exists() { + fs::remove_file(path) + .map_err(context!("failed to remove file {:?}", path))?; + } + Ok(()) +} + +/// Create directory `path` if it does not already exist. +/// +/// A wrapper around `fs::create_dir_all()` which on failure returns an error indicating the path +/// of the directory which failed to be created. +/// +pub fn create_dir(path: impl AsRef) -> Result<()> { + let path = path.as_ref(); + if !path.exists() { + fs::create_dir_all(path) + .map_err(context!("failed to create directory {:?}", path))?; + } + Ok(()) +} + +/// Write `contents` to file `path` +/// +/// A wrapper around `fs::write()` which on failure returns an error indicating the path +/// of the file which failed to be written. +/// +pub fn write_file(path: impl AsRef, contents: impl AsRef<[u8]>) -> Result<()> { + let path = path.as_ref(); + fs::write(path, contents) + .map_err(context!("failed to write to file {:?}", path)) +} + +/// Read content of file `path` into a `String` +/// +/// A wrapper around `fs::read_to_string()` which on failure returns an error indicating the path +/// of the file which failed to be read. +/// +pub fn read_to_string(path: impl AsRef) -> Result { + let path = path.as_ref(); + fs::read_to_string(path) + .map_err(context!("failed to read file {:?}", path)) +} + +/// Copy file at path `from` to a new file at path `to` +/// +/// A wrapper around `fs::copy()` which on failure returns an error indicating the source and +/// destination paths. +/// +pub fn copy_file(from: impl AsRef, to: impl AsRef) -> Result<()> { + let from = from.as_ref(); + let to = to.as_ref(); + fs::copy(from, to) + .map_err(context!("failed to copy file {:?} to {:?}", from, to))?; + Ok(()) +} + fn copy_path(from: &Path, to: &Path, chown_to: Option<(u32,u32)>) -> Result<()> { if to.exists() { bail!("destination path {} already exists which is not expected", to.display()); } - let meta = from.metadata()?; + let meta = from.metadata() + .map_err(context!("failed to read metadata from source file {:?}", from))?; if from.is_dir() { - fs::create_dir(to)?; + util::create_dir(to)?; } else { - fs::copy(&from, &to)?; + util::copy_file(&from, &to)?; } if let Some((uid,gid)) = chown_to { @@ -190,11 +298,14 @@ pub fn copy_tree_with_chown(from_base: &Path, to_base: &Path, chown_to: (u32,u32 fn _copy_tree(from_base: &Path, to_base: &Path, chown_to: Option<(u32,u32)>) -> Result<()> { for entry in WalkDir::new(from_base) { - let path = entry?.path().to_owned(); - let to = to_base.join(path.strip_prefix(from_base)?); - if to != to_base { - copy_path(&path, &to, chown_to) - .map_err(|e| format_err!("failed to copy {} to {}: {}", path.display(), to.display(), e))?; + let entry = entry.map_err(|e| format_err!("Error walking directory tree: {}", e))?; + let path = entry.path(); + let suffix = path.strip_prefix(from_base) + .map_err(|_| format_err!("Failed to strip prefix from {:?}", path))?; + let to = to_base.join(suffix); + if &to != to_base { + copy_path(path, &to, chown_to) + .map_err(context!("failed to copy {:?} to {:?}", path, to))?; } } Ok(()) @@ -202,9 +313,10 @@ fn _copy_tree(from_base: &Path, to_base: &Path, chown_to: Option<(u32,u32)>) -> pub fn chown_tree(base: &Path, chown_to: (u32,u32), include_base: bool) -> Result<()> { for entry in WalkDir::new(base) { - let entry = entry?; + let entry = entry.map_err(|e| format_err!("Error reading directory entry: {}", e))?; if entry.path() != base || include_base { - chown(entry.path(), chown_to.0, chown_to.1)?; + chown(entry.path(), chown_to.0, chown_to.1) + .map_err(context!("failed to chown {:?}", entry.path()))?; } } Ok(()) diff --git a/libcitadel/src/verity.rs b/libcitadel/src/verity.rs index 6280f1b..1980f25 100644 --- a/libcitadel/src/verity.rs +++ b/libcitadel/src/verity.rs @@ -1,9 +1,9 @@ use std::path::{Path,PathBuf}; use std::collections::HashMap; -use std::fs::{self, OpenOptions,File}; +use std::fs::{OpenOptions,File}; use std::io; -use crate::{Result, MetaInfo, Partition, LoopDevice, ImageHeader}; +use crate::{Result, MetaInfo, Partition, LoopDevice, ImageHeader, util}; use std::sync::Arc; @@ -42,21 +42,25 @@ impl Verity { let verityfile = self.image.with_extension("verity"); // Make sure file size is correct or else verity tree will be appended in wrong place - let meta = self.image.metadata()?; + let meta = self.image.metadata() + .map_err(context!("failed to read metadata from image file {:?}", self.image))?; let len = meta.len() as usize; let expected = (nblocks + 1) * 4096; if len != expected { - bail!("Actual file size ({}) does not match expected size ({})", len, expected); + bail!("actual file size ({}) does not match expected size ({})", len, expected); } let vout = LoopDevice::with_loop(self.path(), Some(4096), true, |loopdev| { let output = cmd_with_output!(Self::VERITYSETUP, "--data-blocks={} --salt={} format {} {}", nblocks, salt, loopdev, verityfile.display())?; Ok(VerityOutput::parse(&output)) })?; - let mut input = File::open(&verityfile)?; - let mut output = OpenOptions::new().append(true).open(self.path())?; - io::copy(&mut input, &mut output)?; - fs::remove_file(&verityfile)?; + let mut input = File::open(&verityfile) + .map_err(context!("failed to open temporary verity hashtree file {:?}", verityfile))?; + let mut output = OpenOptions::new().append(true).open(self.path()) + .map_err(context!("failed to open image file {:?}", self.path()))?; + io::copy(&mut input, &mut output) + .map_err(context!("i/o error copying verity hashtree to image file"))?; + util::remove_file(&verityfile)?; Ok(vout) } @@ -104,9 +108,7 @@ impl Verity { let nblocks = metainfo.nblocks(); let verity_root = metainfo.verity_root(); cmd!(Self::VERITYSETUP, "--hash-offset={} --data-blocks={} create {} {} {} {}", - nblocks * 4096, nblocks, devname, srcdev, srcdev, verity_root)?; - - Ok(()) + nblocks * 4096, nblocks, devname, srcdev, srcdev, verity_root) } fn path(&self) -> &Path {