diff --git a/Cargo.lock b/Cargo.lock index 52a70d90..ea37106f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,7 +613,7 @@ dependencies = [ "fs-err", "serde", "serde_yaml", - "thiserror 2.0.3", + "snafu", ] [[package]] @@ -641,8 +641,8 @@ version = "0.24.7" dependencies = [ "fs-err", "nix 0.27.1", + "snafu", "strum", - "thiserror 2.0.3", ] [[package]] @@ -1096,7 +1096,7 @@ version = "0.24.7" dependencies = [ "regex", "serde", - "thiserror 2.0.3", + "snafu", ] [[package]] @@ -1856,6 +1856,7 @@ dependencies = [ "serde_derive", "serpent_buildinfo", "sha2", + "snafu", "stone", "strum", "thiserror 2.0.3", @@ -2528,7 +2529,6 @@ name = "serpent_buildinfo" version = "0.24.7" dependencies = [ "chrono", - "thiserror 2.0.3", ] [[package]] @@ -2599,6 +2599,27 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "snafu" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.5.8" @@ -3129,7 +3150,7 @@ name = "vfs" version = "0.1.0" dependencies = [ "indextree", - "thiserror 2.0.3", + "snafu", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 565b2019..cd9ead2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ serde_derive = "1.0.204" serde_json = "1.0.120" serde_yaml = "0.9.34" sha2 = "0.10.8" +snafu = "0.8.5" strum = { version = "0.26.3", features = ["derive"] } thiserror = "2.0.3" thread-priority = "1.1.0" diff --git a/boulder/src/container.rs b/boulder/src/container.rs index ad5f9ea0..73f872d7 100644 --- a/boulder/src/container.rs +++ b/boulder/src/container.rs @@ -9,14 +9,14 @@ use crate::Paths; pub fn exec(paths: &Paths, networking: bool, f: impl FnMut() -> Result<(), E>) -> Result<(), Error> where - E: std::error::Error + 'static, + E: std::error::Error + Send + Sync + 'static, { run(paths, networking, f) } fn run(paths: &Paths, networking: bool, f: impl FnMut() -> Result<(), E>) -> Result<(), Error> where - E: std::error::Error + 'static, + E: std::error::Error + Send + Sync + 'static, { let rootfs = paths.rootfs().host; let artefacts = paths.artefacts(); diff --git a/boulder/src/draft/build.rs b/boulder/src/draft/build.rs index 7abc9792..2a7f49f0 100644 --- a/boulder/src/draft/build.rs +++ b/boulder/src/draft/build.rs @@ -14,7 +14,7 @@ mod cmake; mod meson; mod python; -pub type Error = Box; +pub type Error = Box; /// A build system #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, strum::Display)] diff --git a/boulder/src/package/analysis.rs b/boulder/src/package/analysis.rs index 3836ad1c..501a00ca 100644 --- a/boulder/src/package/analysis.rs +++ b/boulder/src/package/analysis.rs @@ -18,7 +18,7 @@ use super::collect::{Collector, PathInfo}; mod handler; -pub type BoxError = Box; +pub type BoxError = Box; pub struct Chain<'a> { handlers: Vec>, diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 478981e8..bbb9c7dd 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -11,4 +11,4 @@ dirs.workspace = true fs-err.workspace = true serde.workspace = true serde_yaml.workspace = true -thiserror.workspace = true +snafu.workspace = true diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index eb964cfe..e0458c4a 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -9,7 +9,7 @@ use std::{ use fs_err as fs; use serde::{de::DeserializeOwned, Serialize}; -use thiserror::Error; +use snafu::{ResultExt as _, Snafu}; const EXTENSION: &str = "yaml"; @@ -74,13 +74,13 @@ impl Manager { let dir = self.scope.save_dir(&domain); - fs::create_dir_all(&dir).map_err(|io| SaveError::CreateDir(dir.clone(), io))?; + fs::create_dir_all(&dir).context(CreateDirSnafu)?; let path = dir.join(format!("{name}.{EXTENSION}")); - let serialized = serde_yaml::to_string(config)?; + let serialized = serde_yaml::to_string(config).context(SerializeSnafu)?; - fs::write(&path, serialized).map_err(|io| SaveError::Write(path, io))?; + fs::write(&path, serialized).context(WriteSnafu)?; Ok(()) } @@ -97,18 +97,18 @@ impl Manager { } } -#[derive(Debug, Error)] -#[error("$HOME or $XDG_CONFIG_HOME env not set")] +#[derive(Debug, Snafu)] +#[snafu(display("$HOME or $XDG_CONFIG_HOME env not set"))] pub struct CreateUserError; -#[derive(Debug, Error)] +#[derive(Debug, Snafu)] pub enum SaveError { - #[error("create config dir {0:?}")] - CreateDir(PathBuf, #[source] io::Error), - #[error("serialize config")] - Yaml(#[from] serde_yaml::Error), - #[error("write config file {0:?}")] - Write(PathBuf, #[source] io::Error), + #[snafu(display("create config dir"))] + CreateDir { source: io::Error }, + #[snafu(display("serialize config"))] + Serialize { source: serde_yaml::Error }, + #[snafu(display("write config file"))] + Write { source: io::Error }, } fn enumerate_paths(entry: Entry, resolve: Resolve<'_>, domain: &str) -> Vec { diff --git a/crates/container/Cargo.toml b/crates/container/Cargo.toml index e7a979cb..f091775e 100644 --- a/crates/container/Cargo.toml +++ b/crates/container/Cargo.toml @@ -9,5 +9,5 @@ rust-version.workspace = true [dependencies] fs-err.workspace = true nix.workspace = true +snafu.workspace = true strum.workspace = true -thiserror.workspace = true diff --git a/crates/container/src/idmap.rs b/crates/container/src/idmap.rs index 8ce3798b..64f8436f 100644 --- a/crates/container/src/idmap.rs +++ b/crates/container/src/idmap.rs @@ -6,12 +6,15 @@ use std::process::Command; use fs_err as fs; use nix::unistd::{getgid, getuid, Pid, User}; -use thiserror::Error; +use snafu::{ensure, ResultExt, Snafu}; pub fn idmap(pid: Pid) -> Result<(), Error> { let uid = getuid(); let gid = getgid(); - let username = User::from_uid(uid)?.map(|user| user.name).unwrap_or_default(); + let username = User::from_uid(uid) + .context(GetUserByUidSnafu)? + .map(|user| user.name) + .unwrap_or_default(); let subuid_mappings = load_sub_mappings(Kind::User, uid.as_raw(), &username)?; let subgid_mappings = load_sub_mappings(Kind::Group, gid.as_raw(), &username)?; @@ -64,12 +67,8 @@ fn load_sub_mappings(kind: Kind, id: u32, username: &str) -> Result, fn ensure_sub_count(kind: Kind, id: u32, mappings: &[Submap]) -> Result<(), Error> { let count = mappings.iter().map(|map| map.count).sum::(); - - if count < 1000 { - Err(Error::SubMappingCount(id, kind, count)) - } else { - Ok(()) - } + ensure!(count >= 1000, SubMappingCountSnafu { id, kind, count }); + Ok(()) } fn format_id_mappings(sub_mappings: &[Submap]) -> Vec { @@ -111,10 +110,14 @@ fn add_id_mappings(pid: Pid, kind: Kind, id: u32, mappings: &[Idmap]) -> Result< ] })) .output() - .map_err(|e| Error::Command(e.to_string(), kind))?; + .boxed() + .context(CommandSnafu { kind })?; if !out.status.success() { - return Err(Error::Command(format!("{}", out.status), kind)); + return Err(Error::Command { + kind, + source: out.status.to_string().into(), + }); } Ok(()) @@ -133,12 +136,15 @@ struct Idmap { count: u32, } -#[derive(Debug, Error)] +#[derive(Debug, Snafu)] pub enum Error { - #[error("\n\nAt least 1,000 sub{1} mappings are required for {1} {0}, found {2}\n\nMappings can be added to /etc/sub{1}")] - SubMappingCount(u32, Kind, u32), - #[error("new{1}map command failed: {0}")] - Command(String, Kind), - #[error("nix")] - Nix(#[from] nix::Error), + #[snafu(display("\n\nAt least 1,000 sub{kind} mappings are required for {kind} {id}, found {count}\n\nMappings can be added to /etc/sub{kind}"))] + SubMappingCount { id: u32, kind: Kind, count: u32 }, + #[snafu(display("new{kind}map command failed"))] + Command { + kind: Kind, + source: Box, + }, + #[snafu(display("get user by UID"))] + GetUserByUid { source: nix::Error }, } diff --git a/crates/container/src/lib.rs b/crates/container/src/lib.rs index 87dcfe2c..97b44bb7 100644 --- a/crates/container/src/lib.rs +++ b/crates/container/src/lib.rs @@ -19,7 +19,7 @@ use nix::sys::signalfd::SigSet; use nix::sys::stat::{umask, Mode}; use nix::sys::wait::{waitpid, WaitStatus}; use nix::unistd::{close, pipe, pivot_root, read, sethostname, tcsetpgrp, write, Pid, Uid}; -use thiserror::Error; +use snafu::{ResultExt, Snafu}; use self::idmap::idmap; @@ -113,14 +113,14 @@ impl Container { /// Run `f` as a container process payload pub fn run(self, mut f: impl FnMut() -> Result<(), E>) -> Result<(), Error> where - E: std::error::Error + 'static, + E: std::error::Error + Send + Sync + 'static, { static mut STACK: [u8; 4 * 1024 * 1024] = [0u8; 4 * 1024 * 1024]; let rootless = !Uid::effective().is_root(); // Pipe to synchronize parent & child - let sync = pipe()?; + let sync = pipe().context(NixSnafu)?; let mut flags = CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWPID | CloneFlags::CLONE_NEWIPC | CloneFlags::CLONE_NEWUTS; @@ -158,27 +158,28 @@ impl Container { &mut *addr_of_mut!(STACK), flags, Some(SIGCHLD), - )? + ) + .context(NixSnafu)? }; // Update uid / gid map to map current user to root in container if rootless { - idmap(pid)?; + idmap(pid).context(IdmapSnafu)?; } // Allow child to continue - write(sync.1, &[Message::Continue as u8])?; + write(sync.1, &[Message::Continue as u8]).context(NixSnafu)?; // Write no longer needed - close(sync.1)?; + close(sync.1).context(NixSnafu)?; if self.ignore_host_sigint { - ignore_sigint()?; + ignore_sigint().context(NixSnafu)?; } - let status = waitpid(pid, None)?; + let status = waitpid(pid, None).context(NixSnafu)?; if self.ignore_host_sigint { - default_sigint()?; + default_sigint().context(NixSnafu)?; } match status { @@ -188,7 +189,7 @@ impl Container { let mut buffer = [0u8; 1024]; loop { - let len = read(sync.0, &mut buffer)?; + let len = read(sync.0, &mut buffer).context(NixSnafu)?; if len == 0 { break; @@ -197,9 +198,9 @@ impl Container { error.push_str(String::from_utf8_lossy(&buffer[..len]).as_ref()); } - Err(Error::Failure(error)) + Err(Error::Failure { message: error }) } - WaitStatus::Signaled(_, signal, _) => Err(Error::Signaled(signal)), + WaitStatus::Signaled(_, signal, _) => Err(Error::Signaled { signal }), WaitStatus::Stopped(_, _) | WaitStatus::PtraceEvent(_, _, _) | WaitStatus::PtraceSyscall(_) @@ -212,22 +213,22 @@ impl Container { /// Reenter the container fn enter(container: &Container, sync: (i32, i32), mut f: impl FnMut() -> Result<(), E>) -> Result<(), ContainerError> where - E: std::error::Error + 'static, + E: std::error::Error + Send + Sync + 'static, { // Ensure process is cleaned up if parent dies - set_pdeathsig(Signal::SIGKILL).map_err(ContainerError::SetPDeathSig)?; + set_pdeathsig(Signal::SIGKILL).context(SetPDeathSigSnafu)?; // Wait for continue message let mut message = [0u8; 1]; - read(sync.0, &mut message).map_err(ContainerError::ReadContinueMsg)?; + read(sync.0, &mut message).context(ReadContinueMsgSnafu)?; assert_eq!(message[0], Message::Continue as u8); // Close unused read end - close(sync.0).map_err(ContainerError::CloseReadFd)?; + close(sync.0).context(CloseReadFdSnafu)?; setup(container)?; - f().map_err(|e| ContainerError::Run(Box::new(e))) + f().boxed().context(RunSnafu) } /// Setup the container @@ -245,7 +246,7 @@ fn setup(container: &Container) -> Result<(), ContainerError> { } if let Some(hostname) = &container.hostname { - sethostname(hostname).map_err(ContainerError::SetHostname)?; + sethostname(hostname).context(SetHostnameSnafu)?; } if let Some(dir) = &container.work_dir { @@ -265,7 +266,7 @@ fn pivot(root: &Path, binds: &[Bind]) -> Result<(), ContainerError> { add_mount(Some(root), root, None, MsFlags::MS_BIND)?; for bind in binds { - let source = bind.source.fs_err_canonicalize()?; + let source = bind.source.fs_err_canonicalize().context(FsErrSnafu)?; let target = root.join(bind.target.strip_prefix("/").unwrap_or(&bind.target)); add_mount(Some(&source), &target, None, MsFlags::MS_BIND)?; @@ -282,7 +283,7 @@ fn pivot(root: &Path, binds: &[Bind]) -> Result<(), ContainerError> { } ensure_directory(&old_root)?; - pivot_root(root, &old_root).map_err(ContainerError::PivotRoot)?; + pivot_root(root, &old_root).context(PivotRootSnafu)?; set_current_dir("/")?; @@ -301,37 +302,40 @@ fn pivot(root: &Path, binds: &[Bind]) -> Result<(), ContainerError> { MsFlags::MS_BIND | MsFlags::MS_REC | MsFlags::MS_SLAVE, )?; - umount2(OLD_PATH, MntFlags::MNT_DETACH).map_err(ContainerError::UnmountOldRoot)?; - remove_dir(OLD_PATH)?; + umount2(OLD_PATH, MntFlags::MNT_DETACH).context(UnmountOldRootSnafu)?; + remove_dir(OLD_PATH).context(FsErrSnafu)?; Ok(()) } fn setup_root_user() -> Result<(), ContainerError> { ensure_directory("/etc")?; - fs::write("/etc/passwd", "root:x:0:0:root::/bin/bash")?; - fs::write("/etc/group", "root:x:0:")?; + fs::write("/etc/passwd", "root:x:0:0:root::/bin/bash").context(FsErrSnafu)?; + fs::write("/etc/group", "root:x:0:").context(FsErrSnafu)?; umask(Mode::S_IWGRP | Mode::S_IWOTH); Ok(()) } fn setup_networking(root: &Path) -> Result<(), ContainerError> { ensure_directory(root.join("etc"))?; - copy("/etc/resolv.conf", root.join("etc/resolv.conf"))?; - copy("/etc/protocols", root.join("etc/protocols"))?; + copy("/etc/resolv.conf", root.join("etc/resolv.conf")).context(FsErrSnafu)?; + copy("/etc/protocols", root.join("etc/protocols")).context(FsErrSnafu)?; Ok(()) } fn setup_localhost() -> Result<(), ContainerError> { // TODO: maybe it's better to hunt down the API to do this instead? - Command::new("ip").args(["link", "set", "lo", "up"]).output()?; + Command::new("ip") + .args(["link", "set", "lo", "up"]) + .output() + .context(SetupLocalhostSnafu)?; Ok(()) } fn ensure_directory(path: impl AsRef) -> Result<(), ContainerError> { let path = path.as_ref(); if !path.exists() { - create_dir_all(path)?; + create_dir_all(path).context(FsErrSnafu)?; } Ok(()) } @@ -351,31 +355,13 @@ fn add_mount>( flags, Option::<&str>::None, ) - .map_err(|err| ContainerError::Mount { - target: target.to_owned(), - err, - })?; + .with_context(|_| MountSnafu { target })?; Ok(()) } -fn set_current_dir(path: impl AsRef) -> io::Result<()> { - #[derive(Debug, Error)] - #[error("failed to set current directory to `{}`", path.display())] - struct SetCurrentDirError { - source: io::Error, - path: PathBuf, - } - +fn set_current_dir(path: impl AsRef) -> Result<(), ContainerError> { let path = path.as_ref(); - std::env::set_current_dir(path).map_err(|source| { - io::Error::new( - source.kind(), - SetCurrentDirError { - source, - path: path.to_owned(), - }, - ) - }) + std::env::set_current_dir(path).with_context(|_| SetCurrentDirSnafu { path }) } fn ignore_sigint() -> Result<(), nix::Error> { @@ -451,48 +437,47 @@ struct Bind { read_only: bool, } -#[derive(Debug, Error)] +#[derive(Debug, Snafu)] pub enum Error { - #[error("exited with failure: {0}")] - Failure(String), - #[error("stopped by signal: {}", .0.as_str())] - Signaled(Signal), - #[error("unknown exit reason")] + #[snafu(display("exited with failure: {message}"))] + Failure { message: String }, + #[snafu(display("stopped by signal: {signal}"))] + Signaled { signal: Signal }, + #[snafu(display("unknown exit reason"))] UnknownExit, - #[error("error setting up rootless id map")] - Idmap(#[from] idmap::Error), - #[error("nix")] - Nix(#[from] nix::Error), - #[error("io")] - Io(#[from] io::Error), + #[snafu(display("error setting up rootless id map"))] + Idmap { source: idmap::Error }, + // FIXME: Replace with more fine-grained variants + #[snafu(display("nix"))] + Nix { source: nix::Error }, } -#[derive(Debug, Error)] +#[derive(Debug, Snafu)] enum ContainerError { - #[error(transparent)] - Run(Box), - #[error("io")] - Io(#[from] io::Error), - - // Errors from linux system functions - #[error("set_pdeathsig")] - SetPDeathSig(#[source] nix::Error), - #[error("wait for continue message")] - ReadContinueMsg(#[source] nix::Error), - #[error("close read end of pipe")] - CloseReadFd(#[source] nix::Error), - #[error("sethostname")] - SetHostname(#[source] nix::Error), - #[error("pivot_root")] - PivotRoot(#[source] nix::Error), - #[error("unmount old root")] - UnmountOldRoot(#[source] nix::Error), - #[error("mount {}", target.display())] - Mount { - target: PathBuf, - #[source] - err: nix::Error, + #[snafu(display("run"))] + Run { + source: Box, }, + #[snafu(display("set current dir"))] + SetCurrentDirError { path: PathBuf, source: io::Error }, + #[snafu(display("setup localhost"))] + SetupLocalhost { source: io::Error }, + #[snafu(display("set_pdeathsig"))] + SetPDeathSig { source: nix::Error }, + #[snafu(display("wait for continue message"))] + ReadContinueMsg { source: nix::Error }, + #[snafu(display("close read end of pipe"))] + CloseReadFd { source: nix::Error }, + #[snafu(display("sethostname"))] + SetHostname { source: nix::Error }, + #[snafu(display("pivot_root"))] + PivotRoot { source: nix::Error }, + #[snafu(display("unmount old root"))] + UnmountOldRoot { source: nix::Error }, + #[snafu(display("mount {}", target.display()))] + Mount { target: PathBuf, source: nix::Error }, + #[snafu(display("filesystem"))] + FsErr { source: io::Error }, } #[repr(u8)] diff --git a/crates/fnmatch/Cargo.toml b/crates/fnmatch/Cargo.toml index 1f75b586..6ccef5b8 100644 --- a/crates/fnmatch/Cargo.toml +++ b/crates/fnmatch/Cargo.toml @@ -8,5 +8,5 @@ rust-version.workspace = true [dependencies] regex.workspace = true -thiserror.workspace = true serde.workspace = true +snafu.workspace = true diff --git a/crates/fnmatch/src/lib.rs b/crates/fnmatch/src/lib.rs index cb955a83..828dd07e 100644 --- a/crates/fnmatch/src/lib.rs +++ b/crates/fnmatch/src/lib.rs @@ -15,11 +15,11 @@ //! ``` use std::collections::{BTreeMap, BTreeSet}; -use std::{convert::Infallible, str::FromStr}; +use std::str::FromStr; use regex::Regex; use serde::{de, Deserialize}; -use thiserror::Error; +use snafu::{ResultExt as _, Snafu}; #[derive(Debug)] enum Fragment { @@ -143,20 +143,15 @@ impl Pattern { } } -/// [thiserror] compatible Error -#[derive(Error, Debug)] +#[derive(Debug, Snafu)] pub enum Error { - /// Corrupt string (unicode) - #[error("malformed: {0}")] - String(#[from] Infallible), - /// Illegal group syntax - #[error("malformed group")] + #[snafu(display("malformed group"))] Group, /// Illegal regex - #[error("invalid regex: {0}")] - Regex(#[from] regex::Error), + #[snafu(display("invalid regex"))] + Regex { source: regex::Error }, } fn fragments_from_string(s: &str) -> Result, Error> { @@ -178,11 +173,11 @@ fn fragments_from_string(s: &str) -> Result, Error> { if splits.len() != 2 { return Err(Error::Group); } - let key = splits.first().ok_or(Error::Group)?; - let value = splits.get(1).ok_or(Error::Group)?; + let key = *splits.first().ok_or(Error::Group)?; + let value = *splits.get(1).ok_or(Error::Group)?; let subpattern = fragments_from_string(value)?; - builder.push(Fragment::Group(String::from_str(key)?, subpattern)); + builder.push(Fragment::Group(key.to_owned(), subpattern)); } else { return Err(Error::Group); } @@ -254,7 +249,7 @@ impl FromStr for Pattern { Ok(Self { pattern: s.into(), - regex: Regex::new(&format!("^{compiled}$"))?, + regex: Regex::new(&format!("^{compiled}$")).context(RegexSnafu)?, groups: groups.into_iter().collect(), }) } diff --git a/crates/serpent_buildinfo/Cargo.toml b/crates/serpent_buildinfo/Cargo.toml index 34fa6473..dda8e965 100644 --- a/crates/serpent_buildinfo/Cargo.toml +++ b/crates/serpent_buildinfo/Cargo.toml @@ -10,4 +10,3 @@ chrono.workspace = true [build-dependencies] chrono.workspace = true -thiserror.workspace = true diff --git a/crates/vfs/Cargo.toml b/crates/vfs/Cargo.toml index 4975fa9a..3d3d690c 100644 --- a/crates/vfs/Cargo.toml +++ b/crates/vfs/Cargo.toml @@ -7,4 +7,4 @@ edition.workspace = true [dependencies] indextree.workspace = true -thiserror.workspace = true +snafu.workspace = true diff --git a/crates/vfs/src/tree/mod.rs b/crates/vfs/src/tree/mod.rs index 33afabf1..42cea275 100644 --- a/crates/vfs/src/tree/mod.rs +++ b/crates/vfs/src/tree/mod.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::vec; use indextree::{Arena, Descendants, NodeId}; -use thiserror::Error; +use snafu::Snafu; use crate::path; @@ -112,44 +112,45 @@ impl Tree { /// Add a child to the given parent node fn add_child_to_node(&mut self, node_id: NodeId, parent: &str) -> Result<(), Error> { let node = self.arena.get(node_id).unwrap(); - if let Some(parent_node) = self.map.get(parent) { - let others = parent_node - .children(&self.arena) - .filter_map(|n| self.arena.get(n)) - .filter_map(|n| { - if n.get().file_name == node.get().file_name { - Some(n.get()) - } else { - None - } - }) - .collect::>(); - if !others.is_empty() { - // TODO: Reenable - // Err(Error::Duplicate( - // node.get().path(), - // node.get().id(), - // others.first().unwrap().id(), - // )) - - // Report duplicate and skip for now - eprintln!( - "error: {}", - Error::Duplicate( - node.get().path.clone(), - node.get().id.clone(), - others.first().unwrap().id.clone() - ) - ); - - Ok(()) - } else { - parent_node.append(node_id, &mut self.arena); - Ok(()) - } + let Some(parent_node) = self.map.get(parent) else { + return MissingParentSnafu { parent }.fail(); + }; + + let others = parent_node + .children(&self.arena) + .filter_map(|n| self.arena.get(n)) + .filter_map(|n| { + if n.get().file_name == node.get().file_name { + Some(n.get()) + } else { + None + } + }) + .collect::>(); + if !others.is_empty() { + // TODO: Reenable + // return DuplicateSnafu { + // node_path: &node.get().path, + // node_id: &node.get().id, + // other_id: &others.first().unwrap().id, + // } + // .fail(); + + // Report duplicate and skip for now + eprintln!( + "error: {}", + DuplicateSnafu { + node_path: &node.get().path, + node_id: &node.get().id, + other_id: &others.first().unwrap().id, + } + .build() + ); } else { - Err(Error::MissingParent(parent.to_string())) + parent_node.append(node_id, &mut self.arena); } + + Ok(()) } pub fn print(&self) { @@ -254,11 +255,15 @@ impl<'a, T: BlitFile> Iterator for TreeIterator<'a, T> { } } -#[derive(Debug, Error)] +#[derive(Debug, Snafu)] pub enum Error { - #[error("missing parent: {0}")] - MissingParent(String), - - #[error("duplicate entry: {0} {1} attempts to overwrite {2}")] - Duplicate(String, String, String), + #[snafu(display("missing parent: {parent}"))] + MissingParent { parent: String }, + + #[snafu(display("duplicate entry: {node_path} {node_id} attempts to overwrite {other_id}"))] + Duplicate { + node_path: String, + node_id: String, + other_id: String, + }, } diff --git a/moss/Cargo.toml b/moss/Cargo.toml index b1412ffd..c4e156f8 100644 --- a/moss/Cargo.toml +++ b/moss/Cargo.toml @@ -43,6 +43,7 @@ thiserror.workspace = true url.workspace = true xxhash-rust.workspace = true zbus.workspace = true +snafu.workspace = true [package.metadata.cargo-machete] # Needed for unixepoch() in src/db/state/migrations/2024-03-04-201550_init/up.sql diff --git a/moss/src/client/postblit.rs b/moss/src/client/postblit.rs index c9172678..311d53f7 100644 --- a/moss/src/client/postblit.rs +++ b/moss/src/client/postblit.rs @@ -17,7 +17,7 @@ use crate::Installation; use container::Container; use itertools::Itertools; use serde::Deserialize; -use thiserror::Error; +use snafu::{ResultExt as _, Snafu}; use triggers::format::{CompiledHandler, Handler, Trigger}; use super::PendingFile; @@ -142,10 +142,11 @@ pub(super) fn triggers<'a>( }; // Load trigger collection, process all the paths, convert to scoped TriggerRunner vec - let mut collection = triggers::Collection::new(triggers.iter())?; + let mut collection = triggers::Collection::new(triggers.iter()).context(TriggersSnafu)?; collection.process_paths(fstree.iter().map(|m| m.to_string())); let computed_commands = collection - .bake()? + .bake() + .context(TriggersSnafu)? .into_iter() .map(|trigger| TriggerRunner { scope, trigger }) .collect_vec(); @@ -171,7 +172,9 @@ impl TriggerRunner<'_> { .bind_rw(self.scope.guest_path("usr"), "/usr") .work_dir("/"); - Ok(isolation.run(|| execute_trigger_directly(&self.trigger))?) + Ok(isolation + .run(|| execute_trigger_directly(&self.trigger)) + .context(ContainerSnafu)?) } TriggerScope::System(install, _) => { // OK, if the root == `/` then we can run directly, otherwise we need to containerise with RW. @@ -185,7 +188,9 @@ impl TriggerRunner<'_> { .bind_rw(self.scope.guest_path("usr"), "/usr") .work_dir("/"); - Ok(isolation.run(|| execute_trigger_directly(&self.trigger))?) + Ok(isolation + .run(|| execute_trigger_directly(&self.trigger)) + .context(ContainerSnafu)?) } } } @@ -196,7 +201,11 @@ impl TriggerRunner<'_> { fn execute_trigger_directly(trigger: &CompiledHandler) -> Result<(), Error> { match trigger.handler() { Handler::Run { run, args } => { - let cmd = process::Command::new(run).args(args).current_dir("/").output()?; + let cmd = process::Command::new(run) + .args(args) + .current_dir("/") + .output() + .context(IoSnafu)?; if let Some(code) = cmd.status.code() { if code != 0 { @@ -218,14 +227,14 @@ fn execute_trigger_directly(trigger: &CompiledHandler) -> Result<(), Error> { Ok(()) } -#[derive(Debug, Error)] +#[derive(Debug, Snafu)] pub enum Error { - #[error("container")] - Container(#[from] container::Error), + #[snafu(display("container"))] + Container { source: container::Error }, - #[error("triggers")] - Triggers(#[from] triggers::Error), + #[snafu(display("triggers"))] + Triggers { source: triggers::Error }, - #[error("io")] - IO(#[from] std::io::Error), + #[snafu(display("io"))] + Io { source: std::io::Error }, }