1
0
forked from brl/citadel-tools

Refactor of error handling to replace 'failure' and to display more context for some errors.

This commit is contained in:
Bruce Leidl 2020-07-02 09:34:09 -04:00
parent 9c77af6eff
commit c9d36aca59
64 changed files with 954 additions and 751 deletions

2
Cargo.lock generated
View File

@ -178,7 +178,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libcitadel 0.1.0", "libcitadel 0.1.0",
@ -635,7 +634,6 @@ dependencies = [
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -207,7 +207,7 @@ impl ConfigDialog {
let path = self.realm.base_path_file("config"); 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); warn!("Error writing config file {}: {}", path.display(), e);
} }
info!("Config file written to {}", path.display()); info!("Config file written to {}", path.display());

View File

@ -54,19 +54,19 @@ impl TerminalTools {
let mut t = self.terminal()?; let mut t = self.terminal()?;
let mut palette = TerminalPalette::default(); let mut palette = TerminalPalette::default();
palette.load(&mut t) 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) Ok(palette)
} }
fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> { fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> {
let mut t = self.terminal()?; let mut t = self.terminal()?;
palette.apply(&mut t) 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> { fn terminal(&self) -> Result<AnsiTerminal> {
AnsiTerminal::new() 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<S: AsRef<str>>(&self, slug: S) { pub fn apply_base16_by_slug<S: AsRef<str>>(&self, slug: S) {
@ -84,9 +84,9 @@ impl TerminalTools {
fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> { fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> {
let mut t = self.terminal()?; let mut t = self.terminal()?;
t.apply_base16(scheme) 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() t.clear_screen()
.map_err(|e| format_err!("error clearing screen: {}", e)) .map_err(context!("error clearing screen"))
} }
} }

View File

@ -6,7 +6,6 @@ edition = "2018"
[dependencies] [dependencies]
libcitadel = { path = "../libcitadel" } libcitadel = { path = "../libcitadel" }
failure = "0.1"
rpassword = "4.0" rpassword = "4.0"
clap = "2.33" clap = "2.33"
lazy_static = "1.4" lazy_static = "1.4"

View File

@ -1,7 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::fs;
use libcitadel::Result; use libcitadel::{Result, util};
/// ///
/// Represents a disk partition device on the system /// 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<DiskPartition>` /// Return list of all vfat partitions on the system as a `Vec<DiskPartition>`
pub fn boot_partitions(check_guid: bool) -> Result<Vec<DiskPartition>> { pub fn boot_partitions(check_guid: bool) -> Result<Vec<DiskPartition>> {
let pp = fs::read_to_string("/proc/partitions")?; let pp = util::read_to_string("/proc/partitions")?;
let mut v = Vec::new(); let mut v = Vec::new();
for line in pp.lines().skip(2) for line in pp.lines().skip(2)
{ {
let part = DiskPartition::from_proc_line(&line) 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)? { if part.is_boot_partition(check_guid)? {
v.push(part); v.push(part);

View File

@ -1,12 +1,12 @@
use std::path::Path; use std::path::Path;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs; use std::fs;
use std::thread::{self,JoinHandle}; use std::thread::{self,JoinHandle};
use std::time::{self,Instant}; use std::time::{self,Instant};
use libcitadel::{Result, UtsName}; use libcitadel::{Result, UtsName, util};
use libcitadel::ResourceImage; use libcitadel::ResourceImage;
use crate::boot::disks; use crate::boot::disks;
use crate::boot::rootfs::setup_rootfs_resource; use crate::boot::rootfs::setup_rootfs_resource;
use crate::install::installer::Installer; 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"); info!("Failed to find partition with images, trying again in 2 seconds");
thread::sleep(time::Duration::from_secs(2)); 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<()> { fn deploy_artifacts() -> Result<()> {
let run_images = Path::new(IMAGE_DIRECTORY); let run_images = Path::new(IMAGE_DIRECTORY);
if !run_images.exists() { 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")?; cmd!("/bin/mount", "-t tmpfs -o size=4g images /run/citadel/images")?;
} }
for entry in fs::read_dir("/boot/images")? { util::read_directory("/boot/images", |dent| {
let entry = entry?; println!("Copying {:?} from /boot/images to /run/citadel/images", dent.file_name());
println!("Copying {:?} from /boot/images to /run/citadel/images", entry.file_name()); util::copy_file(dent.path(), run_images.join(dent.file_name()))
fs::copy(entry.path(), run_images.join(entry.file_name()))?; })?;
}
let kv = kernel_version(); let kv = kernel_version();
println!("Copying bzImage-{} to /run/citadel/images", kv); println!("Copying bzImage-{} to /run/citadel/images", kv);
let from = format!("/boot/bzImage-{}", kv); let from = format!("/boot/bzImage-{}", kv);
let to = format!("/run/citadel/images/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"); 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()?; deploy_syslinux_artifacts()?;
@ -104,21 +103,23 @@ fn deploy_syslinux_artifacts() -> Result<()> {
println!("Copying contents of /boot/syslinux to /run/citadel/images/syslinux"); println!("Copying contents of /boot/syslinux to /run/citadel/images/syslinux");
let run_images_syslinux = Path::new("/run/citadel/images/syslinux"); let run_images_syslinux = Path::new("/run/citadel/images/syslinux");
fs::create_dir_all(run_images_syslinux)?; util::create_dir(run_images_syslinux)?;
for entry in fs::read_dir(boot_syslinux)? {
let entry = entry?; util::read_directory(boot_syslinux, |dent| {
if let Some(ext) = entry.path().extension() { if let Some(ext) = dent.path().extension() {
if ext == "c32" || ext == "bin" { 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<ResourceImage> { fn find_rootfs_image() -> Result<ResourceImage> {
for entry in fs::read_dir(IMAGE_DIRECTORY)? { let entries = fs::read_dir(IMAGE_DIRECTORY)
let entry = entry?; .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 entry.path().extension() == Some(OsStr::new("img")) {
if let Ok(image) = ResourceImage::from_path(&entry.path()) { if let Ok(image) = ResourceImage::from_path(&entry.path()) {
if image.metainfo().image_type() == "rootfs" { if image.metainfo().image_type() == "rootfs" {
@ -127,23 +128,23 @@ fn find_rootfs_image() -> Result<ResourceImage> {
} }
} }
} }
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<()> { fn decompress_images() -> Result<()> {
info!("Decompressing images"); info!("Decompressing images");
let mut threads = Vec::new(); let mut threads = Vec::new();
for entry in fs::read_dir("/run/citadel/images")? { util::read_directory("/run/citadel/images", |dent| {
let entry = entry?; if dent.path().extension() == Some(OsStr::new("img")) {
if entry.path().extension() == Some(OsStr::new("img")) { if let Ok(image) = ResourceImage::from_path(&dent.path()) {
if let Ok(image) = ResourceImage::from_path(&entry.path()) {
if image.is_compressed() { if image.is_compressed() {
threads.push(decompress_one_image(image)); threads.push(decompress_one_image(image));
} }
} }
} }
} Ok(())
})?;
for t in threads { for t in threads {
t.join().unwrap()?; t.join().unwrap()?;
} }

View File

@ -1,7 +1,7 @@
use std::fs; use std::fs;
use std::process::exit; 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 libcitadel::RealmManager;
use crate::boot::disks::DiskPartition; use crate::boot::disks::DiskPartition;
use std::path::Path; use std::path::Path;
@ -21,11 +21,11 @@ pub fn main(args: Vec<String>) {
Some(s) if s == "rootfs" => do_rootfs(), Some(s) if s == "rootfs" => do_rootfs(),
Some(s) if s == "setup" => do_setup(), Some(s) if s == "setup" => do_setup(),
Some(s) if s == "start-realms" => do_start_realms(), 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 { if let Err(ref e) = result {
warn!("Failed: {}", format_error(e)); warn!("Failed: {}", e);
exit(1); exit(1);
} }
} }
@ -69,21 +69,21 @@ fn mount_overlay() -> Result<()> {
info!("Creating rootfs overlay"); info!("Creating rootfs overlay");
info!("Moving /sysroot mount to /rootfs.ro"); 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", "--make-private /")?;
cmd!("/usr/bin/mount", "--move /sysroot /rootfs.ro")?; cmd!("/usr/bin/mount", "--move /sysroot /rootfs.ro")?;
info!("Mounting tmpfs on /rootfs.rw"); 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")?; cmd!("/usr/bin/mount", "-t tmpfs -orw,noatime,mode=755 rootfs.rw /rootfs.rw")?;
info!("Creating /rootfs.rw/work /rootfs.rw/upperdir"); info!("Creating /rootfs.rw/work /rootfs.rw/upperdir");
fs::create_dir_all("/rootfs.rw/upperdir")?; util::create_dir("/rootfs.rw/upperdir")?;
fs::create_dir_all("/rootfs.rw/work")?; util::create_dir("/rootfs.rw/work")?;
info!("Mounting overlay on /sysroot"); info!("Mounting overlay on /sysroot");
cmd!("/usr/bin/mount", "-t overlay overlay -olowerdir=/rootfs.ro,upperdir=/rootfs.rw/upperdir,workdir=/rootfs.rw/work /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"); info!("Moving /rootfs.ro and /rootfs.rw to new root");
fs::create_dir_all("/sysroot/rootfs.ro")?; util::create_dir("/sysroot/rootfs.ro")?;
fs::create_dir_all("/sysroot/rootfs.rw")?; util::create_dir("/sysroot/rootfs.rw")?;
cmd!("/usr/bin/mount", "--move /rootfs.ro /sysroot/rootfs.ro")?; cmd!("/usr/bin/mount", "--move /rootfs.ro /sysroot/rootfs.ro")?;
cmd!("/usr/bin/mount", "--move /rootfs.rw /sysroot/rootfs.rw")?; cmd!("/usr/bin/mount", "--move /rootfs.rw /sysroot/rootfs.rw")?;
Ok(()) Ok(())
@ -134,7 +134,8 @@ const LOADER_EFI_VAR_PATH: &str =
fn read_loader_dev_efi_var() -> Result<Option<String>> { fn read_loader_dev_efi_var() -> Result<Option<String>> {
let efi_var = Path::new(LOADER_EFI_VAR_PATH); let efi_var = Path::new(LOADER_EFI_VAR_PATH);
if efi_var.exists() { 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' .into_iter().skip(4) // u32 'attribute'
.filter(|b| *b != 0) // string is utf16 ascii .filter(|b| *b != 0) // string is utf16 ascii
.map(|b| (b as char).to_ascii_lowercase()) .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(); let dev = partition.path().display().to_string();
info!("Writing /boot automount units to /run/systemd/system for {}", dev); info!("Writing /boot automount units to /run/systemd/system for {}", dev);
let mount_unit = BOOT_MOUNT_UNIT.replace("$PARTITION", &dev); let mount_unit = BOOT_MOUNT_UNIT.replace("$PARTITION", &dev);
fs::write("/run/systemd/system/boot.mount", mount_unit)?; util::write_file("/run/systemd/system/boot.mount", &mount_unit)?;
fs::write("/run/systemd/system/boot.automount", BOOT_AUTOMOUNT_UNIT)?; util::write_file("/run/systemd/system/boot.automount", BOOT_AUTOMOUNT_UNIT)?;
info!("Starting /boot automount service"); info!("Starting /boot automount service");
cmd!("/usr/bin/systemctl", "start boot.automount")?; cmd!("/usr/bin/systemctl", "start boot.automount")?;
Ok(()) Ok(())

View File

@ -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 libcitadel::{BlockDev, ResourceImage, CommandLine, ImageHeader, Partition, Result, LoopDevice};
use std::path::Path;
use std::process::Stdio;
use libcitadel::verity::Verity; use libcitadel::verity::Verity;
pub fn setup_rootfs() -> Result<()> { 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"); info!("Creating /dev/mapper/rootfs dm-verity device");
if !CommandLine::nosignatures() { if !CommandLine::nosignatures() {
if !p.has_public_key() { 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() { if !p.is_signature_valid() {
p.write_status(ImageHeader::STATUS_BAD_SIG)?; 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()); info!("Image signature is valid for channel {}", p.metainfo().channel());
} }
@ -71,7 +70,7 @@ fn setup_linear_mapping(blockdev: &Path) -> Result<()> {
.success(); .success();
if !ok { 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(()) Ok(())
} }
@ -89,7 +88,7 @@ fn choose_boot_partiton(scan: bool) -> Result<Partition> {
for p in partitions { for p in partitions {
best = compare_boot_partitions(best, p); 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<Partition>, b: Partition) -> Option<Partition> { fn compare_boot_partitions(a: Option<Partition>, b: Partition) -> Option<Partition> {

View File

@ -3,8 +3,7 @@ use std::process::exit;
use clap::{App,Arg,SubCommand,ArgMatches}; use clap::{App,Arg,SubCommand,ArgMatches};
use clap::AppSettings::*; use clap::AppSettings::*;
use libcitadel::{Result,ResourceImage,Logger,LogLevel,format_error,Partition,KeyPair,ImageHeader}; use libcitadel::{Result, ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
use std::fs;
use hex; use hex;
pub fn main(args: Vec<String>) { pub fn main(args: Vec<String>) {
@ -89,7 +88,7 @@ pub fn main(args: Vec<String>) {
}; };
if let Err(ref e) = result { if let Err(ref e) = result {
println!("Error: {}", format_error(e)); println!("Error: {}", e);
exit(1); exit(1);
} }
} }
@ -252,8 +251,7 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> {
if image_dest.exists() { if image_dest.exists() {
rotate(&image_dest)?; rotate(&image_dest)?;
} }
fs::rename(source,image_dest)?; util::rename(source, &image_dest)
Ok(())
} }
fn rotate(path: &Path) -> Result<()> { fn rotate(path: &Path) -> Result<()> {
@ -262,11 +260,8 @@ fn rotate(path: &Path) -> Result<()> {
} }
let filename = path.file_name().unwrap(); let filename = path.file_name().unwrap();
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy())); let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
if dot_zero.exists() { util::remove_file(&dot_zero)?;
fs::remove_file(&dot_zero)?; util::rename(path, &dot_zero)
}
fs::rename(path, &dot_zero)?;
Ok(())
} }
fn genkeys() -> Result<()> { fn genkeys() -> Result<()> {
@ -334,5 +329,5 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
return Ok(p.clone()) return Ok(p.clone())
} }
} }
Err(format_err!("No suitable install partition found")) bail!("No suitable install partition found")
} }

View File

@ -13,7 +13,7 @@ pub fn run_cli_install() -> Result<bool> {
display_disk(&disk); display_disk(&disk);
let passphrase = match read_passphrase()? { let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? {
Some(passphrase) => passphrase, Some(passphrase) => passphrase,
None => return Ok(false), None => return Ok(false),
}; };
@ -29,7 +29,7 @@ pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
let disk = find_disk_by_path(target.as_ref())?; let disk = find_disk_by_path(target.as_ref())?;
display_disk(&disk); display_disk(&disk);
let passphrase = match read_passphrase()? { let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? {
Some(passphrase) => passphrase, Some(passphrase) => passphrase,
None => return Ok(false), None => return Ok(false),
}; };
@ -66,17 +66,17 @@ fn find_disk_by_path(path: &Path) -> Result<Disk> {
return Ok(disk.clone()); 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<Option<Disk>> { fn choose_disk() -> Result<Option<Disk>> {
let disks = Disk::probe_all()?; let disks = Disk::probe_all()?;
if disks.is_empty() { if disks.is_empty() {
bail!("No disks found."); bail!("no disks found.");
} }
loop { loop {
prompt_choose_disk(&disks)?; prompt_choose_disk(&disks);
let line = read_line()?; let line = read_line()?;
if line == "q" || line == "Q" { if line == "q" || line == "Q" {
return Ok(None); return Ok(None);
@ -89,26 +89,26 @@ fn choose_disk() -> Result<Option<Disk>> {
} }
} }
fn prompt_choose_disk(disks: &[Disk]) -> Result<()> { fn prompt_choose_disk(disks: &[Disk]) {
println!("Available disks:\n"); println!("Available disks:\n");
for (idx,disk) in disks.iter().enumerate() { for (idx,disk) in disks.iter().enumerate() {
println!(" [{}]: {} Size: {} Model: {}", idx + 1, disk.path().display(), disk.size_str(), disk.model()); println!(" [{}]: {} Size: {} Model: {}", idx + 1, disk.path().display(), disk.size_str(), disk.model());
} }
print!("\nChoose a disk to install to (q to quit): "); print!("\nChoose a disk to install to (q to quit): ");
io::stdout().flush()?; let _ = io::stdout().flush();
Ok(())
} }
fn read_line() -> Result<String> { fn read_line() -> Result<String> {
let mut input = String::new(); 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') { if input.ends_with('\n') {
input.pop(); input.pop();
} }
Ok(input) Ok(input)
} }
fn read_passphrase() -> Result<Option<String>> { fn read_passphrase() -> io::Result<Option<String>> {
loop { loop {
println!("Enter a disk encryption passphrase (or 'q' to quit)"); println!("Enter a disk encryption passphrase (or 'q' to quit)");
println!(); println!();
@ -141,7 +141,7 @@ fn confirm_install(disk: &Disk) -> Result<bool> {
println!(" Model: {}", disk.model()); println!(" Model: {}", disk.model());
println!(); println!();
print!("Type YES (uppercase) to continue with install: "); print!("Type YES (uppercase) to continue with install: ");
io::stdout().flush()?; let _ = io::stdout().flush();
let answer = read_line()?; let answer = read_line()?;
Ok(answer == "YES") Ok(answer == "YES")
} }

View File

@ -1,7 +1,7 @@
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use std::fs; use std::fs;
use libcitadel::Result; use libcitadel::{Result, util};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -15,13 +15,15 @@ pub struct Disk {
impl Disk { impl Disk {
pub fn probe_all() -> Result<Vec<Disk>> { pub fn probe_all() -> Result<Vec<Disk>> {
let mut v = Vec::new(); let mut v = Vec::new();
for entry in fs::read_dir("/sys/block")? { util::read_directory("/sys/block", |dent| {
let path = entry?.path(); let path = dent.path();
if Disk::is_disk_device(&path) { if Disk::is_disk_device(&path) {
let disk = Disk::read_device(&path)?; let disk = Disk::read_device(&path)?;
v.push(disk); v.push(disk);
} }
} Ok(())
})?;
Ok(v) Ok(v)
} }
@ -32,18 +34,20 @@ impl Disk {
fn read_device(device: &Path) -> Result<Disk> { fn read_device(device: &Path) -> Result<Disk> {
let path = Path::new("/dev/").join(device.file_name().unwrap()); 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() .trim()
.parse::<usize>()?; .parse::<usize>()
.map_err(context!("error parsing device size for {:?}", device))?;
let size_str = format!("{}G", size >> 21); 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() .trim()
.to_string(); .to_string();
Ok(Disk { path, size, size_str, model }) Ok(Disk { path, size, size_str, model })
} }
pub fn path(&self) -> &Path { pub fn path(&self) -> &Path {
@ -57,5 +61,4 @@ impl Disk {
pub fn model(&self) -> &str { pub fn model(&self) -> &str {
&self.model &self.model
} }
} }

View File

@ -1,7 +1,6 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fs::{self,File}; use std::fs::{self,File};
use std::io::{self,Write}; use std::io::{self,Write};
use std::os::unix::fs as unixfs;
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
@ -183,12 +182,12 @@ impl Installer {
]; ];
if !self.target().exists() { 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 { for a in artifacts {
if !self.artifact_path(a).exists() { 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", "/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")?; 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") { if cmdline.contains("citadel.live") {
self.setup_live_realm()?; self.setup_live_realm()?;
} }
@ -238,10 +238,10 @@ impl Installer {
let base_realmfs = realmfs_dir.join("base-realmfs.img"); let base_realmfs = realmfs_dir.join("base-realmfs.img");
self.info(format!("creating directory {}", realmfs_dir.display()))?; 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"))?; 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")?; let realmfs = RealmFS::load_from_path("/run/citadel/images/base-realmfs.img")?;
realmfs.activate()?; realmfs.activate()?;
@ -260,8 +260,9 @@ impl Installer {
fn setup_luks(&self) -> Result<()> { fn setup_luks(&self) -> Result<()> {
self.header("Setting up LUKS disk encryption")?; self.header("Setting up LUKS disk encryption")?;
fs::create_dir_all(INSTALL_MOUNT)?; util::create_dir(INSTALL_MOUNT)?;
fs::write(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?; util::write_file(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?;
let luks_partition = self.target_partition(2); let luks_partition = self.target_partition(2);
self.cmd_list(LUKS_COMMANDS, &[ self.cmd_list(LUKS_COMMANDS, &[
@ -270,8 +271,7 @@ impl Installer {
("$LUKS_PASSFILE", LUKS_PASSPHRASE_FILE), ("$LUKS_PASSFILE", LUKS_PASSPHRASE_FILE),
])?; ])?;
fs::remove_file(LUKS_PASSPHRASE_FILE)?; util::remove_file(LUKS_PASSPHRASE_FILE)
Ok(())
} }
fn setup_lvm(&self) -> Result<()> { fn setup_lvm(&self) -> Result<()> {
@ -286,17 +286,16 @@ impl Installer {
self.cmd(format!("/bin/mount {} {}", boot_partition, INSTALL_MOUNT))?; 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")?; 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(); let kernel_version = self.kernel_version();
self.info("Writing /boot/entries/boot.conf")?; 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_CMDLINE", KERNEL_CMDLINE)
.replace("$KERNEL_VERSION", &kernel_version) .replace("$KERNEL_VERSION", &kernel_version))?;
)?;
let kernel_bzimage = format!("bzImage-{}", kernel_version); let kernel_bzimage = format!("bzImage-{}", kernel_version);
self.copy_artifact(&kernel_bzimage, INSTALL_MOUNT)?; self.copy_artifact(&kernel_bzimage, INSTALL_MOUNT)?;
@ -318,26 +317,26 @@ impl Installer {
self.header("Installing syslinux")?; self.header("Installing syslinux")?;
let syslinux_src = self.artifact_path("syslinux"); let syslinux_src = self.artifact_path("syslinux");
if !syslinux_src.exists() { 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"); 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")?; self.info("Copying syslinux files to /boot/syslinux")?;
for entry in fs::read_dir(&syslinux_src)? { util::read_directory(&syslinux_src, |dent| {
let entry = entry?; util::copy_file(dent.path(), dst.join(dent.file_name()))
fs::copy(entry.path(), dst.join(entry.file_name()))?; })?;
}
self.info("Writing syslinux.cfg")?; 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))?; SYSLINUX_CONF.replace("$KERNEL_CMDLINE", KERNEL_CMDLINE))?;
self.cmd(format!("/sbin/extlinux --install {}", dst.display()))?; self.cmd(format!("/sbin/extlinux --install {}", dst.display()))
Ok(())
} }
fn setup_syslinux_post_umount(&self) -> Result<()> { fn setup_syslinux_post_umount(&self) -> Result<()> {
let mbrbin = self.artifact_path("syslinux/gptmbr.bin"); let mbrbin = self.artifact_path("syslinux/gptmbr.bin");
if !mbrbin.exists() { 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!("/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())) self.cmd(format!("/sbin/parted -s {} set 1 legacy_boot on", self.target_str()))
@ -351,8 +350,7 @@ impl Installer {
&[("$INSTALL_MOUNT", INSTALL_MOUNT)])?; &[("$INSTALL_MOUNT", INSTALL_MOUNT)])?;
self.setup_storage()?; self.setup_storage()?;
self.cmd(format!("/bin/umount {}", INSTALL_MOUNT))?; self.cmd(format!("/bin/umount {}", INSTALL_MOUNT))
Ok(())
} }
fn setup_storage(&self) -> Result<()> { fn setup_storage(&self) -> Result<()> {
@ -367,12 +365,12 @@ impl Installer {
self.setup_apt_cacher_realm()?; self.setup_apt_cacher_realm()?;
self.info("Creating global realm config file")?; 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")?; self.info("Creating /Shared realms directory")?;
let shared = self.storage().join("realms/Shared"); let shared = self.storage().join("realms/Shared");
fs::create_dir_all(&shared)?; util::create_dir(&shared)?;
util::chown_user(&shared)?; util::chown_user(&shared)?;
Ok(()) Ok(())
@ -386,17 +384,20 @@ impl Installer {
fn setup_base_realmfs(&self) -> Result<()> { fn setup_base_realmfs(&self) -> Result<()> {
let realmfs_dir = self.storage().join("realms/realmfs-images"); 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.sparse_copy_artifact("base-realmfs.img", &realmfs_dir)?;
self.cmd(format!("/usr/bin/citadel-image decompress {}/base-realmfs.img", realmfs_dir.display()))?; self.cmd(format!("/usr/bin/citadel-image decompress {}/base-realmfs.img", realmfs_dir.display()))
Ok(())
} }
fn setup_realm_skel(&self) -> Result<()> { fn setup_realm_skel(&self) -> Result<()> {
let realm_skel = self.storage().join("realms/skel"); let realm_skel = self.storage().join("realms/skel");
fs::create_dir_all(&realm_skel)?; util::create_dir(&realm_skel)?;
util::copy_tree_with_chown(&self.skel(), &realm_skel, (1000,1000))?; 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(()) Ok(())
} }
@ -407,7 +408,7 @@ impl Installer {
self.info("Creating home directory /realms/realm-main/home")?; self.info("Creating home directory /realms/realm-main/home")?;
let home = realm.join("home"); let home = realm.join("home");
fs::create_dir_all(&home)?; util::create_dir(&home)?;
util::chown_user(&home)?; util::chown_user(&home)?;
self.info("Copying /realms/skel into home diectory")?; self.info("Copying /realms/skel into home diectory")?;
@ -415,16 +416,14 @@ impl Installer {
if let Some(scheme) = Base16Scheme::by_name(MAIN_TERMINAL_SCHEME) { if let Some(scheme) = Base16Scheme::by_name(MAIN_TERMINAL_SCHEME) {
scheme.write_realm_files(&home)?; 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)?; util::chown_tree(&home, (1000,1000), false)?;
self.info("Creating default.realm symlink")?; 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"))?; self.create_realmlock(&realm)
Ok(())
} }
fn setup_apt_cacher_realm(&self) -> Result<()> { fn setup_apt_cacher_realm(&self) -> Result<()> {
@ -433,19 +432,18 @@ impl Installer {
self.info("Creating home directory /realms/realm-apt-cacher/home")?; self.info("Creating home directory /realms/realm-apt-cacher/home")?;
let home = realm_base.join("home"); let home = realm_base.join("home");
fs::create_dir_all(&home)?; util::create_dir(&home)?;
util::chown_user(&home)?; util::chown_user(&home)?;
let path = home.join("apt-cacher-ng"); let path = home.join("apt-cacher-ng");
fs::create_dir_all(&path)?; util::create_dir(&path)?;
util::chown_user(&path)?; util::chown_user(&path)?;
self.info("Copying /realms/skel into home diectory")?; self.info("Copying /realms/skel into home diectory")?;
util::copy_tree(&self.storage().join("realms/skel"), &home)?; util::copy_tree(&self.storage().join("realms/skel"), &home)?;
self.info("Creating apt-cacher config file")?; self.info("Creating apt-cacher config file")?;
fs::write(realm_base.join("config"), APT_CACHER_CONFIG)?; util::write_file(realm_base.join("config"), APT_CACHER_CONFIG)?;
fs::File::create(realm_base.join(".realmlock"))?; self.create_realmlock(&realm_base)
Ok(())
} }
fn setup_storage_resources(&self) -> Result<()> { fn setup_storage_resources(&self) -> Result<()> {
@ -454,22 +452,19 @@ impl Installer {
None => "dev", None => "dev",
}; };
let resources = self.storage().join("resources").join(channel); 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)?; self.sparse_copy_artifact(EXTRA_IMAGE_NAME, &resources)?;
let kernel_img = self.kernel_imagename(); let kernel_img = self.kernel_imagename();
self.sparse_copy_artifact(&kernel_img, &resources)?; self.sparse_copy_artifact(&kernel_img, &resources)
Ok(())
} }
fn install_rootfs_partitions(&self) -> Result<()> { fn install_rootfs_partitions(&self) -> Result<()> {
self.header("Installing rootfs partitions")?; self.header("Installing rootfs partitions")?;
let rootfs = self.artifact_path("citadel-rootfs.img"); 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 {}", rootfs.display()))?;
self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha --no-prefer {}", rootfs.display()))?; self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha --no-prefer {}", rootfs.display()))
Ok(())
} }
fn finish_install(&self) -> Result<()> { fn finish_install(&self) -> Result<()> {
@ -522,14 +517,12 @@ impl Installer {
self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?; self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?;
let src = self.artifact_path(filename); let src = self.artifact_path(filename);
let target = target.as_ref(); let target = target.as_ref();
if !target.exists() { util::create_dir(target)?;
fs::create_dir_all(target)?;
}
let dst = target.join(filename); let dst = target.join(filename);
if sparse { if sparse {
self.cmd(format!("/bin/cp --sparse=always {} {}", src.display(), dst.display()))?; self.cmd(format!("/bin/cp --sparse=always {} {}", src.display(), dst.display()))?;
} else { } else {
fs::copy(src, dst)?; util::copy_file(src, dst)?;
} }
Ok(()) Ok(())
} }
@ -542,12 +535,17 @@ impl Installer {
self.output(format!(" [>] {}", s.as_ref())) self.output(format!(" [>] {}", s.as_ref()))
} }
fn output<S: AsRef<str>>(&self, s: S) -> Result<()> { fn output<S: AsRef<str>>(&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()?; io::stdout().flush()?;
if let Some(ref file) = self.logfile { if let Some(ref file) = self.logfile {
writeln!(file.borrow_mut(), "{}", s.as_ref())?; writeln!(file.borrow_mut(), "{}", s)?;
file.borrow_mut().flush()?; file.borrow_mut().flush()?;
} }
Ok(()) Ok(())
@ -580,7 +578,8 @@ impl Installer {
command.args(&args[1..]); 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() { for line in String::from_utf8_lossy(&result.stdout).lines() {
self.output(format!(" {}", line))?; self.output(format!(" {}", line))?;

View File

@ -4,8 +4,6 @@ pub(crate) mod installer;
mod cli; mod cli;
mod disk; mod disk;
use libcitadel::format_error;
pub fn main(args: Vec<String>) { pub fn main(args: Vec<String>) {
let mut args = args.iter().skip(1); let mut args = args.iter().skip(1);
let result = if let Some(dev) = args.next() { let result = if let Some(dev) = args.next() {
@ -17,7 +15,7 @@ pub fn main(args: Vec<String>) {
let ok = match result { let ok = match result {
Ok(ok) => ok, Ok(ok) => ok,
Err(ref err) => { Err(ref err) => {
println!("Install failed: {}", format_error(err)); println!("Install failed: {}", err);
exit(1); exit(1);
}, },
}; };

View File

@ -3,8 +3,7 @@ use std::fs::OpenOptions;
use std::fs::{self,File}; use std::fs::{self,File};
use std::io::{self,Write}; use std::io::{self,Write};
use failure::ResultExt; use libcitadel::{Result, ImageHeader, devkeys, util};
use libcitadel::{Result,ImageHeader,devkeys};
use super::config::BuildConfig; use super::config::BuildConfig;
use std::path::Path; use std::path::Path;
@ -52,13 +51,13 @@ impl UpdateBuilder {
pub fn build(&mut self) -> Result<()> { pub fn build(&mut self) -> Result<()> {
info!("Copying source file to {}", self.image_data.display()); 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() self.pad_image()
.context("failed writing padding to image")?; .map_err(context!("failed writing padding to image"))?;
self.generate_verity() self.generate_verity()
.context("failed generating dm-verity hash tree")?; .map_err(context!("failed generating dm-verity hash tree"))?;
self.calculate_shasum()?; self.calculate_shasum()?;
@ -66,8 +65,7 @@ impl UpdateBuilder {
self.compress_image()?; self.compress_image()?;
self.write_final_image() self.write_final_image()?;
.context("failed to write final image file")?;
Ok(()) Ok(())
} }
@ -77,10 +75,11 @@ impl UpdateBuilder {
} }
fn pad_image(&mut self) -> Result<()> { 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; let len = meta.len() as usize;
if len % 512 != 0 { 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; let padlen = align(len, BLOCK_SIZE) - len;
@ -89,8 +88,11 @@ impl UpdateBuilder {
let zeros = vec![0u8; padlen]; let zeros = vec![0u8; padlen];
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.append(true) .append(true)
.open(self.image())?; .open(self.image())
file.write_all(&zeros)?; .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; let nblocks = (len + padlen) / 4096;
@ -102,7 +104,7 @@ impl UpdateBuilder {
fn calculate_shasum(&mut self) -> Result<()> { fn calculate_shasum(&mut self) -> Result<()> {
let output = cmd_with_output!("sha256sum", "{}", self.image().display()) 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 v: Vec<&str> = output.split_whitespace().collect();
let shasum = v[0].trim().to_owned(); let shasum = v[0].trim().to_owned();
info!("Sha256 of image data is {}", shasum); info!("Sha256 of image data is {}", shasum);
@ -113,7 +115,7 @@ impl UpdateBuilder {
fn prepend_empty_block(&mut self) -> Result<()> { fn prepend_empty_block(&mut self) -> Result<()> {
let tmpfile = self.image().with_extension("tmp"); let tmpfile = self.image().with_extension("tmp");
cmd!("/bin/dd", "if={} of={} bs=4096 seek=1 conv=sparse", self.image().display(), tmpfile.display())?; 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(()) Ok(())
} }
@ -124,8 +126,9 @@ impl UpdateBuilder {
let verity = Verity::new(self.image())?; let verity = Verity::new(self.image())?;
let output = verity.generate_initial_hashtree(&hashfile)?; let output = verity.generate_initial_hashtree(&hashfile)?;
fs::write(outfile, output.output()) if let Err(err) = fs::write(outfile, output.output()) {
.context("failed to write veritysetup command output to a file")?; bail!("Failed to write veritysetup command output to a file: {}", err);
}
let root = match output.root_hash() { let root = match output.root_hash() {
Some(s) => s.to_owned(), Some(s) => s.to_owned(),
@ -148,10 +151,11 @@ impl UpdateBuilder {
fn compress_image(&self) -> Result<()> { fn compress_image(&self) -> Result<()> {
if self.config.compress() { if self.config.compress() {
info!("Compressing image data"); info!("Compressing image data");
cmd!("xz", "-T0 {}", self.image().display()) if let Err(err) = cmd!("xz", "-T0 {}", self.image().display()) {
.context(format!("failed to compress {}", self.image().display()))?; bail!("failed to compress {:?}: {}", self.image(), err);
}
// Rename back to original image_data filename // 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(()) Ok(())
} }
@ -161,14 +165,17 @@ impl UpdateBuilder {
let target = self.config.workdir_path(self.target_filename()); let target = self.config.workdir_path(self.target_filename());
let mut out = File::create(&target) 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()) 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) 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(()) Ok(())
} }
@ -180,12 +187,12 @@ impl UpdateBuilder {
} }
let metainfo = self.generate_metainfo(); 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)?; hdr.set_metainfo_bytes(&metainfo)?;
if self.config.channel() == "dev" { if self.config.channel() == "dev" {
let sig = devkeys().sign(&metainfo); let sig = devkeys().sign(&metainfo);
hdr.set_signature(sig.to_bytes())?; hdr.set_signature(sig.to_bytes());
} }
Ok(hdr) Ok(hdr)
} }
@ -195,7 +202,7 @@ impl UpdateBuilder {
self._generate_metainfo().unwrap() self._generate_metainfo().unwrap()
} }
fn _generate_metainfo(&self) -> Result<Vec<u8>> { fn _generate_metainfo(&self) -> io::Result<Vec<u8>> {
assert!(self.verity_salt.is_some() && self.verity_root.is_some(), assert!(self.verity_salt.is_some() && self.verity_root.is_some(),
"no verity-salt/verity-root in generate_metainfo()"); "no verity-salt/verity-root in generate_metainfo()");

View File

@ -1,10 +1,8 @@
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use toml; use toml;
use libcitadel::Result; use libcitadel::{Result, util};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct BuildConfig { pub struct BuildConfig {
@ -39,10 +37,7 @@ impl BuildConfig {
path.push("mkimage.conf"); path.push("mkimage.conf");
} }
let mut config = match BuildConfig::from_path(&path) { let mut config = BuildConfig::from_path(&path)?;
Ok(config) => config,
Err(e) => bail!("Failed to load config file {}: {}", path.display(), e),
};
path.pop(); path.pop();
config.basedir = path; config.basedir = path;
@ -55,10 +50,9 @@ impl BuildConfig {
} }
fn from_path(path: &Path) -> Result<BuildConfig> { fn from_path(path: &Path) -> Result<BuildConfig> {
let mut f = File::open(path)?; let s = util::read_to_string(path)?;
let mut s = String::new(); let config = toml::from_str::<BuildConfig>(&s)
f.read_to_string(&mut s)?; .map_err(context!("Failed to parse build config file {:?}", path))?;
let config = toml::from_str::<BuildConfig>(&s)?;
config.validate()?; config.validate()?;
Ok(config) Ok(config)
} }

View File

@ -7,7 +7,6 @@ use clap::SubCommand;
use clap::AppSettings::*; use clap::AppSettings::*;
use clap::Arg; use clap::Arg;
use libcitadel::ResizeSize; use libcitadel::ResizeSize;
use libcitadel::format_error;
use std::process::exit; use std::process::exit;
pub fn main(args: Vec<String>) { pub fn main(args: Vec<String>) {
@ -87,7 +86,7 @@ is the final absolute size of the image.")
}; };
if let Err(ref e) = result { if let Err(ref e) = result {
eprintln!("Error: {}", format_error(e)); eprintln!("Error: {}", e);
exit(1); exit(1);
} }
} }
@ -125,12 +124,13 @@ fn parse_resize_size(s: &str) -> Result<ResizeSize> {
.parse::<usize>() .parse::<usize>()
.map_err(|_| format_err!("Unable to parse size value '{}'",s))?; .map_err(|_| format_err!("Unable to parse size value '{}'",s))?;
match unit { let sz = match unit {
Some('g') | Some('G') => Ok(ResizeSize::gigs(size)), Some('g') | Some('G') => ResizeSize::gigs(size),
Some('m') | Some('M') => Ok(ResizeSize::megs(size)), Some('m') | Some('M') => ResizeSize::megs(size),
Some(c) => Err(format_err!("Unknown size unit '{}'", c)), Some(c) => bail!("Unknown size unit '{}'", c),
None => Ok(ResizeSize::blocks(size)), None => ResizeSize::blocks(size),
} };
Ok(sz)
} }
fn resize(arg_matches: &ArgMatches) -> Result<()> { fn resize(arg_matches: &ArgMatches) -> Result<()> {

View File

@ -4,6 +4,7 @@ use std::path::Path;
use std::collections::HashMap; use std::collections::HashMap;
use libcitadel::Result; use libcitadel::Result;
use std::io;
pub struct DesktopFile { pub struct DesktopFile {
@ -20,14 +21,17 @@ impl DesktopFile {
pub fn write_to_dir<P: AsRef<Path>>(&self, directory: P) -> Result<()> { pub fn write_to_dir<P: AsRef<Path>>(&self, directory: P) -> Result<()> {
let path = directory.as_ref().join(&self.filename); let path = directory.as_ref().join(&self.filename);
let f = File::create(&path)?; let f = File::create(&path)
self.write_to(f)?; .map_err(context!("failed to open desktop file {:?}", path))?;
self.write_to(f)
.map_err(context!("error writing to desktop file {:?}", path))?;
Ok(()) Ok(())
} }
pub fn write_to<W: Write>(&self, mut w: W) -> Result<()> { pub fn write_to<W: Write>(&self, mut w: W) -> Result<()> {
for line in &self.lines { 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(()) Ok(())
} }
@ -173,7 +177,7 @@ impl Line {
} }
} }
fn write_to<W: Write>(&self, mut w: W) -> Result<()> { fn write_to<W: Write>(&self, mut w: W) -> io::Result<()> {
match *self { match *self {
Line::Empty => writeln!(w)?, Line::Empty => writeln!(w)?,
Line::Comment(ref s) => writeln!(w, "#{}", s)?, Line::Comment(ref s) => writeln!(w, "#{}", s)?,

View File

@ -1,10 +1,9 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::ffi::{OsStr,OsString}; use std::ffi::{OsStr,OsString};
use std::fs;
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use std::time::SystemTime; use std::time::SystemTime;
use libcitadel::{Realm,Realms,Result}; use libcitadel::{Realm, Realms, Result, util};
use crate::sync::parser::DesktopFileParser; use crate::sync::parser::DesktopFileParser;
use std::fs::DirEntry; use std::fs::DirEntry;
use crate::sync::icons::IconSync; use crate::sync::icons::IconSync;
@ -69,9 +68,7 @@ impl DesktopFileSync {
let target = Path::new(Self::CITADEL_APPLICATIONS); let target = Path::new(Self::CITADEL_APPLICATIONS);
if !target.exists() { util::create_dir(&target)?;
fs::create_dir_all(&target)?;
}
if clear { if clear {
Self::clear_target_files()?; Self::clear_target_files()?;
@ -89,14 +86,15 @@ impl DesktopFileSync {
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> { fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> {
let directory = Realms::current_realm_symlink().join(directory.as_ref()); let directory = Realms::current_realm_symlink().join(directory.as_ref());
if directory.exists() { if directory.exists() {
for entry in fs::read_dir(directory)? { util::read_directory(&directory, |dent| {
self.process_source_entry(entry?); self.process_source_entry(dent);
} Ok(())
})?;
} }
Ok(()) Ok(())
} }
fn process_source_entry(&mut self, entry: DirEntry) { fn process_source_entry(&mut self, entry: &DirEntry) {
let path = entry.path(); let path = entry.path();
if path.extension() == Some(OsStr::new("desktop")) { if path.extension() == Some(OsStr::new("desktop")) {
if let Some(mtime) = Self::mtime(&path) { if let Some(mtime) = Self::mtime(&path) {
@ -106,24 +104,21 @@ impl DesktopFileSync {
} }
pub fn clear_target_files() -> Result<()> { pub fn clear_target_files() -> Result<()> {
for entry in fs::read_dir(Self::CITADEL_APPLICATIONS)? { util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
let entry = entry?; util::remove_file(dent.path())
fs::remove_file(entry.path())?; })
}
Ok(())
} }
fn remove_missing_target_files(&mut self) -> Result<()> { fn remove_missing_target_files(&mut self) -> Result<()> {
let sources = self.source_filenames(); let sources = self.source_filenames();
for entry in fs::read_dir(Self::CITADEL_APPLICATIONS)? { util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
let entry = entry?; if !sources.contains(&dent.file_name()) {
if !sources.contains(&entry.file_name()) { let path = dent.path();
let path = entry.path();
verbose!("Removing desktop entry that no longer exists: {:?}", 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<SystemTime> { fn mtime(path: &Path) -> Option<SystemTime> {

View File

@ -13,7 +13,8 @@ pub struct IconCache {
impl IconCache { impl IconCache {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> { pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
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 }) Ok(IconCache { file })
} }
@ -46,7 +47,8 @@ impl IconCache {
let mut output = String::new(); let mut output = String::new();
let mut nread = 0; let mut nread = 0;
loop { 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 { if n == 0 {
return Ok(output); return Ok(output);
} }
@ -80,7 +82,8 @@ impl IconCache {
fn read_exact_at(&self, buf: &mut [u8], offset: usize) -> Result<()> { fn read_exact_at(&self, buf: &mut [u8], offset: usize) -> Result<()> {
let mut nread = 0; let mut nread = 0;
while nread < buf.len() { 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; nread += sz;
if sz == 0 { if sz == 0 {
bail!("bad offset"); bail!("bad offset");

View File

@ -1,9 +1,8 @@
use crate::sync::icon_cache::IconCache; use crate::sync::icon_cache::IconCache;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs;
use std::path::Path; use std::path::Path;
use libcitadel::{Result, Realms}; use libcitadel::{Result, Realms, util};
use std::cell::{RefCell, Cell}; use std::cell::{RefCell, Cell};
pub struct IconSync { pub struct IconSync {
@ -57,14 +56,14 @@ impl IconSync {
let mut names: Vec<String> = self.known.borrow().iter().map(|s| s.to_string()).collect(); let mut names: Vec<String> = self.known.borrow().iter().map(|s| s.to_string()).collect();
names.sort_unstable(); names.sort_unstable();
let out = names.join("\n") + "\n"; let out = names.join("\n") + "\n";
fs::write(Self::KNOWN_ICONS_FILE, out)?; util::write_file(Self::KNOWN_ICONS_FILE, out)?;
Ok(()) Ok(())
} }
fn read_known_cache() -> Result<HashSet<String>> { fn read_known_cache() -> Result<HashSet<String>> {
let target = Path::new(Self::KNOWN_ICONS_FILE); let target = Path::new(Self::KNOWN_ICONS_FILE);
if target.exists() { 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()) Ok(content.lines().map(|s| s.to_string()).collect())
} else { } else {
Ok(HashSet::new()) Ok(HashSet::new())
@ -77,13 +76,14 @@ impl IconSync {
return Ok(false) return Ok(false)
} }
let mut found = false; let mut found = false;
for entry in fs::read_dir(&base)? { util::read_directory(&base, |dent| {
let entry = entry?; let apps = dent.path().join("apps");
let apps = entry.path().join("apps");
if apps.exists() && self.search_subdirectory(&base, &apps, icon_name)? { if apps.exists() && self.search_subdirectory(&base, &apps, icon_name)? {
found = true; found = true;
} }
} Ok(())
})?;
if found { if found {
self.add_known(icon_name); self.add_known(icon_name);
} }
@ -92,28 +92,28 @@ impl IconSync {
fn search_subdirectory(&self, base: &Path, subdir: &Path, icon_name: &str) -> Result<bool> { fn search_subdirectory(&self, base: &Path, subdir: &Path, icon_name: &str) -> Result<bool> {
let mut found = false; let mut found = false;
for entry in fs::read_dir(subdir)? { util::read_directory(subdir, |dent| {
let entry = entry?; let path = dent.path();
let path = entry.path();
if let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) { if let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) {
if stem == icon_name { if stem == icon_name {
self.copy_icon_file(base, &path)?; self.copy_icon_file(base, &path)?;
found = true; found = true;
} }
} }
} Ok(())
})?;
Ok(found) Ok(found)
} }
fn copy_icon_file(&self, base: &Path, icon_path: &Path) -> Result<()> { fn copy_icon_file(&self, base: &Path, icon_path: &Path) -> Result<()> {
verbose!("copy icon file {}", icon_path.display()); 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 target = Path::new(Self::CITADEL_ICONS).join("hicolor").join(stripped);
let parent = target.parent().unwrap(); let parent = target.parent().unwrap();
if !parent.exists() { util::create_dir(parent)?;
fs::create_dir_all(parent)?; util::copy_file(icon_path, target)?;
}
fs::copy(icon_path, target)?;
Ok(()) Ok(())
} }
} }

View File

@ -1,9 +1,7 @@
use std::io::Read;
use std::fs::File;
use std::path::Path; use std::path::Path;
use std::collections::HashSet; use std::collections::HashSet;
use libcitadel::Result; use libcitadel::{Result, util};
use crate::sync::desktop_file::{DesktopFile,Line}; use crate::sync::desktop_file::{DesktopFile,Line};
lazy_static! { lazy_static! {
@ -36,11 +34,11 @@ fn is_whitelisted_key(key: &str) -> bool {
fn filename_from_path(path: &Path) -> Result<&str> { fn filename_from_path(path: &Path) -> Result<&str> {
let filename = match path.file_name() { let filename = match path.file_name() {
Some(name) => 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() { match filename.to_str() {
Some(s) => Ok(s), Some(s) => Ok(s),
None => Err(format_err!("Filename has invalid utf8 encoding")), None => bail!("Filename has invalid utf8 encoding"),
} }
} }
pub struct DesktopFileParser { pub struct DesktopFileParser {
@ -66,14 +64,9 @@ impl DesktopFileParser {
} }
pub fn parse_from_path<P: AsRef<Path>>(path: P, exec_prefix: &str) -> Result<DesktopFile> { pub fn parse_from_path<P: AsRef<Path>>(path: P, exec_prefix: &str) -> Result<DesktopFile> {
let filename = filename_from_path(path.as_ref())?; let path = path.as_ref();
let f = File::open(path.as_ref())?; let filename = filename_from_path(path)?;
DesktopFileParser::parse_from_reader(f, filename, exec_prefix) let buffer = util::read_to_string(path)?;
}
fn parse_from_reader<T: Read>(mut r: T, filename: &str, exec_prefix: &str) -> Result<DesktopFile> {
let mut buffer = String::new();
r.read_to_string(&mut buffer)?;
DesktopFileParser::parse_from_string(&buffer, filename, exec_prefix) DesktopFileParser::parse_from_string(&buffer, filename, exec_prefix)
} }
@ -82,7 +75,7 @@ impl DesktopFileParser {
for s in body.lines() { for s in body.lines() {
match LineParser::parse(s) { match LineParser::parse(s) {
Some(line) => parser.process_line(line)?, 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) Ok(parser.desktop_file)
@ -92,7 +85,7 @@ impl DesktopFileParser {
match line { match line {
Line::Comment(_) | Line::Empty => {}, Line::Comment(_) | Line::Empty => {},
Line::DesktopHeader => self.seen_header = true, 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); self.desktop_file.add_line(line);
Ok(()) Ok(())
@ -119,13 +112,13 @@ impl DesktopFileParser {
Line::ExecLine(ref mut s) => { Line::ExecLine(ref mut s) => {
s.insert_str(0,self.exec_prefix.as_str()) 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) => { Line::ActionHeader(ref action) => {
if self.known_actions.contains(action) { if self.known_actions.contains(action) {
self.current_action = Some(action.to_string()); self.current_action = Some(action.to_string());
self.in_ignored_group = false; self.in_ignored_group = false;
} else { } else {
return Err(format_err!("Desktop Action header with undecleared action: {}", action)) bail!("desktop Action header with undecleared action: {}", action)
} }
}, },
Line::GroupHeader(_) => { Line::GroupHeader(_) => {

View File

@ -1,4 +1,3 @@
use std::fs;
use std::fmt::{self,Write}; use std::fmt::{self,Write};
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
@ -48,7 +47,7 @@ impl KernelInstaller {
pub fn install(&mut self) -> Result<PathBuf> { pub fn install(&mut self) -> Result<PathBuf> {
let install_path = self.install_kernel_path()?; let install_path = self.install_kernel_path()?;
info!("Copying kernel bzImage to {}", install_path.display()); 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()?; self.boot_entries.rotate()?;
@ -61,9 +60,6 @@ impl KernelInstaller {
e.remove()?; e.remove()?;
} }
// 0) if boot.conf does not exist, just write it. done. // 0) if boot.conf does not exist, just write it. done.
// 1) if current boot.conf is not verified, just replace it. done. // 1) if current boot.conf is not verified, just replace it. done.
// 2) rotate boot.conf to boot.1.conf // 2) rotate boot.conf to boot.1.conf
@ -200,13 +196,12 @@ impl BootEntries {
if !base_path.exists() { if !base_path.exists() {
return Ok(()) return Ok(())
} }
for dirent in fs::read_dir(base_path)? { util::read_directory(base_path, |dent| {
let dirent = dirent?; if let Some(fname) = dent.file_name().to_str() {
if let Some(fname) = dirent.file_name().to_str() {
self.load_filename(fname); self.load_filename(fname);
} }
} Ok(())
Ok(()) })
} }
fn load_filename(&mut self, fname: &str) { fn load_filename(&mut self, fname: &str) {
@ -250,7 +245,7 @@ impl BootEntries {
fn _rotate(&mut self) -> Result<()> { fn _rotate(&mut self) -> Result<()> {
for entry in self.0.iter_mut().rev() { for entry in self.0.iter_mut().rev() {
if !entry.rotate()? { 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(()) Ok(())
@ -333,8 +328,7 @@ impl BootEntry {
writeln!(&mut buffer, "title {}", self.title)?; writeln!(&mut buffer, "title {}", self.title)?;
writeln!(&mut buffer, "linux /{}", kernel)?; writeln!(&mut buffer, "linux /{}", kernel)?;
writeln!(&mut buffer, "options {}", self.options)?; writeln!(&mut buffer, "options {}", self.options)?;
fs::write(self.path(), buffer)?; util::write_file(self.path(), buffer)
Ok(())
} }
fn is_good(&self) -> bool { fn is_good(&self) -> bool {
@ -351,7 +345,7 @@ impl BootEntry {
fn load(&mut self) -> Result<()> { fn load(&mut self) -> Result<()> {
let path = self.path(); 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 ") { if line.starts_with("title ") {
self.title = line.trim_start_matches("title ").to_owned(); self.title = line.trim_start_matches("title ").to_owned();
} else if line.starts_with("linux /") { } else if line.starts_with("linux /") {
@ -408,7 +402,7 @@ impl BootEntry {
return Ok(false); return Ok(false);
} }
verbose!("Rotating boot entry {} to {}", old_path.display(), new_path.display()); 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) Ok(true)
} }
@ -418,7 +412,7 @@ impl BootEntry {
bzimage.remove_file()?; bzimage.remove_file()?;
self.bzimage = None; self.bzimage = None;
} }
fs::remove_file(self.path())?; util::remove_file(self.path())?;
Ok(()) Ok(())
} }
} }
@ -447,8 +441,7 @@ impl KernelBzImage {
} }
fn remove_file(&self) -> Result<()> { fn remove_file(&self) -> Result<()> {
fs::remove_file(&self.path)?; util::remove_file(&self.path)
Ok(())
} }
} }

View File

@ -1,7 +1,6 @@
use std::path::{Path, PathBuf}; 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 crate::update::kernel::{KernelInstaller, KernelVersion};
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::DirEntry; use std::fs::DirEntry;
@ -57,16 +56,15 @@ fn detect_duplicates(image: &ResourceImage) -> Result<()> {
return Ok(()) return Ok(())
} }
for dirent in fs::read_dir(resource_dir)? { util::read_directory(&resource_dir, |dent| {
let dirent = dirent?; match ResourceImage::from_path(dent.path()) {
match ResourceImage::from_path(dirent.path()) {
Ok(img) => if img.metainfo().shasum() == shasum { Ok(img) => if img.metainfo().shasum() == shasum {
bail!("A duplicate image file with the same shasum already exists at {}", img.path().display()); bail!("A duplicate image file with the same shasum already exists at {}", img.path().display());
}, },
Err(err) => warn!("{}", err), Err(err) => warn!("{}", err),
} }
} Ok(())
Ok(()) })
} }
fn install_image(path: &Path, flags: u32) -> Result<()> { 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 new_meta = image.header().metainfo();
let shasum = new_meta.shasum(); let shasum = new_meta.shasum();
let target_dir = target_directory(image)?; let target_dir = target_directory(image)?;
for dirent in fs::read_dir(target_dir)? { util::read_directory(&target_dir, |dent| {
let dirent = dirent?; let path = dent.path();
let path = dirent.path(); maybe_remove_old_extra_image(&path, shasum)
maybe_remove_old_extra_image(&path, shasum)?; })
}
Ok(())
} }
fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> { 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 { if meta.shasum() != shasum {
info!("Removing old extra resource image {}", path.display()); info!("Removing old extra resource image {}", path.display());
fs::remove_file(&path)?; util::remove_file(&path)?;
} }
Ok(()) Ok(())
} }
fn install_kernel_image(image: &mut ResourceImage) -> Result<()> { fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
if !Path::new("/boot/loader/loader.conf").exists() { if !Path::new("/boot/loader/loader.conf").exists() {
bail!("failed to automount /boot partition. Please manually mount correct partition."); 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 version = metainfo.version();
let kernel_version = match metainfo.kernel_version() { let kernel_version = match metainfo.kernel_version() {
Some(kv) => kv, 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); info!("kernel version is {}", kernel_version);
install_kernel_file(image, &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 all_versions = all_boot_kernel_versions()?;
let image_dir = target_directory(image)?; let image_dir = target_directory(image)?;
let mut remove_paths = Vec::new(); let mut remove_paths = Vec::new();
for dirent in fs::read_dir(image_dir)? { util::read_directory(&image_dir, |dent| {
let dirent = dirent?; let path = dent.path();
let path = dirent.path();
if is_unused_kernel_image(&path, &all_versions)? { if is_unused_kernel_image(&path, &all_versions)? {
remove_paths.push(path); remove_paths.push(path);
} }
} Ok(())
})?;
for p in remove_paths { for p in remove_paths {
fs::remove_file(p)?; util::remove_file(p)?;
} }
Ok(()) Ok(())
} }
@ -216,14 +210,15 @@ fn install_kernel_file(image: &mut ResourceImage, kernel_version: &str) -> Resul
fn all_boot_kernel_versions() -> Result<HashSet<String>> { fn all_boot_kernel_versions() -> Result<HashSet<String>> {
let mut result = HashSet::new(); let mut result = HashSet::new();
for dirent in fs::read_dir("/boot")? { util::read_directory("/boot", |dent| {
let dirent = dirent?; if is_kernel_dirent(&dent) {
if is_kernel_dirent(&dirent) { if let Some(kv) = KernelVersion::parse_from_path(&dent.path()) {
if let Some(kv) = KernelVersion::parse_from_path(&dirent.path()) {
result.insert(kv.version()); result.insert(kv.version());
} }
} }
} Ok(())
})?;
Ok(result) Ok(result)
} }
@ -242,7 +237,7 @@ fn install_image_file(image: &ResourceImage, filename: &str) -> Result<()> {
rotate(&image_dest)?; rotate(&image_dest)?;
} }
info!("installing image file by moving from {} to {}", image.path().display(), image_dest.display()); 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(()) Ok(())
} }
@ -259,16 +254,14 @@ fn rotate(path: &Path) -> Result<()> {
} }
let filename = path.file_name().unwrap(); let filename = path.file_name().unwrap();
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy())); let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
if dot_zero.exists() { util::remove_file(&dot_zero)?;
fs::remove_file(&dot_zero)?; util::rename(path, &dot_zero)?;
}
fs::rename(path, &dot_zero)?;
Ok(()) Ok(())
} }
fn validate_channel_name(channel: &str) -> Result<()> { fn validate_channel_name(channel: &str) -> Result<()> {
if !channel.chars().all(|c| c.is_ascii_lowercase()) { if !channel.chars().all(|c| c.is_ascii_lowercase()) {
bail!("Image has invalid channel name '{}'", channel); bail!("image has invalid channel name '{}'", channel);
} }
Ok(()) Ok(())
} }
@ -334,5 +327,5 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
return Ok(p.clone()) return Ok(p.clone())
} }
} }
Err(format_err!("No suitable install partition found")) bail!("no suitable install partition found")
} }

View File

@ -7,7 +7,6 @@ edition = "2018"
[dependencies] [dependencies]
libc = "0.2" libc = "0.2"
nix = "0.17.0" nix = "0.17.0"
failure = "0.1.3"
toml = "0.5" toml = "0.5"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

View File

@ -1,9 +1,9 @@
use std::path::Path; use std::fs::{File,OpenOptions};
use std::fs::File;
use std::io::{Read,Write,Seek,SeekFrom}; use std::io::{Read,Write,Seek,SeekFrom};
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::fs::OpenOptions;
use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use libc; use libc;
use crate::Result; use crate::Result;
@ -112,7 +112,8 @@ impl BlockDev {
oo.write(true); oo.write(true);
} }
let file = oo.open(path) 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}) Ok(BlockDev{file})
} }
@ -121,7 +122,7 @@ impl BlockDev {
let mut sz = 0u64; let mut sz = 0u64;
unsafe { unsafe {
blk_getsize64(self.file.as_raw_fd(), &mut sz) 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) Ok(sz)
} }
@ -138,16 +139,18 @@ impl BlockDev {
fn setup_io(&mut self, offset: usize, buffer: &[u8]) -> Result<()> { fn setup_io(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
let addr = buffer.as_ptr() as usize; let addr = buffer.as_ptr() as usize;
if addr & ALIGNMENT_MASK != 0 { 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 { 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; let count = buffer.len() / SECTOR_SIZE;
if offset + count > self.nsectors()? { 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(()) Ok(())
} }
@ -155,16 +158,15 @@ impl BlockDev {
/// The buffer must be a multiple of sector size (512 bytes). /// The buffer must be a multiple of sector size (512 bytes).
pub fn read_sectors(&mut self, offset: usize, buffer: &mut [u8]) -> Result<()> { pub fn read_sectors(&mut self, offset: usize, buffer: &mut [u8]) -> Result<()> {
self.setup_io(offset, buffer)?; self.setup_io(offset, buffer)?;
self.file.read_exact(buffer)?; self.file.read_exact(buffer)
Ok(()) .map_err(context!("I/O error reading from block device"))
} }
/// Write sectors from `buffer` to device starting at sector `offset`. /// Write sectors from `buffer` to device starting at sector `offset`.
/// The buffer must be a multiple of sector size (512 bytes). /// The buffer must be a multiple of sector size (512 bytes).
pub fn write_sectors(&mut self, offset: usize, buffer: &[u8]) -> Result<()> { pub fn write_sectors(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
self.setup_io(offset, buffer)?; self.setup_io(offset, buffer)?;
self.file.write_all(buffer)?; self.file.write_all(buffer)
Ok(()) .map_err(context!("I/O error writing to block device"))
} }
} }

View File

@ -1,7 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fs;
use crate::Result; use crate::{Result, util};
lazy_static! { lazy_static! {
static ref CMDLINE: CommandLine = match CommandLine::load() { static ref CMDLINE: CommandLine = match CommandLine::load() {
@ -109,7 +108,7 @@ impl CommandLine {
} }
fn load() -> Result<Self> { fn load() -> Result<Self> {
let s = fs::read_to_string("/proc/cmdline")?; let s = util::read_to_string("/proc/cmdline")?;
let varmap = CommandLineParser::new(s).parse(); let varmap = CommandLineParser::new(s).parse();
Ok(CommandLine{varmap}) Ok(CommandLine{varmap})
} }
@ -289,7 +288,6 @@ fn foo() {
let cline = CommandLine::load().unwrap(); let cline = CommandLine::load().unwrap();
println!("hello"); println!("hello");
println!("cline: {:?}", cline.varmap); println!("cline: {:?}", cline.varmap);
} }

View File

@ -1,8 +1,7 @@
use std::path::Path; use std::path::Path;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs;
use crate::Result; use crate::{Result, util};
lazy_static! { lazy_static! {
static ref OS_RELEASE: Option<OsRelease> = match OsRelease::load() { static ref OS_RELEASE: Option<OsRelease> = match OsRelease::load() {
@ -26,12 +25,13 @@ impl OsRelease {
return OsRelease::load_file(path); return OsRelease::load_file(path);
} }
} }
Err(format_err!("File not found")) bail!("failed to find os-release file")
} }
fn load_file(path: &Path) -> Result<OsRelease> { fn load_file(path: &Path) -> Result<OsRelease> {
let mut vars = HashMap::new(); 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)?; let (k,v) = OsRelease::parse_line(line)?;
vars.insert(k,v); vars.insert(k,v);
} }
@ -52,7 +52,7 @@ impl OsRelease {
for q in &["'", "\""] { for q in &["'", "\""] {
if s.starts_with(q) { if s.starts_with(q) {
if !s.ends_with(q) || s.len() < 2 { 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()); return Ok(s[1..s.len() - 1].to_string());
} }

80
libcitadel/src/error.rs Normal file
View File

@ -0,0 +1,80 @@
use std::{result, fmt, error};
use std::fmt::Display;
pub type Result<T> = result::Result<T,Error>;
/// 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<S: Into<String>>(msg: S) -> Self {
Error::Message(msg.into())
}
pub fn with_error<S,D>(msg: S, err: D) -> Self
where
S: Into<String>,
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),
}
}
}

View File

@ -50,10 +50,11 @@ impl Exec {
let args: Vec<&str> = args.as_ref().split_whitespace().collect(); let args: Vec<&str> = args.as_ref().split_whitespace().collect();
let result = self.cmd let result = self.cmd
.args(args) .args(args)
.output()?; .output()
.map_err(context!("failed to execute command {}", self.cmd_name))?;
for line in BufReader::new(result.stderr.as_slice()).lines() { for line in BufReader::new(result.stderr.as_slice()).lines() {
verbose!(" {}", line?); verbose!(" {}", line.unwrap());
} }
self.check_cmd_status(result.status) self.check_cmd_status(result.status)
} }
@ -64,7 +65,8 @@ impl Exec {
let args: Vec<&str> = args.as_ref().split_whitespace().collect(); let args: Vec<&str> = args.as_ref().split_whitespace().collect();
let status = self.cmd let status = self.cmd
.args(args) .args(args)
.status()?; .status()
.map_err(context!("failed to execute command {}", self.cmd_name))?;
Ok(status.success()) Ok(status.success())
} }
@ -72,7 +74,9 @@ impl Exec {
pub fn output(&mut self, args: impl AsRef<str>) -> Result<String> { pub fn output(&mut self, args: impl AsRef<str>) -> Result<String> {
self.ensure_command_exists()?; self.ensure_command_exists()?;
self.add_args(args.as_ref()); 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)?; self.check_cmd_status(result.status)?;
Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned()) Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned())
} }
@ -90,11 +94,14 @@ impl Exec {
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
.spawn()?; .spawn()
.map_err(context!("failed to execute command {}", self.cmd_name))?;
let stdin = child.stdin.as_mut().unwrap(); let stdin = child.stdin.as_mut().unwrap();
io::copy(&mut r, stdin)?; io::copy(&mut r, stdin)
let output = child.wait_with_output()?; .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()) Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned())
} }
@ -121,18 +128,18 @@ impl Exec {
} else if path.exists() { } else if path.exists() {
return Ok(()) 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<PathBuf> { fn search_path(filename: &str) -> Result<PathBuf> {
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) { for mut path in env::split_paths(&path_var) {
path.push(filename); path.push(filename);
if path.exists() { if path.exists() {
return Ok(path); 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<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> { fn ranged_reader<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> {
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 { let offset = match range {
FileRange::All => 0, FileRange::All => 0,
FileRange::Offset(n) => n, FileRange::Offset(n) => n,
FileRange::Range {offset, ..} => offset, FileRange::Range {offset, ..} => offset,
}; };
if offset > 0 { 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); let r = BufReader::new(f);
if let FileRange::Range {len, ..} = range { if let FileRange::Range {len, ..} = range {

View File

@ -5,10 +5,11 @@ use std::path::Path;
use toml; use toml;
use crate::blockdev::AlignedBuffer; 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::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::sync::atomic::{Ordering,AtomicIsize}; use std::sync::atomic::{Ordering,AtomicIsize};
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::io;
/// Expected magic value in header /// Expected magic value in header
const MAGIC: &[u8] = b"SGOS"; const MAGIC: &[u8] = b"SGOS";
@ -159,6 +160,9 @@ impl ImageHeader {
/// Reload header if file has changed on disk /// Reload header if file has changed on disk
pub fn reload_if_stale<P: AsRef<Path>>(&self, path: P) -> Result<bool> { pub fn reload_if_stale<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
let path = path.as_ref(); let path = path.as_ref();
if !path.exists() {
bail!("cannot reload header because image file {:?} is missing", path);
}
let reload = self.is_stale(path)?; let reload = self.is_stale(path)?;
if reload { if reload {
self.reload_file(path)?; self.reload_file(path)?;
@ -185,9 +189,11 @@ impl ImageHeader {
let path = path.as_ref(); let path = path.as_ref();
let (size,ts) = Self::file_metadata(path)?; let (size,ts) = Self::file_metadata(path)?;
if size < Self::HEADER_SIZE { 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)?; let mut header = Self::from_reader(&mut f)?;
*header.timestamp.get_mut() = ts; *header.timestamp.get_mut() = ts;
Ok(header) Ok(header)
@ -195,13 +201,15 @@ impl ImageHeader {
// returns tuple of (size,mtime) // returns tuple of (size,mtime)
fn file_metadata(path: &Path) -> Result<(usize, isize)> { 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)) Ok((metadata.len() as usize, metadata.mtime() as isize))
} }
pub fn from_reader<R: Read>(r: &mut R) -> Result<Self> { pub fn from_reader<R: Read>(r: &mut R) -> Result<Self> {
let mut v = vec![0u8; Self::HEADER_SIZE]; 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) Self::from_slice(&v)
} }
@ -218,12 +226,9 @@ impl ImageHeader {
pub fn from_partition<P: AsRef<Path>>(path: P) -> Result<Self> { pub fn from_partition<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut dev = BlockDev::open_ro(path.as_ref())?; let mut dev = BlockDev::open_ro(path.as_ref())?;
let nsectors = dev.nsectors()?; let nsectors = dev.nsectors()?;
ensure!( if nsectors < 8 {
nsectors >= 8, bail!("cannot load/store header from block device {:?} because it's too short ({} sectors)", path.as_ref(), nsectors);
"{} is a block device bit it's too short ({} sectors)", }
path.as_ref().display(),
nsectors
);
let mut buffer = AlignedBuffer::new(Self::HEADER_SIZE); let mut buffer = AlignedBuffer::new(Self::HEADER_SIZE);
dev.read_sectors(nsectors - 8, buffer.as_mut())?; dev.read_sectors(nsectors - 8, buffer.as_mut())?;
Self::from_slice(buffer.as_ref()) Self::from_slice(buffer.as_ref())
@ -232,12 +237,9 @@ impl ImageHeader {
pub fn write_partition<P: AsRef<Path>>(&self, path: P) -> Result<()> { pub fn write_partition<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut dev = BlockDev::open_rw(path.as_ref())?; let mut dev = BlockDev::open_rw(path.as_ref())?;
let nsectors = dev.nsectors()?; let nsectors = dev.nsectors()?;
ensure!( if nsectors < 8 {
nsectors >= 8, bail!("cannot load/store header from block device {:?} because it's too short ({} sectors)", path.as_ref(), nsectors);
"{} is a block device bit it's too short ({} sectors)", }
path.as_ref().display(),
nsectors
);
let lock = self.bytes(); let lock = self.bytes();
let buffer = AlignedBuffer::from_slice(&lock.0); let buffer = AlignedBuffer::from_slice(&lock.0);
dev.write_sectors(nsectors - 8, buffer.as_ref())?; dev.write_sectors(nsectors - 8, buffer.as_ref())?;
@ -273,7 +275,7 @@ impl ImageHeader {
let mut lock = self.metainfo.lock().unwrap(); let mut lock = self.metainfo.lock().unwrap();
let mb = self.metainfo_bytes(); let mb = self.metainfo_bytes();
let metainfo = MetaInfo::parse_bytes(&mb) 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)); *lock = Some(Arc::new(metainfo));
Ok(()) Ok(())
} }
@ -335,13 +337,13 @@ impl ImageHeader {
pub fn update_metainfo<P: AsRef<Path>>(&self, metainfo_bytes: &[u8], signature: &[u8], path: P) -> Result<()> { pub fn update_metainfo<P: AsRef<Path>>(&self, metainfo_bytes: &[u8], signature: &[u8], path: P) -> Result<()> {
self.set_metainfo_bytes(metainfo_bytes)?; self.set_metainfo_bytes(metainfo_bytes)?;
self.set_signature(signature)?; self.set_signature(signature);
self.write_header_to(path) self.write_header_to(path)
} }
pub fn set_metainfo_bytes(&self, bytes: &[u8]) -> Result<()> { pub fn set_metainfo_bytes(&self, bytes: &[u8]) -> Result<()> {
let metainfo = MetaInfo::parse_bytes(bytes) 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(); let mut lock = self.metainfo.lock().unwrap();
self.with_bytes_mut(|bs| { self.with_bytes_mut(|bs| {
@ -369,18 +371,15 @@ impl ImageHeader {
self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH) self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH)
} }
pub fn set_signature(&self, signature: &[u8]) -> Result<()> { pub fn set_signature(&self, signature: &[u8]) {
if signature.len() != SIGNATURE_LENGTH { assert_eq!(signature.len(), SIGNATURE_LENGTH, "Signature has invalid length");
bail!("Signature has invalid length: {}", signature.len());
}
let mlen = self.metainfo_len(); let mlen = self.metainfo_len();
self.write_bytes(8 + mlen, signature); self.write_bytes(8 + mlen, signature);
Ok(())
} }
pub fn clear_signature(&self) -> Result<()> { pub fn clear_signature(&self) {
let zeros = vec![0u8; SIGNATURE_LENGTH]; let zeros = vec![0u8; SIGNATURE_LENGTH];
self.set_signature(&zeros) self.set_signature(&zeros);
} }
pub fn public_key(&self) -> Result<Option<PublicKey>> { pub fn public_key(&self) -> Result<Option<PublicKey>> {
@ -391,13 +390,16 @@ impl ImageHeader {
pubkey.verify(&self.metainfo_bytes(), &self.signature()) pubkey.verify(&self.metainfo_bytes(), &self.signature())
} }
pub fn write_header<W: Write>(&self, mut writer: W) -> Result<()> { pub fn write_header<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.with_bytes(|bs| writer.write_all(&bs.0))?; self.with_bytes(|bs| writer.write_all(&bs.0))
Ok(())
} }
pub fn write_header_to<P: AsRef<Path>>(&self, path: P) -> Result<()> { pub fn write_header_to<P: AsRef<Path>>(&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 { fn read_u8(&self, idx: usize) -> u8 {

View File

@ -21,7 +21,7 @@ use sodiumoxide::crypto::{
}, },
}; };
use crate::{Result,Error,KeyPair}; use crate::{Result, Error, KeyPair};
#[derive(Serialize,Deserialize,Debug)] #[derive(Serialize,Deserialize,Debug)]
pub struct KeyRing { pub struct KeyRing {
@ -38,9 +38,10 @@ impl KeyRing {
pub fn load<P: AsRef<Path>>(path: P, passphrase: &str) -> Result<Self> { pub fn load<P: AsRef<Path>>(path: P, passphrase: &str) -> Result<Self> {
let mut sbox = SecretBox::new(path.as_ref()); 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 mut bytes = sbox.open(passphrase)?;
let keyring = toml::from_slice::<KeyRing>(&bytes)?; let keyring = toml::from_slice::<KeyRing>(&bytes)
.map_err(context!("failed to parse keyring file {:?}", path.as_ref()))?;
bytes.iter_mut().for_each(|b| *b = 0); bytes.iter_mut().for_each(|b| *b = 0);
Ok(keyring) Ok(keyring)
} }
@ -73,13 +74,13 @@ impl KeyRing {
info!("Found {} key with request_key", name); info!("Found {} key with request_key", name);
return Ok(key); 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<()> { pub fn add_keys_to_kernel(&self) -> Result<()> {
for (k,v) in self.keypairs.iter() { for (k,v) in self.keypairs.iter() {
info!("Adding {} to kernel keystore", k.as_str()); 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)?; let key = KernelKey::add_key("user", k.as_str(), &bytes, KEY_SPEC_USER_KEYRING)?;
key.set_perm(0x3f03_0000)?; key.set_perm(0x3f03_0000)?;
} }
@ -96,12 +97,18 @@ impl KeyRing {
let salt = pwhash::gen_salt(); let salt = pwhash::gen_salt();
let nonce = secretbox::gen_nonce(); let nonce = secretbox::gen_nonce();
let key = SecretBox::passphrase_to_key(passphrase, &salt)?; 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 ciphertext = secretbox::seal(&bytes, &nonce, &key);
let mut file = fs::File::create(path.as_ref())?; Self::write_keyring(path.as_ref(), &salt.0, &nonce.0, &ciphertext)
file.write_all(&salt.0)?; .map_err(context!("error writing keyring file {:?}", path.as_ref()))
file.write_all(&nonce.0)?; }
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)?; file.write_all(&ciphertext)?;
Ok(()) Ok(())
} }
@ -143,6 +150,12 @@ impl SecretBox {
if !self.data.is_empty() { if !self.data.is_empty() {
self.data.clear(); 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)?; let mut file = fs::File::open(&self.path)?;
file.read_exact(&mut self.salt.0)?; file.read_exact(&mut self.salt.0)?;
file.read_exact(&mut self.nonce.0)?; file.read_exact(&mut self.nonce.0)?;
@ -153,14 +166,14 @@ impl SecretBox {
fn open(&self, passphrase: &str) -> Result<Vec<u8>> { fn open(&self, passphrase: &str) -> Result<Vec<u8>> {
let key = Self::passphrase_to_key(passphrase, &self.salt)?; let key = Self::passphrase_to_key(passphrase, &self.salt)?;
let result = secretbox::open(&self.data, &self.nonce, &key) 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) Ok(result)
} }
fn passphrase_to_key(passphrase: &str, salt: &Salt) -> Result<secretbox::Key> { fn passphrase_to_key(passphrase: &str, salt: &Salt) -> Result<secretbox::Key> {
let mut keybuf = [0; secretbox::KEYBYTES]; let mut keybuf = [0; secretbox::KEYBYTES];
pwhash::derive_key(&mut keybuf, passphrase.as_bytes(), salt, pwhash::OPSLIMIT_INTERACTIVE, pwhash::MEMLIMIT_INTERACTIVE) 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)) Ok(secretbox::Key(keybuf))
} }
@ -212,7 +225,7 @@ impl KernelKey {
let mut size = 0; let mut size = 0;
loop { loop {
size = match self.buffer_request(KEYCTL_DESCRIBE, size) { 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::Ok(vec) => return Ok(String::from_utf8(vec).expect("KEYCTL_DESCRIBE returned bad utf8")),
BufferResult::TooSmall(sz) => sz, BufferResult::TooSmall(sz) => sz,
} }
@ -231,7 +244,7 @@ impl KernelKey {
let mut size = 0; let mut size = 0;
loop { loop {
size = match self.buffer_request(KEYCTL_READ, size) { 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::Ok(buffer) => return Ok(buffer),
BufferResult::TooSmall(sz) => sz + 1, BufferResult::TooSmall(sz) => sz + 1,
} }
@ -242,14 +255,14 @@ impl KernelKey {
if size == 0 { if size == 0 {
return match keyctl1(command, self.id()) { return match keyctl1(command, self.id()) {
Err(err) => BufferResult::Err(err), 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), Ok(n) => BufferResult::TooSmall(n as usize),
}; };
} }
let mut buffer = vec![0u8; size]; let mut buffer = vec![0u8; size];
match keyctl3(command, self.id(), buffer.as_ptr() as u64, buffer.len() as u64) { match keyctl3(command, self.id(), buffer.as_ptr() as u64, buffer.len() as u64) {
Err(err) => BufferResult::Err(err), 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) => { Ok(sz) if size >= (sz as usize) => {
let sz = sz as usize; let sz = sz as usize;
if size > sz { if size > sz {
@ -294,7 +307,7 @@ fn sys_keyctl(command: c_int, arg2: c_ulong, arg3: c_ulong, arg4: c_ulong, arg5:
unsafe { unsafe {
let r = libc::syscall(libc::SYS_keyctl, command, arg2, arg3, arg4, arg5); let r = libc::syscall(libc::SYS_keyctl, command, arg2, arg3, arg4, arg5);
if r == -1 { if r == -1 {
Err(io::Error::last_os_error().into()) bail!("error calling sys_keyctl(): {}", io::Error::last_os_error());
} else { } else {
Ok(r) Ok(r)
} }
@ -305,7 +318,7 @@ fn _request_key(key_type: *const c_char, description: *const c_char) -> Result<c
unsafe { unsafe {
let r = libc::syscall(libc::SYS_request_key, key_type, description, 0, 0); let r = libc::syscall(libc::SYS_request_key, key_type, description, 0, 0);
if r == -1 { if r == -1 {
Err(io::Error::last_os_error().into()) bail!("error calling sys_request_key(): {}", io::Error::last_os_error());
} else { } else {
Ok(r) Ok(r)
} }
@ -316,7 +329,7 @@ fn _add_key(key_type: *const c_char, description: *const c_char, payload: *const
unsafe { unsafe {
let r = libc::syscall(libc::SYS_add_key, key_type, description, payload, plen, ring_id); let r = libc::syscall(libc::SYS_add_key, key_type, description, payload, plen, ring_id);
if r == -1 { if r == -1 {
Err(io::Error::last_os_error().into()) bail!("error calling sys_add_key(): {}", io::Error::last_os_error());
} else { } else {
Ok(r) Ok(r)
} }

View File

@ -1,9 +1,9 @@
use crate::Result;
use sodiumoxide::randombytes::randombytes_into; use sodiumoxide::randombytes::randombytes_into;
use sodiumoxide::crypto::sign::{self,Seed,SEEDBYTES,PUBLICKEYBYTES}; use sodiumoxide::crypto::sign::{self,Seed,SEEDBYTES,PUBLICKEYBYTES};
use hex; use hex;
use crate::Result;
/// ///
/// Keys for signing or verifying signatures. Small convenience /// Keys for signing or verifying signatures. Small convenience
/// wrapper around `sodiumoxide::crypto::sign`. /// wrapper around `sodiumoxide::crypto::sign`.
@ -18,10 +18,11 @@ pub struct Signature(sign::Signature);
impl PublicKey { impl PublicKey {
pub fn from_hex(hex: &str) -> Result<PublicKey> { pub fn from_hex(hex: &str) -> Result<PublicKey> {
let bytes = hex::decode(hex)?; let bytes = hex::decode(hex)
.map_err(context!("error hex decoding public key"))?;
if bytes.len() != PUBLICKEYBYTES { 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) let pubkey = sign::PublicKey::from_slice(&bytes)
.expect("PublicKey::from_slice() failed"); .expect("PublicKey::from_slice() failed");
@ -49,13 +50,14 @@ impl KeyPair {
} }
pub fn from_hex(hex: &str) -> Result<KeyPair> { pub fn from_hex(hex: &str) -> Result<KeyPair> {
let bytes = hex::decode(hex)?; let bytes = hex::decode(hex)
.map_err(context!("Error hex decoding key pair"))?;
KeyPair::from_bytes(&bytes) KeyPair::from_bytes(&bytes)
} }
pub fn from_bytes(bytes: &[u8]) -> Result<KeyPair> { pub fn from_bytes(bytes: &[u8]) -> Result<KeyPair> {
if bytes.len() != SEEDBYTES { 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"); let seed = sign::Seed::from_slice(&bytes).expect("Seed::from_slice() failed");
Ok(KeyPair(seed)) Ok(KeyPair(seed))

View File

@ -1,22 +1,8 @@
#[macro_use] extern crate failure;
#[macro_use] extern crate nix; #[macro_use] extern crate nix;
#[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_derive;
#[macro_use] extern crate lazy_static; #[macro_use] extern crate lazy_static;
use std::result; #[macro_use] pub mod error;
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] mod log; #[macro_use] mod log;
#[macro_use] mod exec; #[macro_use] mod exec;
mod blockdev; mod blockdev;
@ -87,7 +73,7 @@ pub fn public_key_for_channel(channel: &str) -> Result<Option<PublicKey>> {
Ok(None) Ok(None)
} }
pub type Result<T> = result::Result<T,Error>; pub use error::{Result,Error};
pub const BLOCK_SIZE: usize = 4096; pub const BLOCK_SIZE: usize = 4096;

View File

@ -109,8 +109,10 @@ impl LogOutput for DefaultLogOutput {
let stdout = io::stdout(); let stdout = io::stdout();
let mut lock = stdout.lock(); let mut lock = stdout.lock();
lock.write_all(line.as_bytes())?; lock.write_all(line.as_bytes())
lock.flush()?; .map_err(context!("error writing log line to stdout"))?;
lock.flush()
.map_err(context!("error flushing stdout"))?;
Ok(()) Ok(())
} }
} }

View File

@ -1,8 +1,10 @@
use std::path::{Path,PathBuf};
use std::fs; use std::fs;
use crate::{Result,ImageHeader,MetaInfo,Mounts,PublicKey,public_key_for_channel}; use std::path::{Path,PathBuf};
use std::sync::Arc; use std::sync::Arc;
use crate::{Result, ImageHeader, MetaInfo, Mounts, PublicKey, public_key_for_channel,util};
#[derive(Clone)] #[derive(Clone)]
pub struct Partition { pub struct Partition {
path: PathBuf, path: PathBuf,
@ -121,17 +123,21 @@ impl Partition {
pub fn write_status(&mut self, status: u8) -> Result<()> { pub fn write_status(&mut self, status: u8) -> Result<()> {
self.header().set_status(status); 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<()> { pub fn set_flag_and_write(&mut self, flag: u8) -> Result<()> {
self.header().set_flag(flag); 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<()> { pub fn clear_flag_and_write(&mut self, flag: u8) -> Result<()> {
self.header().clear_flag(flag); 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 /// Called at boot to perform various checks and possibly
@ -180,29 +186,36 @@ fn is_in_use(path: &Path) -> Result<bool> {
// //
fn count_block_holders(path: &Path) -> Result<usize> { fn count_block_holders(path: &Path) -> Result<usize> {
if !path.exists() { 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() { let fname = match resolved.file_name() {
Some(s) => s, Some(s) => s,
None => bail!("path does not have filename?"), None => bail!("path {:?} does not have a filename", resolved),
}; };
let holders_dir = let holders_dir =
Path::new("/sys/block") Path::new("/sys/block")
.join(fname) .join(fname)
.join("holders"); .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) Ok(count)
} }
fn rootfs_partition_paths() -> Result<Vec<PathBuf>> { fn rootfs_partition_paths() -> Result<Vec<PathBuf>> {
let mut rootfs_paths = Vec::new(); 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) { if is_path_rootfs(&path) {
rootfs_paths.push(path); rootfs_paths.push(path);
} }
} Ok(())
})?;
Ok(rootfs_paths) Ok(rootfs_paths)
} }

View File

@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use std::fs; use std::fs;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use toml; use toml;
use crate::{Result, Realms}; use crate::{Result, Realms, util};
lazy_static! { lazy_static! {
pub static ref GLOBAL_CONFIG: RealmConfig = RealmConfig::load_global_config(); pub static ref GLOBAL_CONFIG: RealmConfig = RealmConfig::load_global_config();
@ -147,16 +147,14 @@ impl RealmConfig {
None None
} }
pub fn write_config<P: AsRef<Path>>(&self, path: P) -> Result<()> { pub fn write_to<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let serialized = toml::to_string(self)?; let serialized = toml::to_string(self)
fs::write(path.as_ref(), serialized)?; .map_err(context!("failed to serialize realm config"))?;
Ok(()) util::write_file(path, serialized)
} }
pub fn write(&self) -> Result<()> { pub fn write(&self) -> Result<()> {
let serialized = toml::to_string(self)?; self.write_to(&self.path)
fs::write(&self.path, serialized)?;
Ok(())
} }
fn read_mtime(&self) -> i64 { fn read_mtime(&self) -> i64 {
@ -171,8 +169,9 @@ impl RealmConfig {
let path = self.path.clone(); let path = self.path.clone();
if self.path.exists() { if self.path.exists() {
let s = fs::read_to_string(&self.path)?; let s = util::read_to_string(&self.path)?;
*self = toml::from_str(&s)?; *self = toml::from_str(&s)
.map_err(context!("Failed to parse realm config"))?;
} else { } else {
*self = Self::empty(); *self = Self::empty();
} }

View File

@ -49,26 +49,22 @@ impl RealmCreateDestroy {
fn create_realm_directory(&self) -> Result<()> { fn create_realm_directory(&self) -> Result<()> {
self.create_home()?; self.create_home()?;
self.move_from_temp()?; self.move_from_temp()
Ok(())
} }
fn create_home(&self) -> Result<()> { fn create_home(&self) -> Result<()> {
let home = self.temp_basepath().join("home"); let home = self.temp_basepath().join("home");
fs::create_dir_all(&home) util::create_dir(&home)?;
.map_err(|e| format_err!("failed to create directory {}: {}", home.display(), e))?; util::chown(&home, 1000, 1000)?;
util::chown(&home, 1000, 1000)
.map_err(|e| format_err!("failed to change ownership of {} to 1000:1000: {}", home.display(), e))?;
let skel = Path::new(Realms::BASE_PATH).join("skel"); let skel = Path::new(Realms::BASE_PATH).join("skel");
if skel.exists() { if skel.exists() {
info!("Populating realm home directory with files from {}", skel.display()); info!("Populating realm home directory with files from {}", skel.display());
util::copy_tree(&skel, &home) 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(()) Ok(())
} }
@ -78,8 +74,7 @@ impl RealmCreateDestroy {
if to.exists() { if to.exists() {
bail!("Cannot move temporary directory {} to {} because the target already exists", from.display(), to.display()); bail!("Cannot move temporary directory {} to {} because the target already exists", from.display(), to.display());
} }
fs::rename(from, to)?; util::rename(&from, &to)
Ok(())
} }
fn move_to_temp(&self) -> Result<()> { 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()); bail!("Cannot move realm directory {} to {} because the target already exists", from.display(), to.display());
} }
if !Self::tmpdir().exists() { let tmpdir = Self::tmpdir();
fs::create_dir_all(Self::tmpdir())?; util::create_dir(&tmpdir)?;
} util::rename(&from, &to)
fs::rename(from, to)?;
Ok(())
} }
pub fn delete_realm(&self, save_home: bool) -> Result<()> { pub fn delete_realm(&self, save_home: bool) -> Result<()> {
@ -106,22 +96,19 @@ impl RealmCreateDestroy {
self.save_home_for_delete()?; self.save_home_for_delete()?;
} }
info!("removing realm directory {}", self.temp_basepath().display()); let realmdir = self.temp_basepath();
fs::remove_dir_all(self.temp_basepath())?; info!("removing realm directory {:?}", realmdir);
Ok(()) fs::remove_dir_all(&realmdir)
.map_err(context!("error removing realm directory {:?}", realmdir))
} }
fn save_home_for_delete(&self) -> Result<()> { fn save_home_for_delete(&self) -> Result<()> {
if !Path::new("/realms/removed").exists() { util::create_dir("/realms/removed")?;
fs::create_dir("/realms/removed")?;
}
let target = self.home_save_directory(); let target = self.home_save_directory();
let home = self.temp_basepath().join("home"); 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()); info!("home directory been moved to {}, delete it at your leisure", target.display());
Ok(()) Ok(())
} }

View File

@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self,JoinHandle}; use std::thread::{self,JoinHandle};
use std::path; use std::path;
use crate::{RealmManager, Result, Realm}; use crate::{RealmManager, Result, Realm, util};
use super::realms::HasCurrentChanged; use super::realms::HasCurrentChanged;
use dbus::{Connection, BusType, ConnectionItem, Message, Path}; use dbus::{Connection, BusType, ConnectionItem, Message, Path};
use inotify::{Inotify, WatchMask, WatchDescriptor, Event}; use inotify::{Inotify, WatchMask, WatchDescriptor, Event};
@ -212,8 +212,10 @@ impl DbusEventListener {
} }
fn dbus_event_loop(&self) -> Result<()> { fn dbus_event_loop(&self) -> Result<()> {
let connection = Connection::get_private(BusType::System)?; let connection = Connection::get_private(BusType::System)
connection.add_match("interface='org.freedesktop.machine1.Manager',type='signal'")?; .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) { for item in connection.iter(1000) {
if self.inner().quit_flag() { if self.inner().quit_flag() {
break; break;
@ -244,7 +246,8 @@ impl DbusEventListener {
let member = message.member() let member = message.member()
.ok_or_else(|| format_err!("invalid signal"))?; .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()) { if let (Some(interface),Some(member)) = (message.interface(),message.member()) {
verbose!("DBUS: {}:[{}({})]", interface, member,name); verbose!("DBUS: {}:[{}({})]", interface, member,name);
} }
@ -286,18 +289,21 @@ struct InotifyEventListener {
impl InotifyEventListener { impl InotifyEventListener {
fn create(inner: Arc<RwLock<Inner>>) -> Result<Self> { fn create(inner: Arc<RwLock<Inner>>) -> Result<Self> {
let mut inotify = Inotify::init()?; let mut inotify = Inotify::init()
let realms_watch = inotify.add_watch("/realms", WatchMask::MOVED_FROM|WatchMask::MOVED_TO)?; .map_err(context!("inotify initialization failed"))?;
let current_watch = inotify.add_watch("/run/citadel/realms/current", WatchMask::CREATE|WatchMask::MOVED_TO)?; 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, }) Ok(InotifyEventListener { inner, inotify, realms_watch, current_watch, })
} }
fn wake_inotify() -> Result<()> { fn wake_inotify() -> Result<()> {
let path = "/run/citadel/realms/current/stop-events"; let path = "/run/citadel/realms/current/stop-events";
fs::File::create(path)?; fs::File::create(path)
fs::remove_file(path)?; .map_err(context!("error creating {}", path))?;
Ok(()) util::remove_file(path)
} }
fn spawn(mut self) -> JoinHandle<Result<()>> { fn spawn(mut self) -> JoinHandle<Result<()>> {
@ -307,7 +313,8 @@ impl InotifyEventListener {
fn inotify_event_loop(&mut self) -> Result<()> { fn inotify_event_loop(&mut self) -> Result<()> {
let mut buffer = [0; 1024]; let mut buffer = [0; 1024];
while !self.inner().quit_flag() { 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() { if !self.inner().quit_flag() {
for event in events { for event in events {

View File

@ -1,9 +1,7 @@
use std::fs; use std::fmt::{self,Write};
use std::fmt::Write;
use crate::{Realm,Result};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::realm::network::NetworkConfig;
use crate::{Realm, Result, util, realm::network::NetworkConfig};
const NSPAWN_FILE_TEMPLATE: &str = "\ const NSPAWN_FILE_TEMPLATE: &str = "\
[Exec] [Exec]
@ -77,15 +75,8 @@ impl <'a> RealmLauncher <'a> {
} }
pub fn remove_launch_config_files(&self) -> Result<()> { pub fn remove_launch_config_files(&self) -> Result<()> {
let nspawn_path = self.realm_nspawn_path(); util::remove_file(self.realm_nspawn_path())?;
if nspawn_path.exists() { util::remove_file(self.realm_service_path())
fs::remove_file(&nspawn_path)?;
}
let service_path = self.realm_service_path();
if service_path.exists() {
fs::remove_file(&service_path)?;
}
Ok(())
} }
pub fn write_launch_config_files(&mut self, rootfs: &Path, netconfig: &mut NetworkConfig) -> Result<()> { 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_path = self.realm_nspawn_path();
let nspawn_content = self.generate_nspawn_file(netconfig)?; let nspawn_content = self.generate_nspawn_file(netconfig)?;
self.write_launch_config_file(&nspawn_path, &nspawn_content) self.write_launch_config_file(&nspawn_path, &nspawn_content)?;
.map_err(|e| format_err!("failed to write nspawn config file {}: {}", nspawn_path.display(), e))?;
let service_path = self.realm_service_path(); let service_path = self.realm_service_path();
let service_content = self.generate_service_file(rootfs); let service_content = self.generate_service_file(rootfs);
self.write_launch_config_file(&service_path, &service_content) 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 { pub fn realm_service_name(&self) -> &str {
@ -113,15 +100,10 @@ impl <'a> RealmLauncher <'a> {
/// not already exist, create it. /// not already exist, create it.
fn write_launch_config_file(&self, path: &Path, content: &str) -> Result<()> { fn write_launch_config_file(&self, path: &Path, content: &str) -> Result<()> {
match path.parent() { match path.parent() {
Some(parent) => { Some(parent) => util::create_dir(parent)?,
if !parent.exists() {
fs::create_dir_all(parent)?;
}
},
None => bail!("config file path {} has no parent?", path.display()), None => bail!("config file path {} has no parent?", path.display()),
}; };
fs::write(path, content)?; util::write_file(path, content)
Ok(())
} }
fn generate_nspawn_file(&mut self, netconfig: &mut NetworkConfig) -> Result<String> { fn generate_nspawn_file(&mut self, netconfig: &mut NetworkConfig) -> Result<String> {
@ -239,3 +221,9 @@ impl <'a> RealmLauncher <'a> {
PathBuf::from(SYSTEMD_NSPAWN_PATH).join(format!("{}.nspawn", self.realm.name())) PathBuf::from(SYSTEMD_NSPAWN_PATH).join(format!("{}.nspawn", self.realm.name()))
} }
} }
impl From<fmt::Error> for crate::Error {
fn from(e: fmt::Error) -> Self {
format_err!("Error formatting string: {}", e).into()
}
}

View File

@ -1,5 +1,4 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
@ -205,7 +204,7 @@ impl RealmManager {
let home = realm.base_path_file("home"); let home = realm.base_path_file("home");
if !home.exists() { if !home.exists() {
warn!("No home directory exists at {}, creating an empty directory", home.display()); 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)?; util::chown_user(&home)?;
} }
@ -226,10 +225,9 @@ impl RealmManager {
fn create_realm_namefile(&self, realm: &Realm) -> Result<()> { fn create_realm_namefile(&self, realm: &Realm) -> Result<()> {
let namefile = realm.run_path_file("realm-name"); 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")?; self.systemd.machinectl_copy_to(realm, &namefile, "/run/realm-name")?;
fs::remove_file(&namefile)?; util::remove_file(&namefile)
Ok(())
} }
fn start_realm_dependencies(&self, realm: &Realm, starting: &mut HashSet<String>) -> Result<()> { fn start_realm_dependencies(&self, realm: &Realm, starting: &mut HashSet<String>) -> Result<()> {
@ -331,13 +329,14 @@ impl RealmManager {
// ensure that /proc/pid/root/run and /proc/pid/root/run/realm-name // ensure that /proc/pid/root/run and /proc/pid/root/run/realm-name
// are not symlinks // are not symlinks
let run_meta = run.symlink_metadata()?; let run_meta = run.symlink_metadata()
let name_meta = realm_name.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() { if !run_meta.file_type().is_dir() || !name_meta.file_type().is_file() {
bail!("invalid path"); bail!("invalid path");
} }
let bytes = fs::read(realm_name)?; util::read_to_string(&realm_name)
Ok(String::from_utf8(bytes)?)
} }
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> { pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
@ -381,7 +380,6 @@ impl RealmManager {
} }
self.inner_mut().realmfs_set.remove(realmfs.name()); self.inner_mut().realmfs_set.remove(realmfs.name());
info!("Removing RealmFS image file {}", realmfs.path().display()); info!("Removing RealmFS image file {}", realmfs.path().display());
fs::remove_file(realmfs.path())?; util::remove_file(realmfs.path())
Ok(())
} }
} }

View File

@ -2,9 +2,9 @@ use std::path::{Path,PathBuf};
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::collections::{HashSet,HashMap}; use std::collections::{HashSet,HashMap};
use std::io::{BufReader,BufRead,Write}; 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"; const REALMS_RUN_PATH: &str = "/run/citadel/realms";
@ -90,7 +90,8 @@ impl BridgeAllocator {
let (addr_str, mask_size) = match network.find('/') { let (addr_str, mask_size) = match network.find('/') {
Some(idx) => { Some(idx) => {
let (net,bits) = network.split_at(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), None => (network.to_owned(), 24),
}; };
@ -99,7 +100,7 @@ impl BridgeAllocator {
} }
let mask = (1u32 << (32 - mask_size)) - 1; let mask = (1u32 << (32 - mask_size)) - 1;
let ip = addr_str.parse::<Ipv4Addr>()?; let ip = addr_str.parse::<Ipv4Addr>().map_err(|_| format_err!("Failed to parse IP address ({})", addr_str))?;
if (u32::from(ip) & mask) != 0 { if (u32::from(ip) & mask) != 0 {
bail!("network {} has masked bits with netmask /{}", addr_str, mask_size); bail!("network {} has masked bits with netmask /{}", addr_str, mask_size);
@ -199,13 +200,12 @@ impl BridgeAllocator {
if !path.exists() { if !path.exists() {
return Ok(()) 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); let reader = BufReader::new(f);
for line in reader.lines() { 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)?; self.parse_state_line(line)?;
} }
Ok(()) Ok(())
} }
@ -213,7 +213,7 @@ impl BridgeAllocator {
match line.find(':') { match line.find(':') {
Some(idx) => { Some(idx) => {
let (name,addr) = line.split_at(idx); let (name,addr) = line.split_at(idx);
let ip = addr[1..].parse::<Ipv4Addr>()?; let ip = addr[1..].parse::<Ipv4Addr>().map_err(|_| format_err!("Failed to parse IP address ({})", &addr[1..]))?;
self.allocated.insert(ip); self.allocated.insert(ip);
self.allocations.insert(name.to_owned(), ip); self.allocations.insert(name.to_owned(), ip);
}, },
@ -225,15 +225,13 @@ impl BridgeAllocator {
fn write_state(&mut self) -> Result<()> { fn write_state(&mut self) -> Result<()> {
let path = self.state_file_path(); let path = self.state_file_path();
let dir = path.parent().unwrap(); let dir = path.parent().unwrap();
if !dir.exists() { util::create_dir(dir)?;
fs::create_dir_all(dir)
.map_err(|e| format_err!("failed to create directory {} for network allocation state file: {}", dir.display(), e))?;
}
let mut f = File::create(&path) 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 { 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(()) Ok(())
} }

View File

@ -1,8 +1,7 @@
use std::fs; use std::fs;
use std::os::unix;
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use crate::{Realm,Result}; use crate::{Realm, Result, util};
use crate::Exec; use crate::Exec;
use crate::realm::config::OverlayType; use crate::realm::config::OverlayType;
@ -68,7 +67,7 @@ impl RealmOverlay {
} }
let lower = base.join("lower").read_link() 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 { match self.overlay {
OverlayType::TmpFS => self.remove_tmpfs(&base)?, OverlayType::TmpFS => self.remove_tmpfs(&base)?,
@ -93,14 +92,14 @@ impl RealmOverlay {
fn remove_tmpfs(&self, base: &Path) -> Result<()> { fn remove_tmpfs(&self, base: &Path) -> Result<()> {
fs::remove_dir_all(base) 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<()> { fn remove_btrfs(&self, base: &Path) -> Result<()> {
Exec::new("/usr/bin/btrfs") Exec::new("/usr/bin/btrfs")
.quiet() .quiet()
.run(format!("subvolume delete {}", base.display())) .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<PathBuf> { fn create_tmpfs(&self, lower: &Path) -> Result<PathBuf> {
@ -139,7 +138,8 @@ impl RealmOverlay {
let upper = self.mkdir(base, "upperdir")?; let upper = self.mkdir(base, "upperdir")?;
let work = self.mkdir(base, "workdir")?; let work = self.mkdir(base, "workdir")?;
let mountpoint = self.mkdir(base, "mountpoint")?; 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", cmd!("/usr/bin/mount",
"-t overlay realm-{}-overlay -olowerdir={},upperdir={},workdir={} {}", "-t overlay realm-{}-overlay -olowerdir={},upperdir={},workdir={} {}",
self.realm, self.realm,
@ -152,8 +152,7 @@ impl RealmOverlay {
fn mkdir(&self, base: &Path, dirname: &str) -> Result<PathBuf> { fn mkdir(&self, base: &Path, dirname: &str) -> Result<PathBuf> {
let path = base.join(dirname); let path = base.join(dirname);
fs::create_dir_all(&path) util::create_dir(&path)?;
.map_err(|e| format_err!("failed to create directory {}: {}", path.display(), e))?;
Ok(path) Ok(path)
} }

View File

@ -187,11 +187,9 @@ impl Realm {
/// to order the output when listing realms. /// to order the output when listing realms.
pub fn update_timestamp(&self) -> Result<()> { pub fn update_timestamp(&self) -> Result<()> {
let tstamp = self.base_path().join(".tstamp"); let tstamp = self.base_path().join(".tstamp");
if tstamp.exists() { util::remove_file(&tstamp)?;
fs::remove_file(&tstamp)?;
}
fs::File::create(&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 // also load the new value
self.inner_mut().timestamp = self.load_timestamp(); self.inner_mut().timestamp = self.load_timestamp();
Ok(()) Ok(())
@ -309,7 +307,7 @@ impl Realm {
// not exist, create it as a fork of 'base' // not exist, create it as a fork of 'base'
base.fork(default) base.fork(default)
} else { } 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 path = self.base_path_file("notes");
let notes = notes.as_ref(); let notes = notes.as_ref();
if path.exists() && notes.is_empty() { if path.exists() && notes.is_empty() {
fs::remove_file(path)?; util::remove_file(&path)
} else { } else {
fs::write(path, notes)?; util::write_file(&path, notes)
} }
Ok(())
} }
} }

View File

@ -1,9 +1,9 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use std::fs; use std::fs;
use crate::{Realm, Result, symlink, RealmManager,FileLock};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use crate::{Realm, Result, symlink, RealmManager, FileLock, util};
use super::create::RealmCreateDestroy; use super::create::RealmCreateDestroy;
use crate::realm::systemd::Systemd; use crate::realm::systemd::Systemd;
@ -88,12 +88,13 @@ impl Realms {
fn all_realms(mark_active: bool) -> Result<Vec<Realm>> { fn all_realms(mark_active: bool) -> Result<Vec<Realm>> {
let mut v = Vec::new(); let mut v = Vec::new();
for entry in fs::read_dir(Realms::BASE_PATH)? { util::read_directory(Realms::BASE_PATH, |dent| {
let entry = entry?; if let Some(realm) = Realms::entry_to_realm(dent) {
if let Some(realm) = Realms::entry_to_realm(&entry) {
v.push(realm); v.push(realm);
} }
} Ok(())
})?;
if mark_active { if mark_active {
Realms::mark_active_realms(&mut v)?; Realms::mark_active_realms(&mut v)?;
} }

View File

@ -1,18 +1,17 @@
use std::process::Command;
use std::path::Path;
use std::env; 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 SYSTEMCTL_PATH: &str = "/usr/bin/systemctl";
const MACHINECTL_PATH: &str = "/usr/bin/machinectl"; 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 { pub struct Systemd {
network: Mutex<NetworkConfig>, network: Mutex<NetworkConfig>,
} }
@ -56,7 +55,8 @@ impl Systemd {
for dir in realm.config().ephemeral_persistent_dirs() { for dir in realm.config().ephemeral_persistent_dirs() {
let src = home.join(&dir); let src = home.join(&dir);
if src.exists() { 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() { if src.starts_with(&home) && src.exists() {
let dst = Path::new("/home/user").join(&dir); let dst = Path::new("/home/user").join(&dir);
self.machinectl_bind(realm, &src, &dst)?; self.machinectl_bind(realm, &src, &dst)?;
@ -86,12 +86,13 @@ impl Systemd {
} }
fn run_systemctl(&self, op: &str, name: &str) -> Result<bool> { fn run_systemctl(&self, op: &str, name: &str) -> Result<bool> {
Command::new(SYSTEMCTL_PATH) let ok = Command::new(SYSTEMCTL_PATH)
.arg(op) .arg(op)
.arg(name) .arg(name)
.status() .status()
.map(|status| status.success()) .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<Path>, to: &str) -> Result<()> { pub fn machinectl_copy_to(&self, realm: &Realm, from: impl AsRef<Path>, to: &str) -> Result<()> {
@ -100,7 +101,7 @@ impl Systemd {
Command::new(MACHINECTL_PATH) Command::new(MACHINECTL_PATH)
.args(&["copy-to", realm.name(), from, to ]) .args(&["copy-to", realm.name(), from, to ])
.status() .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(()) Ok(())
} }
@ -110,17 +111,18 @@ impl Systemd {
Command::new(MACHINECTL_PATH) Command::new(MACHINECTL_PATH)
.args(&["--mkdir", "bind", realm.name(), from.as_str(), to.as_str() ]) .args(&["--mkdir", "bind", realm.name(), from.as_str(), to.as_str() ])
.status() .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(()) Ok(())
} }
pub fn is_active(realm: &Realm) -> Result<bool> { pub fn is_active(realm: &Realm) -> Result<bool> {
Command::new(SYSTEMCTL_PATH) let ok = Command::new(SYSTEMCTL_PATH)
.args(&["--quiet", "is-active"]) .args(&["--quiet", "is-active"])
.arg(format!("realm-{}", realm.name())) .arg(format!("realm-{}", realm.name()))
.status() .status()
.map(|status| status.success()) .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<Realm>) -> Result<String> { pub fn are_realms_active(realms: &mut Vec<Realm>) -> Result<String> {
@ -132,7 +134,8 @@ impl Systemd {
.arg("is-active") .arg("is-active")
.args(args) .args(args)
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
.output()?; .output()
.map_err(context!("failed to run /usr/bin/systemctl"))?;
Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned()) Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned())
} }
@ -175,7 +178,7 @@ impl Systemd {
cmd.arg(arg.as_ref()); 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(()) Ok(())
} }
} }

View File

@ -3,7 +3,7 @@ use std::fmt;
use std::fs::{self, DirEntry}; use std::fs::{self, DirEntry};
use std::path::{PathBuf, Path}; use std::path::{PathBuf, Path};
use crate::{Result, RealmFS, CommandLine, ImageHeader}; use crate::{Result, RealmFS, CommandLine, ImageHeader, util};
use crate::verity::Verity; use crate::verity::Verity;
@ -28,7 +28,8 @@ impl Mountpoint {
/// Read `RealmFS::RUN_DIRECTORY` to collect all current mountpoints /// Read `RealmFS::RUN_DIRECTORY` to collect all current mountpoints
/// and return them. /// and return them.
pub fn all_mountpoints() -> Result<Vec<Mountpoint>> { pub fn all_mountpoints() -> Result<Vec<Mountpoint>> {
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()) .flat_map(|e| e.ok())
.map(Into::into) .map(Into::into)
.filter(Mountpoint::is_valid) .filter(Mountpoint::is_valid)
@ -51,11 +52,6 @@ impl Mountpoint {
self.0.exists() self.0.exists()
} }
fn create_dir(&self) -> Result<()> {
fs::create_dir_all(self.path())?;
Ok(())
}
pub fn is_mounted(&self) -> bool { pub fn is_mounted(&self) -> bool {
// test for an arbitrary expected directory // test for an arbitrary expected directory
self.path().join("etc").exists() self.path().join("etc").exists()
@ -73,9 +69,8 @@ impl Mountpoint {
return Ok(()) return Ok(())
} }
if !self.exists() { util::create_dir(self.path())?;
self.create_dir()?;
}
let verity_path = self.verity_device_path(); let verity_path = self.verity_device_path();
if verity_path.exists() { if verity_path.exists() {
warn!("dm-verity device {:?} already exists which was not expected", verity_path); warn!("dm-verity device {:?} already exists which was not expected", verity_path);

View File

@ -130,11 +130,10 @@ impl RealmFS {
/// Return an Error result if name is not valid. /// Return an Error result if name is not valid.
fn validate_name(name: &str) -> Result<()> { fn validate_name(name: &str) -> Result<()> {
if Self::is_valid_name(name) { if !Self::is_valid_name(name) {
Ok(()) bail!("Invalid realm name '{}'", name);
} else {
Err(format_err!("Invalid realm name '{}'", name))
} }
Ok(())
} }
/// Return `true` if `name` is a valid name for a RealmFS. /// 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 path = self.path_with_extension("notes");
let notes = notes.as_ref(); let notes = notes.as_ref();
if path.exists() && notes.is_empty() { if path.exists() && notes.is_empty() {
fs::remove_file(path)?; util::remove_file(&path)
} else { } else {
fs::write(path, notes)?; util::write_file(&path, notes)
} }
Ok(())
} }
/// Return `MetaInfo` from image header of this RealmFS. /// 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 // Return the length in blocks of the actual image file on disk
pub fn file_nblocks(&self) -> Result<usize> { pub fn file_nblocks(&self) -> Result<usize> {
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; let len = meta.len() as usize;
if len % 4096 != 0 { if len % 4096 != 0 {
bail!("realmfs image file '{}' has size which is not a multiple of block size", self.path.display()); 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<usize> { pub fn allocated_size_blocks(&self) -> Result<usize> {
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) Ok(meta.blocks() as usize / 8)
} }

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::fs; use std::fs;
use std::sync::Arc; use std::sync::Arc;
use crate::{RealmFS, RealmManager, Result}; use crate::{RealmFS, RealmManager, Result, util};
pub struct RealmFSSet { pub struct RealmFSSet {
realmfs_map: HashMap<String, RealmFS>, realmfs_map: HashMap<String, RealmFS>,
@ -21,12 +21,23 @@ impl RealmFSSet {
fn load_all() -> Result<Vec<RealmFS>> { fn load_all() -> Result<Vec<RealmFS>> {
let mut v = Vec::new(); let mut v = Vec::new();
for entry in fs::read_dir(RealmFS::BASE_PATH)? { util::read_directory(RealmFS::BASE_PATH, |dent| {
let entry = entry?; 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) { if let Some(realmfs) = Self::entry_to_realmfs(&entry) {
v.push(realmfs) v.push(realmfs)
} }
} }
*/
Ok(v) Ok(v)
} }

View File

@ -80,10 +80,14 @@ impl Superblock {
} }
pub fn load(path: impl AsRef<Path>, offset: u64) -> Result<Self> { pub fn load(path: impl AsRef<Path>, offset: u64) -> Result<Self> {
let path = path.as_ref();
let mut sb = Self::new(); let mut sb = Self::new();
let mut file = File::open(path.as_ref())?; let mut file = File::open(path)
file.seek(SeekFrom::Start(1024 + offset))?; .map_err(context!("failed to open image file {:?}", path))?;
file.read_exact(&mut sb.0)?; 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) Ok(sb)
} }

View File

@ -5,7 +5,7 @@ use std::process::Command;
use sodiumoxide::randombytes::randombytes; 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::realm::BridgeAllocator;
use crate::util::is_euid_root; use crate::util::is_euid_root;
use crate::terminal::TerminalRestorer; use crate::terminal::TerminalRestorer;
@ -72,7 +72,7 @@ impl <'a> Update<'a> {
fn create_update_copy(&self) -> Result<()> { fn create_update_copy(&self) -> Result<()> {
if self.target.exists() { if self.target.exists() {
info!("Update file {} already exists, removing it", self.target.display()); 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())?; self.realmfs.copy_image_file(self.target())?;
@ -108,9 +108,7 @@ impl <'a> Update<'a> {
if self.resize.is_some() { if self.resize.is_some() {
self.resize_device(loopdev)?; self.resize_device(loopdev)?;
} }
if !self.mountpath.exists() { util::create_dir(&self.mountpath)?;
fs::create_dir_all(&self.mountpath)?;
}
util::mount(loopdev.device_str(), &self.mountpath, Some("-orw,noatime"))?; util::mount(loopdev.device_str(), &self.mountpath, Some("-orw,noatime"))?;
Ok(()) Ok(())
}) })
@ -126,7 +124,6 @@ impl <'a> Update<'a> {
if self.mountpath.exists() { if self.mountpath.exists() {
if let Err(err) = util::umount(&self.mountpath) { if let Err(err) = util::umount(&self.mountpath) {
warn!("Failed to unmount directory {:?}: {}", self.mountpath, err); warn!("Failed to unmount directory {:?}: {}", self.mountpath, err);
} }
if let Err(err) = fs::remove_dir(&self.mountpath) { if let Err(err) = fs::remove_dir(&self.mountpath) {
warn!("Failed to remove mountpoint directory {:?}: {}", self.mountpath, err); 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 len = (nblocks * BLOCK_SIZE) as u64;
let f = fs::OpenOptions::new() let f = fs::OpenOptions::new()
.write(true) .write(true)
.open(&self.target)?; .open(&self.target)
f.set_len(len)?; .map_err(context!("failed to open update image file {:?} to set length", self.target))?;
Ok(())
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. // 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 salt = hex::encode(randombytes(32));
let verity = Verity::new(&self.target)?; let verity = Verity::new(&self.target)
let output = verity.generate_image_hashtree_with_salt(&salt, nblocks)?; .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 // XXX passes metainfo for nblocks
//let output = Verity::new(&self.target).generate_image_hashtree_with_salt(&self.realmfs.metainfo(), &salt)?; //let output = Verity::new(&self.target).generate_image_hashtree_with_salt(&self.realmfs.metainfo(), &salt)?;
let root_hash = output.root_hash() let root_hash = output.root_hash()
@ -252,15 +253,21 @@ impl <'a> Update<'a> {
let header = ImageHeader::new(); let header = ImageHeader::new();
header.set_flag(ImageHeader::FLAG_HASH_TREE); header.set_flag(ImageHeader::FLAG_HASH_TREE);
header.update_metainfo(&metainfo_bytes, sig.to_bytes(), &self.target) 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<bool> { fn prompt_user(prompt: &str, default_y: bool) -> Result<bool> {
let yn = if default_y { "(Y/n)" } else { "(y/N)" }; let yn = if default_y { "(Y/n)" } else { "(y/N)" };
print!("{} {} : ", prompt, yn); print!("{} {} : ", prompt, yn);
io::stdout().flush()?; io::stdout().flush()
.map_err(context!("failed to flush stdout"))?;
let mut line = String::new(); 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() { let yes = match line.trim().chars().next() {
Some(c) => c == 'Y' || c == 'y', Some(c) => c == 'Y' || c == 'y',
@ -319,7 +326,7 @@ impl <'a> Update<'a> {
.status() .status()
.map_err(|e| { .map_err(|e| {
let _ = self.cleanup(); let _ = self.cleanup();
e Error::with_error("failed to run systemd-nspawn", e)
})?; })?;
Ok(()) Ok(())
} }
@ -339,12 +346,13 @@ impl <'a> Update<'a> {
for i in (1..NUM_BACKUPS).rev() { for i in (1..NUM_BACKUPS).rev() {
let from = backup(i - 1); let from = backup(i - 1);
if from.exists() { if from.exists() {
fs::rename(from, backup(i))?; let to = backup(i);
util::rename(&from, &to)?;
} }
} }
fs::rename(self.realmfs.path(), backup(0))?; let to = backup(0);
fs::rename(self.target(), self.realmfs.path())?; util::rename(self.realmfs.path(), &to)?;
Ok(()) util::rename(self.target(), self.realmfs.path())
} }
} }

View File

@ -1,11 +1,10 @@
use std::fs::{self,File,DirEntry}; use std::fs::{File,DirEntry};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::io::{self,Seek,SeekFrom}; use std::io::{self,Seek,SeekFrom};
use std::path::{Path, PathBuf}; 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 std::sync::Arc;
use crate::UtsName; use crate::UtsName;
use crate::verity::Verity; use crate::verity::Verity;
@ -49,7 +48,7 @@ impl ResourceImage {
} }
if !Self::ensure_storage_mounted()? { if !Self::ensure_storage_mounted()? {
bail!("Unable to mount /storage"); bail!("unable to mount /storage");
} }
let storage_path = Path::new(STORAGE_BASEDIR).join(&channel); let storage_path = Path::new(STORAGE_BASEDIR).join(&channel);
@ -58,7 +57,7 @@ impl ResourceImage {
return Ok(image); 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<()> { pub fn mount_image_type(image_type: &str) -> Result<()> {
@ -70,14 +69,14 @@ impl ResourceImage {
pub fn find_rootfs() -> Result<Self> { pub fn find_rootfs() -> Result<Self> {
match search_directory(RUN_DIRECTORY, "rootfs", None)? { match search_directory(RUN_DIRECTORY, "rootfs", None)? {
Some(image) => Ok(image), 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<P: AsRef<Path>>(path: P) -> Result<Self> { pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let header = ImageHeader::from_file(path.as_ref())?; let header = ImageHeader::from_file(path.as_ref())?;
if !header.is_magic_valid() { 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 )) Ok(Self::new(path.as_ref(), header ))
} }
@ -143,25 +142,28 @@ impl ResourceImage {
return Ok(()) return Ok(())
} }
info!("decompressing image file {}", self.path().display()); info!("decompressing image file {}", self.path().display());
let mut reader = File::open(self.path())?; let mut reader = File::open(self.path())
reader.seek(SeekFrom::Start(4096))?; .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 xzfile = self.path.with_extension("tmp.xz");
let mut out = File::create(&xzfile)?; let mut out = File::create(&xzfile)
io::copy(&mut reader, &mut out)?; .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)?; 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.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED);
self.header.write_header_to(self.path())?; self.header.write_header_to(self.path())
Ok(())
} }
pub fn write_to_partition(&self, partition: &Partition) -> Result<()> { pub fn write_to_partition(&self, partition: &Partition) -> Result<()> {
if self.metainfo().image_type() != "rootfs" { 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() { if !self.has_verity_hashtree() {
@ -183,7 +185,7 @@ impl ResourceImage {
info!("Mounting dm-verity device to {}", mount_path.display()); 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)?; util::mount(verity_path, mount_path, None)?;
Ok(ResourceMount::new_verity(mount_path, verity_dev)) Ok(ResourceMount::new_verity(mount_path, verity_dev))
@ -195,11 +197,11 @@ impl ResourceImage {
match self.header.public_key()? { match self.header.public_key()? {
Some(pubkey) => { Some(pubkey) => {
if !self.header.verify_signature(pubkey) { if !self.header.verify_signature(pubkey) {
bail!("Header signature verification failed"); bail!("header signature verification failed");
} }
info!("Image header signature is valid"); 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"); info!("Setting up dm-verity device for image");
@ -240,7 +242,7 @@ impl ResourceImage {
} }
info!("Calculating sha256 of image"); 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}) 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 v: Vec<&str> = output.split_whitespace().collect();
let shasum = v[0].trim().to_owned(); let shasum = v[0].trim().to_owned();
Ok(shasum) Ok(shasum)
@ -261,7 +263,7 @@ impl ResourceImage {
info!("Loop device created: {}", loopdev); info!("Loop device created: {}", loopdev);
info!("Mounting to: {}", mount_path.display()); 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"))?; 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()); warn!("No manifest file found for resource image: {}", self.path.display());
return Ok(()) return Ok(())
} }
let s = fs::read_to_string(manifest)?; let s = util::read_to_string(manifest)?;
for line in s.lines() { for line in s.lines() {
if let Err(e) = self.process_manifest_line(&line) { if let Err(e) = self.process_manifest_line(&line) {
warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e); warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e);
@ -416,7 +419,7 @@ fn parse_timestamp(img: &ResourceImage) -> Result<usize> {
let ts = img.metainfo() let ts = img.metainfo()
.timestamp() .timestamp()
.parse::<usize>() .parse::<usize>()
.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) 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 kernel_id = OsRelease::citadel_kernel_id();
let mut v = Vec::new(); let mut v = Vec::new();
for entry in fs::read_dir(dir)? { util::read_directory(dir, |dent| {
maybe_add_dir_entry(entry?, image_type, channel, kv, kernel_id, &mut v)?; maybe_add_dir_entry(dent, image_type, channel, kv, kernel_id, &mut v)?;
} Ok(())
})?;
Ok(v) 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 // If the entry is a match, then instantiate a ResourceImage and add it to
// the images vector. // the images vector.
fn maybe_add_dir_entry(entry: DirEntry, fn maybe_add_dir_entry(entry: &DirEntry,
image_type: &str, image_type: &str,
channel: Option<&str>, channel: Option<&str>,
kernel_version: Option<&str>, kernel_version: Option<&str>,
@ -465,7 +469,8 @@ fn maybe_add_dir_entry(entry: DirEntry,
if Some(OsStr::new("img")) != path.extension() { if Some(OsStr::new("img")) != path.extension() {
return Ok(()) 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 { if meta.len() < ImageHeader::HEADER_SIZE as u64 {
return Ok(()) return Ok(())
} }

View File

@ -1,8 +1,7 @@
use std::fs; use std::fs;
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use std::os::unix;
use crate::Result; use crate::{Result, util};
pub fn read(path: impl AsRef<Path>) -> Option<PathBuf> { pub fn read(path: impl AsRef<Path>) -> Option<PathBuf> {
let path = path.as_ref(); let path = path.as_ref();
@ -29,18 +28,12 @@ pub fn write(target: impl AsRef<Path>, link: impl AsRef<Path>, tmp_in_parent: bo
let tmp = write_tmp_path(link, tmp_in_parent); let tmp = write_tmp_path(link, tmp_in_parent);
if let Some(parent) = link.parent() { if let Some(parent) = link.parent() {
if !parent.exists() { util::create_dir(parent)?;
fs::create_dir_all(parent)?;
}
} }
if tmp.exists() { util::remove_file(&tmp)?;
fs::remove_file(&tmp)?; util::symlink(target, &tmp)?;
} util::rename(&tmp, link)
unix::fs::symlink(target, &tmp)?;
fs::rename(&tmp, link)?;
Ok(())
} }
fn write_tmp_path(link: &Path, tmp_in_parent: bool) -> PathBuf { 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<Path>) -> Result<()> { pub fn remove(path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref(); let path = path.as_ref();
if fs::symlink_metadata(path).is_ok() { if fs::symlink_metadata(path).is_ok() {
fs::remove_file(path)?; util::remove_file(path)?;
} }
Ok(()) Ok(())
} }

View File

@ -3,7 +3,7 @@ use std::io::{Error,ErrorKind};
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use crate::Result; use crate::{Result, util};
/// ///
/// Create a lockfile and acquire an exclusive lock with flock(2) /// Create a lockfile and acquire an exclusive lock with flock(2)
@ -45,9 +45,7 @@ impl FileLock {
fn open_lockfile(path: &Path) -> Result<File> { fn open_lockfile(path: &Path) -> Result<File> {
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
if !parent.exists() { util::create_dir(parent)?;
fs::create_dir_all(parent)?;
}
} }
// Make a few attempts just in case we try to open lockfile // Make a few attempts just in case we try to open lockfile
@ -61,14 +59,14 @@ impl FileLock {
return Ok(file); 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<Option<File>> { fn try_create_lockfile(path: &Path) -> Result<Option<File>> {
match OpenOptions::new().write(true).create_new(true).open(path) { match OpenOptions::new().write(true).create_new(true).open(path) {
Ok(file) => Ok(Some(file)), Ok(file) => Ok(Some(file)),
Err(ref e) if e.kind() == ErrorKind::AlreadyExists => Ok(None), 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) { match File::open(path) {
Ok(file) => Ok(Some(file)), Ok(file) => Ok(Some(file)),
Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(None), 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 { if !block && errno == libc::EWOULDBLOCK {
return Ok(false); return Ok(false);
} }
return Err(Error::from_raw_os_error(errno).into()); bail!("error calling flock(): {}", Error::from_raw_os_error(errno));
} }
Ok(true) Ok(true)
} }

View File

@ -39,7 +39,7 @@ impl LoopDevice {
let result = f(&loopdev); let result = f(&loopdev);
let detach_result = loopdev.detach(); let detach_result = loopdev.detach();
let r = result?; 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) Ok(r)
} }
@ -108,8 +108,7 @@ impl LoopDevice {
// mount --bind olddir newdir // mount --bind olddir newdir
// mount -o remount,bind,ro olddir newdir // mount -o remount,bind,ro olddir newdir
cmd!(Self::MOUNT, "--bind {} {}", rw.display(), ro.display())?; cmd!(Self::MOUNT, "--bind {} {}", rw.display(), ro.display())?;
cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display())?; cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display())
Ok(())
} }
} }

View File

@ -1,8 +1,7 @@
use std::fs;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use crate::Result; use crate::{Result, util};
pub struct Mounts { pub struct Mounts {
content: String, content: String,
@ -34,7 +33,7 @@ impl Mounts {
} }
pub fn load() -> Result<Mounts> { pub fn load() -> Result<Mounts> {
let content = fs::read_to_string("/proc/mounts")?; let content = util::read_to_string("/proc/mounts")?;
Ok(Mounts { content }) Ok(Mounts { content })
} }

View File

@ -2,6 +2,7 @@
use crate::Result; use crate::Result;
use crate::terminal::{RawTerminal, Color, Base16Scheme}; use crate::terminal::{RawTerminal, Color, Base16Scheme};
use std::io::{self,Read,Write,Stdout}; use std::io::{self,Read,Write,Stdout};
use std::string::FromUtf8Error;
#[derive(Default)] #[derive(Default)]
pub struct AnsiControl(String); pub struct AnsiControl(String);
@ -125,10 +126,9 @@ impl AnsiControl {
io::stdout().flush().unwrap(); io::stdout().flush().unwrap();
} }
pub fn write_to<W: Write>(&self, mut writer: W) -> Result<()> { fn write_to<W: Write>(&self, mut writer: W) -> Result<()> {
writer.write_all(self.as_bytes())?; writer.write_all(self.as_bytes()).map_err(context!("error writing to terminal"))?;
writer.flush()?; writer.flush().map_err(context!("error calling flush() on terminal"))
Ok(())
} }
} }
@ -137,7 +137,6 @@ pub struct AnsiTerminal {
raw: RawTerminal<Stdout>, raw: RawTerminal<Stdout>,
} }
impl AnsiTerminal { impl AnsiTerminal {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let raw = RawTerminal::create(io::stdout())?; let raw = RawTerminal::create(io::stdout())?;
@ -210,18 +209,24 @@ impl AnsiTerminal {
} }
fn write_code(&mut self, sequence: AnsiControl) -> Result<()> { 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.write_all(sequence.as_bytes())?;
self.raw.flush()?; self.raw.flush()
Ok(())
} }
fn read_response(&mut self) -> Result<String> { fn read_response(&mut self) -> Result<String> {
let stdin = io::stdin(); let stdin = io::stdin();
let mut input = stdin.lock(); let mut input = stdin.lock();
let mut buffer = Vec::new(); let mut buffer = Vec::new();
input.read_to_end(&mut buffer)?; input.read_to_end(&mut buffer)
let s = String::from_utf8(buffer)?; .map_err(context!("error reading terminal input"))?;
Ok(s)
String::from_utf8(buffer)
.map_err(context!("terminal input is not correct utf-8"))
} }
pub fn apply_base16(&mut self, base16: &Base16Scheme) -> Result<()> { pub fn apply_base16(&mut self, base16: &Base16Scheme) -> Result<()> {
@ -235,3 +240,9 @@ impl AnsiTerminal {
} }
} }
impl From<FromUtf8Error> for crate::Error {
fn from(_: FromUtf8Error) -> Self {
format_err!("failed to convert string to UTF-8").into()
}
}

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use crate::terminal::{Color, Base16Shell}; use crate::terminal::{Color, Base16Shell};
use crate::{Realm, Result, util, RealmManager}; use crate::{Realm, Result, util, RealmManager};
use std::path::Path; use std::path::Path;
use std::fs; use std::{fs, io};
use std::io::Write; use std::io::Write;
lazy_static! { lazy_static! {
@ -123,7 +123,7 @@ impl Base16Scheme {
fn write_ephemeral_realm_files(&self, manager: &RealmManager, realm: &Realm) -> Result<()> { fn write_ephemeral_realm_files(&self, manager: &RealmManager, realm: &Realm) -> Result<()> {
let skel = realm.base_path_file("skel"); let skel = realm.base_path_file("skel");
fs::create_dir_all(&skel)?; util::create_dir(&skel)?;
util::chown_user(&skel)?; util::chown_user(&skel)?;
self.write_realm_files(&skel)?; self.write_realm_files(&skel)?;
if realm.is_active() { if realm.is_active() {
@ -144,9 +144,9 @@ impl Base16Scheme {
pub fn write_realm_files<P: AsRef<Path>>(&self, base: P) -> Result<()> { pub fn write_realm_files<P: AsRef<Path>>(&self, base: P) -> Result<()> {
let base = base.as_ref(); let base = base.as_ref();
self.write_shell_file(base) 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) 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(()) Ok(())
} }
@ -160,16 +160,20 @@ impl Base16Scheme {
fn write_vim_file(&self, dir: &Path) -> Result<()> { fn write_vim_file(&self, dir: &Path) -> Result<()> {
let path = dir.join(Self::BASE16_VIM_FILE); let path = dir.join(Self::BASE16_VIM_FILE);
let mut file = fs::File::create(&path)?; self.write_vim_file_to(&path)
writeln!(&mut file, "if !exists('g:colors_name') || g:colors_name != '{}'", self.slug())?; .map_err(context!("failed to write vim color scheme file to {:?}", path))?;
writeln!(&mut file, " colorscheme base16-{}", self.slug())?;
writeln!(&mut file, "endif")?;
drop(file);
util::chown_user(&path)?; util::chown_user(&path)?;
debug!("Wrote base16 vim config file to {}", path.display()); debug!("Wrote base16 vim config file to {}", path.display());
Ok(()) 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(())
}
} }

View File

@ -1,8 +1,7 @@
use std::fs;
use std::path::Path; use std::path::Path;
use crate::terminal::Base16Scheme; use crate::terminal::Base16Scheme;
use crate::Result; use crate::{Result, util};
const TEMPLATE: &str = r##" const TEMPLATE: &str = r##"
if [ -n "$TMUX" ]; then if [ -n "$TMUX" ]; then
@ -70,8 +69,8 @@ impl Base16Shell {
pub fn write_script<P: AsRef<Path>>(path: P, scheme: &Base16Scheme) -> Result<()> { pub fn write_script<P: AsRef<Path>>(path: P, scheme: &Base16Scheme) -> Result<()> {
let output = Base16Shell::new(scheme.clone()).build(); let output = Base16Shell::new(scheme.clone()).build();
fs::write(path.as_ref(), output)?; let path = path.as_ref();
Ok(()) util::write_file(path, output)
} }
fn new(scheme: Base16Scheme) -> Self { fn new(scheme: Base16Scheme) -> Self {

View File

@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
use crate::Result; use crate::{Result, Error};
use crate::terminal::AnsiTerminal; use crate::terminal::AnsiTerminal;
#[derive(Copy,Clone,Default,Debug)] #[derive(Copy,Clone,Default,Debug)]
@ -21,7 +21,7 @@ impl Color {
return Ok(Color(r, g, b)) return Ok(Color(r, g, b))
} }
} }
Err(format_err!("Cannot parse '{}'", s)) bail!("Cannot parse '{}'", s)
} }
pub fn rgb(self) -> (u16,u16,u16) { pub fn rgb(self) -> (u16,u16,u16) {
@ -93,3 +93,9 @@ impl TerminalPalette {
} }
} }
impl From<std::num::ParseIntError> for crate::Error {
fn from(_: std::num::ParseIntError) -> Self {
Error::message("failed to parse integer")
}
}

View File

@ -78,7 +78,7 @@ pub fn spawn_citadel_gnome_terminal<S>(command: Option<S>)
pub fn open_citadel_gnome_terminal<S: AsRef<str>>(command: Option<S>) -> Result<()> pub fn open_citadel_gnome_terminal<S: AsRef<str>>(command: Option<S>) -> Result<()>
{ {
let mut cmd = build_open_terminal_command(command); 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); info!("Gnome terminal exited with: {}", status);
Ok(()) Ok(())
} }

View File

@ -1,31 +1,31 @@
use std::mem; use std::mem;
use std::io::{self,Write}; use std::io::{self,Write};
use libc::c_int;
pub use libc::termios as Termios; pub use libc::termios as Termios;
use libc::c_int;
use crate::Result; use crate::Result;
fn get_terminal_attr() -> io::Result<Termios> { fn get_terminal_attr() -> Result<Termios> {
extern "C" { extern "C" {
pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int; pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int;
} }
unsafe { unsafe {
let mut termios = mem::zeroed(); let mut termios = mem::zeroed();
if tcgetattr(0, &mut termios) == -1 { 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) Ok(termios)
} }
} }
fn set_terminal_attr(termios: &Termios) -> io::Result<()> { fn set_terminal_attr(termios: &Termios) -> Result<()> {
extern "C" { extern "C" {
pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int; pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
} }
unsafe { unsafe {
if tcsetattr(0, 0, termios) == -1 { 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(()) Ok(())
} }

View File

@ -54,19 +54,20 @@ impl TerminalRestorer {
let mut t = self.terminal()?; let mut t = self.terminal()?;
let mut palette = TerminalPalette::default(); let mut palette = TerminalPalette::default();
palette.load(&mut t) 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) Ok(palette)
} }
fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> { fn apply_palette(&self, palette: &TerminalPalette) -> Result<()> {
let mut t = self.terminal()?; let mut t = self.terminal()?;
palette.apply(&mut t) 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> { fn terminal(&self) -> Result<AnsiTerminal> {
AnsiTerminal::new() 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<S: AsRef<str>>(&self, slug: S) { pub fn apply_base16_by_slug<S: AsRef<str>>(&self, slug: S) {
@ -84,11 +85,10 @@ impl TerminalRestorer {
fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> { fn apply_base16(&self, scheme: &Base16Scheme) -> Result<()> {
let mut t = self.terminal()?; let mut t = self.terminal()?;
t.apply_base16(scheme) 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() t.clear_screen()
.map_err(|e| format_err!("error clearing screen: {}", e)) .map_err(context!("error clearing screen"))
} }
} }
impl Drop for TerminalRestorer { impl Drop for TerminalRestorer {

View File

@ -2,16 +2,16 @@ use std::path::{Path,PathBuf};
use std::process::{Command,Stdio}; use std::process::{Command,Stdio};
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::os::unix::fs as unixfs;
use std::env; use std::env;
use std::fs::{self,File}; use std::fs::{self, File, DirEntry};
use std::ffi::CString; use std::ffi::CString;
use std::io::{self, Seek, Read, BufReader, SeekFrom}; use std::io::{self, Seek, Read, BufReader, SeekFrom};
use failure::ResultExt;
use walkdir::WalkDir; use walkdir::WalkDir;
use libc; use libc;
use crate::Result; use crate::{Result, util};
pub fn is_valid_name(name: &str, maxsize: usize) -> bool { pub fn is_valid_name(name: &str, maxsize: usize) -> bool {
name.len() <= maxsize && name.len() <= maxsize &&
@ -36,14 +36,14 @@ pub fn is_first_char_alphabetic(s: &str) -> bool {
} }
fn search_path(filename: &str) -> Result<PathBuf> { fn search_path(filename: &str) -> Result<PathBuf> {
let path_var = env::var("PATH")?; let path_var = env::var("PATH").unwrap_or("".into());
for mut path in env::split_paths(&path_var) { for mut path in env::split_paths(&path_var) {
path.push(filename); path.push(filename);
if path.exists() { if path.exists() {
return Ok(path); 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<()> { pub fn ensure_command_exists(cmd: &str) -> Result<()> {
@ -54,14 +54,13 @@ pub fn ensure_command_exists(cmd: &str) -> Result<()> {
} else if path.exists() { } else if path.exists() {
return Ok(()) return Ok(())
} }
Err(format_err!("Cannot execute '{}': command does not exist", cmd)) bail!("cannot execute '{}': command does not exist", cmd)
} }
pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> { pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> {
let path = path.as_ref(); let path = path.as_ref();
let output = cmd_with_output!("/usr/bin/sha256sum", "{}", path.display()) 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(); let v: Vec<&str> = output.split_whitespace().collect();
Ok(v[0].trim().to_owned()) Ok(v[0].trim().to_owned())
@ -75,14 +74,17 @@ pub enum FileRange {
} }
fn ranged_reader<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> { fn ranged_reader<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> {
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 { let offset = match range {
FileRange::All => 0, FileRange::All => 0,
FileRange::Offset(n) => n, FileRange::Offset(n) => n,
FileRange::Range {offset, .. } => offset, FileRange::Range {offset, .. } => offset,
}; };
if offset > 0 { 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); let r = BufReader::new(f);
if let FileRange::Range {len, ..} = range { if let FileRange::Range {len, ..} = range {
@ -107,26 +109,26 @@ pub fn exec_cmdline_pipe_input<S,P>(cmd_path: &str, args: S, input: P, range: Fi
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
.spawn() .spawn()
.context(format!("unable to execute {}", cmd_path))?; .map_err(context!("unable to execute {}", cmd_path))?;
let stdin = child.stdin.as_mut().unwrap(); let stdin = child.stdin.as_mut().unwrap();
io::copy(&mut r, stdin)?; io::copy(&mut r, stdin)
let output = child.wait_with_output()?; .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()) Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned())
} }
pub fn xz_compress<P: AsRef<Path>>(path: P) -> Result<()> { pub fn xz_compress<P: AsRef<Path>>(path: P) -> Result<()> {
let path = path.as_ref(); let path = path.as_ref();
cmd!("/usr/bin/xz", "-T0 {}", path.display()) cmd!("/usr/bin/xz", "-T0 {}", path.display())
.context(format!("failed to compress {}", path.display()))?; .map_err(context!("failed to compress {:?}", path))
Ok(())
} }
pub fn xz_decompress<P: AsRef<Path>>(path: P) -> Result<()> { pub fn xz_decompress<P: AsRef<Path>>(path: P) -> Result<()> {
let path = path.as_ref(); let path = path.as_ref();
cmd!("/usr/bin/xz", "-d {}", path.display()) cmd!("/usr/bin/xz", "-d {}", path.display())
.context(format!("failed to decompress {}", path.display()))?; .map_err(context!("failed to decompress {:?}", path))
Ok(())
} }
pub fn mount<P: AsRef<Path>>(source: impl AsRef<str>, target: P, options: Option<&str>) -> Result<()> { pub fn mount<P: AsRef<Path>>(source: impl AsRef<str>, target: P, options: Option<&str>) -> Result<()> {
@ -136,39 +138,145 @@ pub fn mount<P: AsRef<Path>>(source: impl AsRef<str>, target: P, options: Option
cmd!("/usr/bin/mount", "{} {} {}", options, source, target.display()) cmd!("/usr/bin/mount", "{} {} {}", options, source, target.display())
} else { } else {
cmd!("/usr/bin/mount", "{} {}", source, target.display()) cmd!("/usr/bin/mount", "{} {}", source, target.display())
} }.map_err(context!("failed to mount {} to {:?}", source, target))
} }
pub fn umount<P: AsRef<Path>>(path: P) -> Result<()> { pub fn umount<P: AsRef<Path>>(path: P) -> Result<()> {
let path = path.as_ref(); let path = path.as_ref();
cmd!("/usr/bin/umount", "{}", path.display()) cmd!("/usr/bin/umount", "{}", path.display())
.map_err(context!("failed to unmount {:?}", path))
} }
pub fn chown_user<P: AsRef<Path>>(path: P) -> io::Result<()> { pub fn chown_user<P: AsRef<Path>>(path: P) -> Result<()> {
chown(path.as_ref(), 1000, 1000) chown(path.as_ref(), 1000, 1000)
} }
pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { pub fn chown(path: &Path, uid: u32, gid: u32) -> Result<()> {
let cstr = CString::new(path.as_os_str().as_bytes())?; let cstr = CString::new(path.as_os_str().as_bytes())
.expect("path contains null byte");
unsafe { unsafe {
if libc::chown(cstr.as_ptr(), uid, gid) == -1 { 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(()) 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<Path>, to: impl AsRef<Path>) -> 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<Path>, dst: impl AsRef<Path>) -> 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<F>(dir: impl AsRef<Path>, 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<Path>) -> 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<Path>) -> 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<Path>, 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<Path>) -> Result<String> {
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<Path>, to: impl AsRef<Path>) -> 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<()> { fn copy_path(from: &Path, to: &Path, chown_to: Option<(u32,u32)>) -> Result<()> {
if to.exists() { if to.exists() {
bail!("destination path {} already exists which is not expected", to.display()); 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() { if from.is_dir() {
fs::create_dir(to)?; util::create_dir(to)?;
} else { } else {
fs::copy(&from, &to)?; util::copy_file(&from, &to)?;
} }
if let Some((uid,gid)) = chown_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<()> { fn _copy_tree(from_base: &Path, to_base: &Path, chown_to: Option<(u32,u32)>) -> Result<()> {
for entry in WalkDir::new(from_base) { for entry in WalkDir::new(from_base) {
let path = entry?.path().to_owned(); let entry = entry.map_err(|e| format_err!("Error walking directory tree: {}", e))?;
let to = to_base.join(path.strip_prefix(from_base)?); let path = entry.path();
if to != to_base { let suffix = path.strip_prefix(from_base)
copy_path(&path, &to, chown_to) .map_err(|_| format_err!("Failed to strip prefix from {:?}", path))?;
.map_err(|e| format_err!("failed to copy {} to {}: {}", path.display(), to.display(), e))?; 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(()) 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<()> { pub fn chown_tree(base: &Path, chown_to: (u32,u32), include_base: bool) -> Result<()> {
for entry in WalkDir::new(base) { 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 { 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(()) Ok(())

View File

@ -1,9 +1,9 @@
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{self, OpenOptions,File}; use std::fs::{OpenOptions,File};
use std::io; use std::io;
use crate::{Result, MetaInfo, Partition, LoopDevice, ImageHeader}; use crate::{Result, MetaInfo, Partition, LoopDevice, ImageHeader, util};
use std::sync::Arc; use std::sync::Arc;
@ -42,21 +42,25 @@ impl Verity {
let verityfile = self.image.with_extension("verity"); let verityfile = self.image.with_extension("verity");
// Make sure file size is correct or else verity tree will be appended in wrong place // 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 len = meta.len() as usize;
let expected = (nblocks + 1) * 4096; let expected = (nblocks + 1) * 4096;
if len != expected { 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 vout = LoopDevice::with_loop(self.path(), Some(4096), true, |loopdev| {
let output = cmd_with_output!(Self::VERITYSETUP, "--data-blocks={} --salt={} format {} {}", let output = cmd_with_output!(Self::VERITYSETUP, "--data-blocks={} --salt={} format {} {}",
nblocks, salt, loopdev, verityfile.display())?; nblocks, salt, loopdev, verityfile.display())?;
Ok(VerityOutput::parse(&output)) Ok(VerityOutput::parse(&output))
})?; })?;
let mut input = File::open(&verityfile)?; let mut input = File::open(&verityfile)
let mut output = OpenOptions::new().append(true).open(self.path())?; .map_err(context!("failed to open temporary verity hashtree file {:?}", verityfile))?;
io::copy(&mut input, &mut output)?; let mut output = OpenOptions::new().append(true).open(self.path())
fs::remove_file(&verityfile)?; .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) Ok(vout)
} }
@ -104,9 +108,7 @@ impl Verity {
let nblocks = metainfo.nblocks(); let nblocks = metainfo.nblocks();
let verity_root = metainfo.verity_root(); let verity_root = metainfo.verity_root();
cmd!(Self::VERITYSETUP, "--hash-offset={} --data-blocks={} create {} {} {} {}", cmd!(Self::VERITYSETUP, "--hash-offset={} --data-blocks={} create {} {} {} {}",
nblocks * 4096, nblocks, devname, srcdev, srcdev, verity_root)?; nblocks * 4096, nblocks, devname, srcdev, srcdev, verity_root)
Ok(())
} }
fn path(&self) -> &Path { fn path(&self) -> &Path {