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 = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libcitadel 0.1.0",
@ -635,7 +634,6 @@ dependencies = [
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"dbus 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"inotify 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -207,7 +207,7 @@ impl ConfigDialog {
let path = self.realm.base_path_file("config");
if let Err(e) = self.realm.config().write_config(&path) {
if let Err(e) = self.realm.config().write_to(&path) {
warn!("Error writing config file {}: {}", path.display(), e);
}
info!("Config file written to {}", path.display());

View File

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

View File

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

View File

@ -1,7 +1,6 @@
use std::path::{Path, PathBuf};
use std::fs;
use libcitadel::Result;
use libcitadel::{Result, util};
///
/// Represents a disk partition device on the system
@ -21,12 +20,12 @@ impl DiskPartition {
/// Return list of all vfat partitions on the system as a `Vec<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();
for line in pp.lines().skip(2)
{
let part = DiskPartition::from_proc_line(&line)
.map_err(|e| format_err!("Failed to parse line '{}': {}", line, e))?;
.map_err(context!("failed to parse line '{}'", line))?;
if part.is_boot_partition(check_guid)? {
v.push(part);

View File

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

View File

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

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 std::path::Path;
use std::process::Stdio;
use libcitadel::verity::Verity;
pub fn setup_rootfs() -> Result<()> {
@ -45,11 +44,11 @@ fn setup_partition_verified(p: &mut Partition) -> Result<()> {
info!("Creating /dev/mapper/rootfs dm-verity device");
if !CommandLine::nosignatures() {
if !p.has_public_key() {
bail!("No public key available for channel {}", p.metainfo().channel())
bail!("no public key available for channel {}", p.metainfo().channel())
}
if !p.is_signature_valid() {
p.write_status(ImageHeader::STATUS_BAD_SIG)?;
bail!("Signature verification failed on partition");
bail!("signature verification failed on partition");
}
info!("Image signature is valid for channel {}", p.metainfo().channel());
}
@ -71,7 +70,7 @@ fn setup_linear_mapping(blockdev: &Path) -> Result<()> {
.success();
if !ok {
bail!("Failed to set up linear identity mapping with /usr/sbin/dmsetup");
bail!("failed to set up linear identity mapping with /usr/sbin/dmsetup");
}
Ok(())
}
@ -89,7 +88,7 @@ fn choose_boot_partiton(scan: bool) -> Result<Partition> {
for p in partitions {
best = compare_boot_partitions(best, p);
}
best.ok_or_else(|| format_err!("No partition found to boot from"))
best.ok_or_else(|| format_err!("No partition found to boot from").into())
}
fn compare_boot_partitions(a: Option<Partition>, b: Partition) -> Option<Partition> {

View File

@ -3,8 +3,7 @@ use std::process::exit;
use clap::{App,Arg,SubCommand,ArgMatches};
use clap::AppSettings::*;
use libcitadel::{Result,ResourceImage,Logger,LogLevel,format_error,Partition,KeyPair,ImageHeader};
use std::fs;
use libcitadel::{Result, ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
use hex;
pub fn main(args: Vec<String>) {
@ -89,7 +88,7 @@ pub fn main(args: Vec<String>) {
};
if let Err(ref e) = result {
println!("Error: {}", format_error(e));
println!("Error: {}", e);
exit(1);
}
}
@ -252,8 +251,7 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> {
if image_dest.exists() {
rotate(&image_dest)?;
}
fs::rename(source,image_dest)?;
Ok(())
util::rename(source, &image_dest)
}
fn rotate(path: &Path) -> Result<()> {
@ -262,11 +260,8 @@ fn rotate(path: &Path) -> Result<()> {
}
let filename = path.file_name().unwrap();
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
if dot_zero.exists() {
fs::remove_file(&dot_zero)?;
}
fs::rename(path, &dot_zero)?;
Ok(())
util::remove_file(&dot_zero)?;
util::rename(path, &dot_zero)
}
fn genkeys() -> Result<()> {
@ -334,5 +329,5 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
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);
let passphrase = match read_passphrase()? {
let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? {
Some(passphrase) => passphrase,
None => return Ok(false),
};
@ -29,7 +29,7 @@ pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
let disk = find_disk_by_path(target.as_ref())?;
display_disk(&disk);
let passphrase = match read_passphrase()? {
let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? {
Some(passphrase) => passphrase,
None => return Ok(false),
};
@ -66,17 +66,17 @@ fn find_disk_by_path(path: &Path) -> Result<Disk> {
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>> {
let disks = Disk::probe_all()?;
if disks.is_empty() {
bail!("No disks found.");
bail!("no disks found.");
}
loop {
prompt_choose_disk(&disks)?;
prompt_choose_disk(&disks);
let line = read_line()?;
if line == "q" || line == "Q" {
return Ok(None);
@ -89,26 +89,26 @@ fn choose_disk() -> Result<Option<Disk>> {
}
}
fn prompt_choose_disk(disks: &[Disk]) -> Result<()> {
fn prompt_choose_disk(disks: &[Disk]) {
println!("Available disks:\n");
for (idx,disk) in disks.iter().enumerate() {
println!(" [{}]: {} Size: {} Model: {}", idx + 1, disk.path().display(), disk.size_str(), disk.model());
}
print!("\nChoose a disk to install to (q to quit): ");
io::stdout().flush()?;
Ok(())
let _ = io::stdout().flush();
}
fn read_line() -> Result<String> {
let mut input = String::new();
io::stdin().read_line(&mut input)?;
io::stdin().read_line(&mut input)
.map_err(context!("error reading line from stdin"))?;
if input.ends_with('\n') {
input.pop();
}
Ok(input)
}
fn read_passphrase() -> Result<Option<String>> {
fn read_passphrase() -> io::Result<Option<String>> {
loop {
println!("Enter a disk encryption passphrase (or 'q' to quit)");
println!();
@ -141,7 +141,7 @@ fn confirm_install(disk: &Disk) -> Result<bool> {
println!(" Model: {}", disk.model());
println!();
print!("Type YES (uppercase) to continue with install: ");
io::stdout().flush()?;
let _ = io::stdout().flush();
let answer = read_line()?;
Ok(answer == "YES")
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,8 @@ pub struct IconCache {
impl IconCache {
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 })
}
@ -46,7 +47,8 @@ impl IconCache {
let mut output = String::new();
let mut nread = 0;
loop {
let n = self.file.read_at(&mut buf, (offset + nread)as u64 )?;
let n = self.file.read_at(&mut buf, (offset + nread)as u64 )
.map_err(context!("failed to read from icon cache at offset {}", (offset + nread)))?;
if n == 0 {
return Ok(output);
}
@ -80,7 +82,8 @@ impl IconCache {
fn read_exact_at(&self, buf: &mut [u8], offset: usize) -> Result<()> {
let mut nread = 0;
while nread < buf.len() {
let sz = self.file.read_at(&mut buf[nread..], (offset + nread) as u64)?;
let sz = self.file.read_at(&mut buf[nread..], (offset + nread) as u64)
.map_err(context!("failed to read from icon cache at offset {}", (offset + nread)))?;
nread += sz;
if sz == 0 {
bail!("bad offset");

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
use std::path::Path;
use std::fs::File;
use std::fs::{File,OpenOptions};
use std::io::{Read,Write,Seek,SeekFrom};
use std::os::unix::io::AsRawFd;
use std::fs::OpenOptions;
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use libc;
use crate::Result;
@ -112,7 +112,8 @@ impl BlockDev {
oo.write(true);
}
let file = oo.open(path)
.map_err(|e| format_err!("Failed to open block device {}: {}", path.display(), e))?;
.map_err(context!("failed to open block device {:?}", path))?;
Ok(BlockDev{file})
}
@ -121,7 +122,7 @@ impl BlockDev {
let mut sz = 0u64;
unsafe {
blk_getsize64(self.file.as_raw_fd(), &mut sz)
.map_err(|e| format_err!("Error calling getsize ioctl on block device: {}", e))?;
.map_err(context!("error calling getsize ioctl on block device"))?;
}
Ok(sz)
}
@ -138,16 +139,18 @@ impl BlockDev {
fn setup_io(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
let addr = buffer.as_ptr() as usize;
if addr & ALIGNMENT_MASK != 0 {
bail!("block device i/o attempted with incorrectly aligned buffer: {:p}", buffer);
bail!("block device I/O attempted with incorrectly aligned buffer address: {:#x}", addr);
}
if buffer.len() % SECTOR_SIZE != 0 {
bail!("buffer length {} is not a multiple of sector size", buffer.len());
bail!("buffer length ({}) is not a multiple of sector size", buffer.len());
}
let count = buffer.len() / SECTOR_SIZE;
if offset + count > self.nsectors()? {
bail!("sector_io({}, {}) is past end of device", offset, buffer.len());
bail!("block device sector i/o ({},{}) is past end of device", offset, buffer.len());
}
self.file.seek(SeekFrom::Start((offset * SECTOR_SIZE) as u64))?;
self.file.seek(SeekFrom::Start((offset * SECTOR_SIZE) as u64))
.map_err(context!("I/O error accessing block device"))?;
Ok(())
}
@ -155,16 +158,15 @@ impl BlockDev {
/// The buffer must be a multiple of sector size (512 bytes).
pub fn read_sectors(&mut self, offset: usize, buffer: &mut [u8]) -> Result<()> {
self.setup_io(offset, buffer)?;
self.file.read_exact(buffer)?;
Ok(())
self.file.read_exact(buffer)
.map_err(context!("I/O error reading from block device"))
}
/// Write sectors from `buffer` to device starting at sector `offset`.
/// The buffer must be a multiple of sector size (512 bytes).
pub fn write_sectors(&mut self, offset: usize, buffer: &[u8]) -> Result<()> {
self.setup_io(offset, buffer)?;
self.file.write_all(buffer)?;
Ok(())
self.file.write_all(buffer)
.map_err(context!("I/O error writing to block device"))
}
}

View File

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

View File

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

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 result = self.cmd
.args(args)
.output()?;
.output()
.map_err(context!("failed to execute command {}", self.cmd_name))?;
for line in BufReader::new(result.stderr.as_slice()).lines() {
verbose!(" {}", line?);
verbose!(" {}", line.unwrap());
}
self.check_cmd_status(result.status)
}
@ -64,7 +65,8 @@ impl Exec {
let args: Vec<&str> = args.as_ref().split_whitespace().collect();
let status = self.cmd
.args(args)
.status()?;
.status()
.map_err(context!("failed to execute command {}", self.cmd_name))?;
Ok(status.success())
}
@ -72,7 +74,9 @@ impl Exec {
pub fn output(&mut self, args: impl AsRef<str>) -> Result<String> {
self.ensure_command_exists()?;
self.add_args(args.as_ref());
let result = self.cmd.stderr(Stdio::inherit()).output()?;
let result = self.cmd.stderr(Stdio::inherit())
.output()
.map_err(context!("failed to execute command {}", self.cmd_name))?;
self.check_cmd_status(result.status)?;
Ok(String::from_utf8(result.stdout).unwrap().trim().to_owned())
}
@ -90,11 +94,14 @@ impl Exec {
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()?;
.spawn()
.map_err(context!("failed to execute command {}", self.cmd_name))?;
let stdin = child.stdin.as_mut().unwrap();
io::copy(&mut r, stdin)?;
let output = child.wait_with_output()?;
io::copy(&mut r, stdin)
.map_err(context!("error writing to stdin of command {}", self.cmd_name))?;
let output = child.wait_with_output()
.map_err(context!("error waiting for child command {} to exit", self.cmd_name))?;
Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned())
}
@ -121,18 +128,18 @@ impl Exec {
} else if path.exists() {
return Ok(())
}
Err(format_err!("Cannot execute '{}': command does not exist", self.cmd_name))
bail!("cannot execute '{}': command does not exist", self.cmd_name)
}
fn search_path(filename: &str) -> Result<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) {
path.push(filename);
if path.exists() {
return Ok(path);
}
}
Err(format_err!("Could not find {} in $PATH", filename))
bail!("could not find {} in $PATH", filename)
}
}
@ -144,14 +151,16 @@ pub enum FileRange {
fn ranged_reader<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 {
FileRange::All => 0,
FileRange::Offset(n) => n,
FileRange::Range {offset, ..} => offset,
};
if offset > 0 {
f.seek(SeekFrom::Start(offset as u64))?;
f.seek(SeekFrom::Start(offset as u64))
.map_err(context!("failed to seek to offset {} of input file {:?}", offset, path.as_ref()))?;
}
let r = BufReader::new(f);
if let FileRange::Range {len, ..} = range {

View File

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

View File

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

View File

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

View File

@ -1,22 +1,8 @@
#[macro_use] extern crate failure;
#[macro_use] extern crate nix;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate lazy_static;
use std::result;
use failure::Error;
pub fn format_error(err: &Error) -> String {
let mut output = err.to_string();
let mut prev = err.as_fail();
while let Some(next) = prev.cause() {
output.push_str(": ");
output.push_str(&next.to_string());
prev = next;
}
output
}
#[macro_use] pub mod error;
#[macro_use] mod log;
#[macro_use] mod exec;
mod blockdev;
@ -87,7 +73,7 @@ pub fn public_key_for_channel(channel: &str) -> Result<Option<PublicKey>> {
Ok(None)
}
pub type Result<T> = result::Result<T,Error>;
pub use error::{Result,Error};
pub const BLOCK_SIZE: usize = 4096;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,7 @@
use std::fs;
use std::fmt::Write;
use crate::{Realm,Result};
use std::fmt::{self,Write};
use std::path::{Path, PathBuf};
use crate::realm::network::NetworkConfig;
use crate::{Realm, Result, util, realm::network::NetworkConfig};
const NSPAWN_FILE_TEMPLATE: &str = "\
[Exec]
@ -77,15 +75,8 @@ impl <'a> RealmLauncher <'a> {
}
pub fn remove_launch_config_files(&self) -> Result<()> {
let nspawn_path = self.realm_nspawn_path();
if nspawn_path.exists() {
fs::remove_file(&nspawn_path)?;
}
let service_path = self.realm_service_path();
if service_path.exists() {
fs::remove_file(&service_path)?;
}
Ok(())
util::remove_file(self.realm_nspawn_path())?;
util::remove_file(self.realm_service_path())
}
pub fn write_launch_config_files(&mut self, rootfs: &Path, netconfig: &mut NetworkConfig) -> Result<()> {
@ -94,15 +85,11 @@ impl <'a> RealmLauncher <'a> {
}
let nspawn_path = self.realm_nspawn_path();
let nspawn_content = self.generate_nspawn_file(netconfig)?;
self.write_launch_config_file(&nspawn_path, &nspawn_content)
.map_err(|e| format_err!("failed to write nspawn config file {}: {}", nspawn_path.display(), e))?;
self.write_launch_config_file(&nspawn_path, &nspawn_content)?;
let service_path = self.realm_service_path();
let service_content = self.generate_service_file(rootfs);
self.write_launch_config_file(&service_path, &service_content)
.map_err(|e| format_err!("failed to write service config file {}: {}", service_path.display(), e))?;
Ok(())
}
pub fn realm_service_name(&self) -> &str {
@ -113,15 +100,10 @@ impl <'a> RealmLauncher <'a> {
/// not already exist, create it.
fn write_launch_config_file(&self, path: &Path, content: &str) -> Result<()> {
match path.parent() {
Some(parent) => {
if !parent.exists() {
fs::create_dir_all(parent)?;
}
},
Some(parent) => util::create_dir(parent)?,
None => bail!("config file path {} has no parent?", path.display()),
};
fs::write(path, content)?;
Ok(())
util::write_file(path, content)
}
fn generate_nspawn_file(&mut self, netconfig: &mut NetworkConfig) -> Result<String> {
@ -238,4 +220,10 @@ impl <'a> RealmLauncher <'a> {
fn realm_nspawn_path(&self) -> PathBuf {
PathBuf::from(SYSTEMD_NSPAWN_PATH).join(format!("{}.nspawn", self.realm.name()))
}
}
impl From<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::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
@ -205,7 +204,7 @@ impl RealmManager {
let home = realm.base_path_file("home");
if !home.exists() {
warn!("No home directory exists at {}, creating an empty directory", home.display());
fs::create_dir_all(&home)?;
util::create_dir(&home)?;
util::chown_user(&home)?;
}
@ -226,10 +225,9 @@ impl RealmManager {
fn create_realm_namefile(&self, realm: &Realm) -> Result<()> {
let namefile = realm.run_path_file("realm-name");
fs::write(&namefile, realm.name())?;
util::write_file(&namefile, realm.name())?;
self.systemd.machinectl_copy_to(realm, &namefile, "/run/realm-name")?;
fs::remove_file(&namefile)?;
Ok(())
util::remove_file(&namefile)
}
fn start_realm_dependencies(&self, realm: &Realm, starting: &mut HashSet<String>) -> Result<()> {
@ -331,13 +329,14 @@ impl RealmManager {
// ensure that /proc/pid/root/run and /proc/pid/root/run/realm-name
// are not symlinks
let run_meta = run.symlink_metadata()?;
let name_meta = realm_name.symlink_metadata()?;
let run_meta = run.symlink_metadata()
.map_err(context!("failed reading symlink metadata {:?}", run))?;
let name_meta = realm_name.symlink_metadata()
.map_err(context!("failed reading symlink metadata {:?}", realm_name))?;
if !run_meta.file_type().is_dir() || !name_meta.file_type().is_file() {
bail!("invalid path");
}
let bytes = fs::read(realm_name)?;
Ok(String::from_utf8(bytes)?)
util::read_to_string(&realm_name)
}
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
@ -381,7 +380,6 @@ impl RealmManager {
}
self.inner_mut().realmfs_set.remove(realmfs.name());
info!("Removing RealmFS image file {}", realmfs.path().display());
fs::remove_file(realmfs.path())?;
Ok(())
util::remove_file(realmfs.path())
}
}

View File

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

View File

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

View File

@ -187,11 +187,9 @@ impl Realm {
/// to order the output when listing realms.
pub fn update_timestamp(&self) -> Result<()> {
let tstamp = self.base_path().join(".tstamp");
if tstamp.exists() {
fs::remove_file(&tstamp)?;
}
util::remove_file(&tstamp)?;
fs::File::create(&tstamp)
.map_err(|e| format_err!("failed to create timestamp file {}: {}", tstamp.display(), e))?;
.map_err(context!("failed to create timestamp file {:?}", tstamp))?;
// also load the new value
self.inner_mut().timestamp = self.load_timestamp();
Ok(())
@ -309,7 +307,7 @@ impl Realm {
// not exist, create it as a fork of 'base'
base.fork(default)
} else {
Err(format_err!("Default RealmFS '{}' does not exist and neither does 'base'", default))
bail!("Default RealmFS '{}' does not exist and neither does 'base'", default)
}
}
@ -459,11 +457,10 @@ impl Realm {
let path = self.base_path_file("notes");
let notes = notes.as_ref();
if path.exists() && notes.is_empty() {
fs::remove_file(path)?;
util::remove_file(&path)
} else {
fs::write(path, notes)?;
util::write_file(&path, notes)
}
Ok(())
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,10 @@
use std::fs::{self,File,DirEntry};
use std::fs::{File,DirEntry};
use std::ffi::OsStr;
use std::io::{self,Seek,SeekFrom};
use std::path::{Path, PathBuf};
use crate::{CommandLine, OsRelease, ImageHeader, MetaInfo, Result, Partition, Mounts, util, LoopDevice};
use crate::{Result, CommandLine, OsRelease, ImageHeader, MetaInfo, Partition, Mounts, util, LoopDevice};
use failure::ResultExt;
use std::sync::Arc;
use crate::UtsName;
use crate::verity::Verity;
@ -49,7 +48,7 @@ impl ResourceImage {
}
if !Self::ensure_storage_mounted()? {
bail!("Unable to mount /storage");
bail!("unable to mount /storage");
}
let storage_path = Path::new(STORAGE_BASEDIR).join(&channel);
@ -58,7 +57,7 @@ impl ResourceImage {
return Ok(image);
}
Err(format_err!("Failed to find resource image of type: {}", image_type))
bail!("failed to find resource image of type: {}", image_type)
}
pub fn mount_image_type(image_type: &str) -> Result<()> {
@ -70,14 +69,14 @@ impl ResourceImage {
pub fn find_rootfs() -> Result<Self> {
match search_directory(RUN_DIRECTORY, "rootfs", None)? {
Some(image) => Ok(image),
None => Err(format_err!("Failed to find rootfs resource image")),
None => bail!("failed to find rootfs resource image"),
}
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let header = ImageHeader::from_file(path.as_ref())?;
if !header.is_magic_valid() {
bail!("Image file {} does not have a valid header", path.as_ref().display());
bail!("image file {:?} does not have a valid header", path.as_ref());
}
Ok(Self::new(path.as_ref(), header ))
}
@ -143,25 +142,28 @@ impl ResourceImage {
return Ok(())
}
info!("decompressing image file {}", self.path().display());
let mut reader = File::open(self.path())?;
reader.seek(SeekFrom::Start(4096))?;
let mut reader = File::open(self.path())
.map_err(context!("error opening image file {:?}", self.path()))?;
reader.seek(SeekFrom::Start(4096))
.map_err(context!("error seeking to offset 4096 in image file {:?}", self.path()))?;
let xzfile = self.path.with_extension("tmp.xz");
let mut out = File::create(&xzfile)?;
io::copy(&mut reader, &mut out)?;
let mut out = File::create(&xzfile)
.map_err(context!("error creating temporary file {:?}", xzfile))?;
io::copy(&mut reader, &mut out)
.map_err(context!("error copying image file {:?} to temporary file", self.path()))?;
util::xz_decompress(xzfile)?;
fs::rename(self.path.with_extension("tmp"), self.path())?;
let tmpfile = self.path.with_extension("tmp");
util::rename(&tmpfile, self.path())?;
self.header.clear_flag(ImageHeader::FLAG_DATA_COMPRESSED);
self.header.write_header_to(self.path())?;
Ok(())
self.header.write_header_to(self.path())
}
pub fn write_to_partition(&self, partition: &Partition) -> Result<()> {
if self.metainfo().image_type() != "rootfs" {
bail!("Cannot write to partition, image type is not rootfs");
bail!("cannot write to partition, image type is not rootfs");
}
if !self.has_verity_hashtree() {
@ -183,7 +185,7 @@ impl ResourceImage {
info!("Mounting dm-verity device to {}", mount_path.display());
fs::create_dir_all(mount_path)?;
util::create_dir(mount_path)?;
util::mount(verity_path, mount_path, None)?;
Ok(ResourceMount::new_verity(mount_path, verity_dev))
@ -195,11 +197,11 @@ impl ResourceImage {
match self.header.public_key()? {
Some(pubkey) => {
if !self.header.verify_signature(pubkey) {
bail!("Header signature verification failed");
bail!("header signature verification failed");
}
info!("Image header signature is valid");
}
None => bail!("Cannot verify header signature because no public key for channel {} is available", self.metainfo().channel())
None => bail!("cannot verify header signature because no public key for channel {} is available", self.metainfo().channel())
}
}
info!("Setting up dm-verity device for image");
@ -240,7 +242,7 @@ impl ResourceImage {
}
info!("Calculating sha256 of image");
let output = util::exec_cmdline_pipe_input("sha256sum", "-", self.path(), util::FileRange::Range{offset: 4096, len: self.metainfo().nblocks() * 4096})
.context(format!("failed to calculate sha256 on {}", self.path().display()))?;
.map_err(context!("failed to calculate sha256 on {:?}", self.path()))?;
let v: Vec<&str> = output.split_whitespace().collect();
let shasum = v[0].trim().to_owned();
Ok(shasum)
@ -261,7 +263,7 @@ impl ResourceImage {
info!("Loop device created: {}", loopdev);
info!("Mounting to: {}", mount_path.display());
fs::create_dir_all(mount_path)?;
util::create_dir(mount_path)?;
util::mount(&loopdev.device_str(), mount_path, Some("-oro"))?;
@ -286,7 +288,8 @@ impl ResourceImage {
warn!("No manifest file found for resource image: {}", self.path.display());
return Ok(())
}
let s = fs::read_to_string(manifest)?;
let s = util::read_to_string(manifest)?;
for line in s.lines() {
if let Err(e) = self.process_manifest_line(&line) {
warn!("Processing manifest file for resource image ({}): {}", self.path.display(), e);
@ -416,7 +419,7 @@ fn parse_timestamp(img: &ResourceImage) -> Result<usize> {
let ts = img.metainfo()
.timestamp()
.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)
}
@ -441,9 +444,10 @@ fn all_matching_images(dir: &Path, image_type: &str, channel: Option<&str>) -> R
let kernel_id = OsRelease::citadel_kernel_id();
let mut v = Vec::new();
for entry in fs::read_dir(dir)? {
maybe_add_dir_entry(entry?, image_type, channel, kv, kernel_id, &mut v)?;
}
util::read_directory(dir, |dent| {
maybe_add_dir_entry(dent, image_type, channel, kv, kernel_id, &mut v)?;
Ok(())
})?;
Ok(v)
}
@ -454,7 +458,7 @@ fn all_matching_images(dir: &Path, image_type: &str, channel: Option<&str>) -> R
//
// If the entry is a match, then instantiate a ResourceImage and add it to
// the images vector.
fn maybe_add_dir_entry(entry: DirEntry,
fn maybe_add_dir_entry(entry: &DirEntry,
image_type: &str,
channel: Option<&str>,
kernel_version: Option<&str>,
@ -465,7 +469,8 @@ fn maybe_add_dir_entry(entry: DirEntry,
if Some(OsStr::new("img")) != path.extension() {
return Ok(())
}
let meta = entry.metadata()?;
let meta = entry.metadata()
.map_err(context!("failed to read metadata for {:?}", entry.path()))?;
if meta.len() < ImageHeader::HEADER_SIZE as u64 {
return Ok(())
}

View File

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

View File

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

View File

@ -39,7 +39,7 @@ impl LoopDevice {
let result = f(&loopdev);
let detach_result = loopdev.detach();
let r = result?;
detach_result.map_err(|e| format_err!("error detaching loop device: {}", e))?;
detach_result.map_err(context!("error detaching loop device"))?;
Ok(r)
}
@ -108,8 +108,7 @@ impl LoopDevice {
// mount --bind olddir newdir
// mount -o remount,bind,ro olddir newdir
cmd!(Self::MOUNT, "--bind {} {}", rw.display(), ro.display())?;
cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display())?;
Ok(())
cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display())
}
}

View File

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

View File

@ -2,6 +2,7 @@
use crate::Result;
use crate::terminal::{RawTerminal, Color, Base16Scheme};
use std::io::{self,Read,Write,Stdout};
use std::string::FromUtf8Error;
#[derive(Default)]
pub struct AnsiControl(String);
@ -125,10 +126,9 @@ impl AnsiControl {
io::stdout().flush().unwrap();
}
pub fn write_to<W: Write>(&self, mut writer: W) -> Result<()> {
writer.write_all(self.as_bytes())?;
writer.flush()?;
Ok(())
fn write_to<W: Write>(&self, mut writer: W) -> Result<()> {
writer.write_all(self.as_bytes()).map_err(context!("error writing to terminal"))?;
writer.flush().map_err(context!("error calling flush() on terminal"))
}
}
@ -137,7 +137,6 @@ pub struct AnsiTerminal {
raw: RawTerminal<Stdout>,
}
impl AnsiTerminal {
pub fn new() -> Result<Self> {
let raw = RawTerminal::create(io::stdout())?;
@ -210,18 +209,24 @@ impl AnsiTerminal {
}
fn write_code(&mut self, sequence: AnsiControl) -> Result<()> {
self._write_code(sequence)
.map_err(context!("error writing ansi sequence to terminal"))
}
fn _write_code(&mut self, sequence: AnsiControl) -> io::Result<()> {
self.raw.write_all(sequence.as_bytes())?;
self.raw.flush()?;
Ok(())
self.raw.flush()
}
fn read_response(&mut self) -> Result<String> {
let stdin = io::stdin();
let mut input = stdin.lock();
let mut buffer = Vec::new();
input.read_to_end(&mut buffer)?;
let s = String::from_utf8(buffer)?;
Ok(s)
input.read_to_end(&mut buffer)
.map_err(context!("error reading terminal input"))?;
String::from_utf8(buffer)
.map_err(context!("terminal input is not correct utf-8"))
}
pub fn apply_base16(&mut self, base16: &Base16Scheme) -> Result<()> {
@ -235,3 +240,9 @@ impl AnsiTerminal {
}
}
impl From<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::{Realm, Result, util, RealmManager};
use std::path::Path;
use std::fs;
use std::{fs, io};
use std::io::Write;
lazy_static! {
@ -123,7 +123,7 @@ impl Base16Scheme {
fn write_ephemeral_realm_files(&self, manager: &RealmManager, realm: &Realm) -> Result<()> {
let skel = realm.base_path_file("skel");
fs::create_dir_all(&skel)?;
util::create_dir(&skel)?;
util::chown_user(&skel)?;
self.write_realm_files(&skel)?;
if realm.is_active() {
@ -144,9 +144,9 @@ impl Base16Scheme {
pub fn write_realm_files<P: AsRef<Path>>(&self, base: P) -> Result<()> {
let base = base.as_ref();
self.write_shell_file(base)
.map_err(|e| format_err!("error writing {} to {}: {}", Self::BASE16_SHELL_FILE, base.display(), e))?;
.map_err(context!("error writing {} to {}", Self::BASE16_SHELL_FILE, base.display()))?;
self.write_vim_file(base)
.map_err(|e| format_err!("error writing {} to {}: {}", Self::BASE16_VIM_FILE, base.display(), e))?;
.map_err(context!("error writing {} to {}", Self::BASE16_VIM_FILE, base.display()))?;
Ok(())
}
@ -160,16 +160,20 @@ impl Base16Scheme {
fn write_vim_file(&self, dir: &Path) -> Result<()> {
let path = dir.join(Self::BASE16_VIM_FILE);
let mut file = fs::File::create(&path)?;
writeln!(&mut file, "if !exists('g:colors_name') || g:colors_name != '{}'", self.slug())?;
writeln!(&mut file, " colorscheme base16-{}", self.slug())?;
writeln!(&mut file, "endif")?;
drop(file);
self.write_vim_file_to(&path)
.map_err(context!("failed to write vim color scheme file to {:?}", path))?;
util::chown_user(&path)?;
debug!("Wrote base16 vim config file to {}", path.display());
Ok(())
}
fn write_vim_file_to(&self, path: &Path) -> io::Result<()> {
let mut file = fs::File::create(&path)?;
writeln!(&mut file, "if !exists('g:colors_name') || g:colors_name != '{}'", self.slug())?;
writeln!(&mut file, " colorscheme base16-{}", self.slug())?;
writeln!(&mut file, "endif")?;
Ok(())
}
}

View File

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

View File

@ -1,6 +1,6 @@
use std::fmt;
use crate::Result;
use crate::{Result, Error};
use crate::terminal::AnsiTerminal;
#[derive(Copy,Clone,Default,Debug)]
@ -21,7 +21,7 @@ impl Color {
return Ok(Color(r, g, b))
}
}
Err(format_err!("Cannot parse '{}'", s))
bail!("Cannot parse '{}'", s)
}
pub fn rgb(self) -> (u16,u16,u16) {
@ -93,3 +93,9 @@ impl TerminalPalette {
}
}
impl From<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<()>
{
let mut cmd = build_open_terminal_command(command);
let status = cmd.status()?;
let status = cmd.status().map_err(context!("error running gnome-terminal"))?;
info!("Gnome terminal exited with: {}", status);
Ok(())
}

View File

@ -1,31 +1,31 @@
use std::mem;
use std::io::{self,Write};
use libc::c_int;
pub use libc::termios as Termios;
use libc::c_int;
use crate::Result;
fn get_terminal_attr() -> io::Result<Termios> {
fn get_terminal_attr() -> Result<Termios> {
extern "C" {
pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int;
}
unsafe {
let mut termios = mem::zeroed();
if tcgetattr(0, &mut termios) == -1 {
return Err(io::Error::last_os_error())
bail!("error calling tcgetattr() on terminal: {}", io::Error::last_os_error());
}
Ok(termios)
}
}
fn set_terminal_attr(termios: &Termios) -> io::Result<()> {
fn set_terminal_attr(termios: &Termios) -> Result<()> {
extern "C" {
pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
}
unsafe {
if tcsetattr(0, 0, termios) == -1 {
return Err(io::Error::last_os_error())
bail!("error calling tcsetattr() on terminal: {}", io::Error::last_os_error());
}
Ok(())
}

View File

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

View File

@ -2,16 +2,16 @@ use std::path::{Path,PathBuf};
use std::process::{Command,Stdio};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs as unixfs;
use std::env;
use std::fs::{self,File};
use std::fs::{self, File, DirEntry};
use std::ffi::CString;
use std::io::{self, Seek, Read, BufReader, SeekFrom};
use failure::ResultExt;
use walkdir::WalkDir;
use libc;
use crate::Result;
use crate::{Result, util};
pub fn is_valid_name(name: &str, maxsize: usize) -> bool {
name.len() <= maxsize &&
@ -36,14 +36,14 @@ pub fn is_first_char_alphabetic(s: &str) -> bool {
}
fn search_path(filename: &str) -> Result<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) {
path.push(filename);
if path.exists() {
return Ok(path);
}
}
Err(format_err!("Could not find {} in $PATH", filename))
bail!("could not find {} in $PATH", filename)
}
pub fn ensure_command_exists(cmd: &str) -> Result<()> {
@ -54,14 +54,13 @@ pub fn ensure_command_exists(cmd: &str) -> Result<()> {
} else if path.exists() {
return Ok(())
}
Err(format_err!("Cannot execute '{}': command does not exist", cmd))
bail!("cannot execute '{}': command does not exist", cmd)
}
pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> {
let path = path.as_ref();
let output = cmd_with_output!("/usr/bin/sha256sum", "{}", path.display())
.context(format!("failed to calculate sha256 on {}", path.display()))?;
.map_err(context!("failed to calculate sha256 on {:?}", path))?;
let v: Vec<&str> = output.split_whitespace().collect();
Ok(v[0].trim().to_owned())
@ -75,14 +74,17 @@ pub enum FileRange {
}
fn ranged_reader<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 {
FileRange::All => 0,
FileRange::Offset(n) => n,
FileRange::Range {offset, .. } => offset,
};
if offset > 0 {
f.seek(SeekFrom::Start(offset as u64))?;
f.seek(SeekFrom::Start(offset as u64))
.map_err(context!("error seeking to offset {} in input file {:?}", offset, path))?;
}
let r = BufReader::new(f);
if let FileRange::Range {len, ..} = range {
@ -107,26 +109,26 @@ pub fn exec_cmdline_pipe_input<S,P>(cmd_path: &str, args: S, input: P, range: Fi
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.context(format!("unable to execute {}", cmd_path))?;
.map_err(context!("unable to execute {}", cmd_path))?;
let stdin = child.stdin.as_mut().unwrap();
io::copy(&mut r, stdin)?;
let output = child.wait_with_output()?;
io::copy(&mut r, stdin)
.map_err(context!("error copying input to stdin"))?;
let output = child.wait_with_output()
.map_err(context!("error waiting for command {} to exit", cmd_path))?;
Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned())
}
pub fn xz_compress<P: AsRef<Path>>(path: P) -> Result<()> {
let path = path.as_ref();
cmd!("/usr/bin/xz", "-T0 {}", path.display())
.context(format!("failed to compress {}", path.display()))?;
Ok(())
.map_err(context!("failed to compress {:?}", path))
}
pub fn xz_decompress<P: AsRef<Path>>(path: P) -> Result<()> {
let path = path.as_ref();
cmd!("/usr/bin/xz", "-d {}", path.display())
.context(format!("failed to decompress {}", path.display()))?;
Ok(())
.map_err(context!("failed to decompress {:?}", path))
}
pub fn mount<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())
} else {
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<()> {
let path = path.as_ref();
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)
}
pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> {
let cstr = CString::new(path.as_os_str().as_bytes())?;
pub fn chown(path: &Path, uid: u32, gid: u32) -> Result<()> {
let cstr = CString::new(path.as_os_str().as_bytes())
.expect("path contains null byte");
unsafe {
if libc::chown(cstr.as_ptr(), uid, gid) == -1 {
return Err(io::Error::last_os_error());
let err = io::Error::last_os_error();
bail!("failed to chown({},{}) {:?}: {}", uid, gid, path, err);
}
}
Ok(())
}
/// Rename or move file at `from` to file path `to`
///
/// A wrapper around `fs::rename()` which on failure returns an error indicating the source and
/// destination paths.
///
pub fn rename(from: impl AsRef<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<()> {
if to.exists() {
bail!("destination path {} already exists which is not expected", to.display());
}
let meta = from.metadata()?;
let meta = from.metadata()
.map_err(context!("failed to read metadata from source file {:?}", from))?;
if from.is_dir() {
fs::create_dir(to)?;
util::create_dir(to)?;
} else {
fs::copy(&from, &to)?;
util::copy_file(&from, &to)?;
}
if let Some((uid,gid)) = chown_to {
@ -190,11 +298,14 @@ pub fn copy_tree_with_chown(from_base: &Path, to_base: &Path, chown_to: (u32,u32
fn _copy_tree(from_base: &Path, to_base: &Path, chown_to: Option<(u32,u32)>) -> Result<()> {
for entry in WalkDir::new(from_base) {
let path = entry?.path().to_owned();
let to = to_base.join(path.strip_prefix(from_base)?);
if to != to_base {
copy_path(&path, &to, chown_to)
.map_err(|e| format_err!("failed to copy {} to {}: {}", path.display(), to.display(), e))?;
let entry = entry.map_err(|e| format_err!("Error walking directory tree: {}", e))?;
let path = entry.path();
let suffix = path.strip_prefix(from_base)
.map_err(|_| format_err!("Failed to strip prefix from {:?}", path))?;
let to = to_base.join(suffix);
if &to != to_base {
copy_path(path, &to, chown_to)
.map_err(context!("failed to copy {:?} to {:?}", path, to))?;
}
}
Ok(())
@ -202,9 +313,10 @@ fn _copy_tree(from_base: &Path, to_base: &Path, chown_to: Option<(u32,u32)>) ->
pub fn chown_tree(base: &Path, chown_to: (u32,u32), include_base: bool) -> Result<()> {
for entry in WalkDir::new(base) {
let entry = entry?;
let entry = entry.map_err(|e| format_err!("Error reading directory entry: {}", e))?;
if entry.path() != base || include_base {
chown(entry.path(), chown_to.0, chown_to.1)?;
chown(entry.path(), chown_to.0, chown_to.1)
.map_err(context!("failed to chown {:?}", entry.path()))?;
}
}
Ok(())

View File

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