From c8fe501edccfccf250ec819cfc318713517158c7 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Tue, 24 Mar 2026 04:34:58 +0700 Subject: [PATCH] Use custom error for fs and pty module --- src/bin/repo.rs | 6 +- src/cook/cook_build.rs | 18 ++- src/cook/fs.rs | 266 ++++++++++++++++------------------------- src/cook/pty.rs | 71 +++++------ src/lib.rs | 115 ++++++++++++++++++ 5 files changed, 262 insertions(+), 214 deletions(-) diff --git a/src/bin/repo.rs b/src/bin/repo.rs index 04deef4ce..f7d394ebd 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -704,7 +704,7 @@ fn handle_fetch( true => fetch_offline(&recipe, logger), false => fetch(&recipe, !recipe.is_deps, logger), } - .map_err(|e| anyhow!("failed to fetch: {:?}", e))?; + .map_err(|e| anyhow!("failed to fetch: {}", e))?; Ok(source_dir) } @@ -725,10 +725,10 @@ fn handle_cook( &config.cook, logger, ) - .map_err(|err| anyhow!("failed to build: {:?}", err))?; + .map_err(|err| anyhow!("failed to build: {}", err))?; package(&recipe, &build_result, &config.cook, logger) - .map_err(|err| anyhow!("failed to package: {:?}", err))?; + .map_err(|err| anyhow!("failed to package: {}", err))?; if config.cook.clean_target || config.cook.write_filetree { for stage_dir in &build_result.stage_dirs { diff --git a/src/cook/cook_build.rs b/src/cook/cook_build.rs index 2718a1532..cd05e1427 100644 --- a/src/cook/cook_build.rs +++ b/src/cook/cook_build.rs @@ -269,16 +269,14 @@ pub fn build( } } - let deps_modified = dep_pkgars - .iter() - .map(|(_dep, pkgar)| modified(pkgar)) - .max() - .unwrap_or(Ok(SystemTime::UNIX_EPOCH))?; - let deps_host_modified = dep_host_pkgars - .iter() - .map(|(_dep, pkgar)| modified(pkgar)) - .max() - .unwrap_or(Ok(SystemTime::UNIX_EPOCH))?; + let deps_modified = modified_all_btree( + dep_pkgars.iter().map(|(_dep, pkgar)| pkgar.as_path()), + modified, + )?; + let deps_host_modified = modified_all_btree( + dep_host_pkgars.iter().map(|(_dep, pkgar)| pkgar.as_path()), + modified, + )?; // check stage dir modified against pkgar files, any files missing will result in UNIX_EPOCH let stage_modified = modified_all(&stage_pkgars, modified).unwrap_or(SystemTime::UNIX_EPOCH); diff --git a/src/cook/fs.rs b/src/cook/fs.rs index fc7135d2e..ebe975bb8 100644 --- a/src/cook/fs.rs +++ b/src/cook/fs.rs @@ -10,41 +10,36 @@ use std::{ use walkdir::{DirEntry, WalkDir}; use crate::{ + Error, Result, bail_other_err, config::translate_mirror, cook::pty::{PtyOut, spawn_to_pipe}, - wrap_io_err, + wrap_io_err, wrap_other_err, }; //TODO: pub(crate) for all of these functions -pub fn remove_all(path: &Path) -> Result<(), String> { +pub fn remove_all(path: &Path) -> Result<()> { if path.is_dir() { fs::remove_dir_all(path) } else { fs::remove_file(path) } - .map_err(|err| format!("failed to remove '{}': {}\n{:?}", path.display(), err, err)) + .map_err(wrap_io_err!(path, "Removing all")) } -pub fn create_dir(dir: &Path) -> Result<(), String> { - fs::create_dir(dir) - .map_err(|err| format!("failed to create '{}': {}\n{:?}", dir.display(), err, err)) +pub fn create_dir(dir: &Path) -> Result<()> { + fs::create_dir_all(dir).map_err(wrap_io_err!(dir, "Recursively creating dir")) } -pub fn create_dir_clean(dir: &Path) -> Result<(), String> { +pub fn create_dir_clean(dir: &Path) -> Result<()> { if dir.is_dir() { remove_all(dir)?; } - fs::create_dir_all(dir) - .map_err(|err| format!("failed to create '{}': {}\n{:?}", dir.display(), err, err)) + create_dir(dir) } -pub fn create_target_dir(recipe_dir: &Path, target: &'static str) -> Result { - let target_parent_dir = recipe_dir.join("target"); - if !target_parent_dir.is_dir() { - create_dir(&target_parent_dir)?; - } - let target_dir = target_parent_dir.join(target); +pub fn create_target_dir(recipe_dir: &Path, target: &'static str) -> Result { + let target_dir = recipe_dir.join("target").join(target); if !target_dir.is_dir() { create_dir(&target_dir)?; } @@ -102,41 +97,26 @@ fn move_dir_all_inner_fn<'a>( Ok(()) } -pub fn symlink(original: impl AsRef, link: impl AsRef) -> Result<(), String> { - std::os::unix::fs::symlink(&original, &link).map_err(|err| { - format!( - "failed to symlink '{}' to '{}': {}\n{:?}", - original.as_ref().display(), - link.as_ref().display(), - err, - err - ) - }) +pub fn symlink(original: impl AsRef, link: impl AsRef) -> Result<()> { + std::os::unix::fs::symlink(&original, &link) + .map_err(wrap_io_err!(link.as_ref(), "Creating symlink")) } -pub fn modified(path: &Path) -> Result { - let metadata = fs::metadata(path).map_err(|err| { - format!( - "failed to get metadata of '{}': {}\n{:#?}", - path.display(), - err, - err - ) - })?; - metadata.modified().map_err(|err| { - format!( - "failed to get modified time of '{}': {}\n{:#?}", - path.display(), - err, - err - ) - }) +fn modified_inner(path: &Path, metadata: fs::Metadata) -> Result { + metadata + .modified() + .map_err(wrap_io_err!(path, "Reading modified time")) +} + +pub fn modified(path: &Path) -> Result { + let metadata = fs::metadata(path).map_err(wrap_io_err!(path, "Reading metadata"))?; + modified_inner(path, metadata) } pub fn modified_all( path: &Vec, - func: fn(path: &Path) -> Result, -) -> Result { + func: fn(path: &Path) -> Result, +) -> Result { let mut newest = SystemTime::UNIX_EPOCH; for entry_res in path { let modified = func(entry_res)?; @@ -147,14 +127,13 @@ pub fn modified_all( Ok(newest) } -pub fn modified_dir_inner bool>( - dir: &Path, - filter: F, -) -> io::Result { - let mut newest = fs::metadata(dir)?.modified()?; - for entry_res in WalkDir::new(dir).into_iter().filter_entry(filter) { - let entry = entry_res?; - let modified = entry.metadata()?.modified()?; +pub fn modified_all_btree<'a>( + path: impl Iterator, + func: fn(path: &Path) -> Result, +) -> Result { + let mut newest = SystemTime::UNIX_EPOCH; + for entry_res in path { + let modified = func(entry_res)?; if modified > newest { newest = modified; } @@ -162,18 +141,23 @@ pub fn modified_dir_inner bool>( Ok(newest) } -pub fn modified_dir(dir: &Path) -> Result { - modified_dir_inner(dir, |_| true).map_err(|err| { - format!( - "failed to get modified time of '{}': {}\n{:#?}", - dir.display(), - err, - err - ) - }) +fn modified_dir_inner bool>(dir: &Path, filter: F) -> Result { + let mut newest = modified(dir)?; + for entry_res in WalkDir::new(dir).into_iter().filter_entry(filter) { + let entry = entry_res?; + let modified = modified_inner(entry.path(), entry.metadata()?)?; + if modified > newest { + newest = modified; + } + } + Ok(newest) } -pub fn modified_dir_ignore_git(dir: &Path) -> Result { +pub fn modified_dir(dir: &Path) -> Result { + modified_dir_inner(dir, |_| true) +} + +pub fn modified_dir_ignore_git(dir: &Path) -> Result { modified_dir_inner(dir, |entry| { entry .file_name() @@ -181,24 +165,14 @@ pub fn modified_dir_ignore_git(dir: &Path) -> Result { .map(|s| s != ".git") .unwrap_or(true) }) - .map_err(|err| { - format!( - "failed to get modified time of '{}': {}\n{:#?}", - dir.display(), - err, - err - ) - }) } -pub fn check_files_present(dir: &Path, expected_files: &BTreeSet<&str>) -> Result { - let entries = fs::read_dir(dir) - .map_err(|err| format!("failed to get list files of '{}': {:?}", dir.display(), err))?; +pub fn check_files_present(dir: &Path, expected_files: &BTreeSet<&str>) -> Result { + let entries = fs::read_dir(dir).map_err(wrap_io_err!(dir, "Reading list files"))?; let mut matches = 0; for entry_res in entries { - let entry = entry_res - .map_err(|err| format!("failed to get file entry of '{}': {:?}", dir.display(), err))?; + let entry = entry_res.map_err(wrap_io_err!(dir, "Reading file entry"))?; let filename = entry.file_name(); let Some(filename) = filename.to_str() else { @@ -217,29 +191,17 @@ pub fn check_files_present(dir: &Path, expected_files: &BTreeSet<&str>) -> Resul Ok(matches == expected_files.len()) } -pub fn rename(src: &Path, dst: &Path) -> Result<(), String> { - fs::rename(src, dst).map_err(|err| { - format!( - "failed to rename '{}' to '{}': {}\n{:?}", - src.display(), - dst.display(), - err, - err - ) - }) +pub fn rename(src: &Path, dst: &Path) -> Result<()> { + fs::rename(src, dst).map_err(wrap_io_err!(src, dst, "Renaming")) } -pub fn run_command(mut command: process::Command, stdout_pipe: &PtyOut) -> Result<(), String> { - let status = spawn_to_pipe(&mut command, stdout_pipe) - .map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))? +pub fn run_command(mut command: process::Command, stdout_pipe: &PtyOut) -> Result<()> { + let status = spawn_to_pipe(&mut command, stdout_pipe)? .wait() - .map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?; + .map_err(wrap_io_err!("waiting to exit"))?; if !status.success() { - return Err(format!( - "failed to run {:?}: exited with status {}", - command, status - )); + return Err(Error::Command(command, status)); } Ok(()) @@ -249,61 +211,51 @@ pub fn run_command_stdin( mut command: process::Command, stdin_data: &[u8], stdout_pipe: &PtyOut, -) -> Result<(), String> { +) -> Result<()> { command.stdin(Stdio::piped()); - let mut child = spawn_to_pipe(&mut command, stdout_pipe) - .map_err(|err| format!("failed to spawn {:?}: {}\n{:#?}", command, err, err))?; + let mut child = spawn_to_pipe(&mut command, stdout_pipe)?; if let Some(ref mut stdin) = child.stdin { - stdin.write_all(stdin_data).map_err(|err| { - format!( - "failed to write stdin of {:?}: {}\n{:#?}", - command, err, err - ) - })?; + stdin + .write_all(stdin_data) + .map_err(wrap_io_err!("Writing to stdin"))?; } else { - return Err(format!("failed to find stdin of {:?}", command)); + bail_other_err!("stdin is not captured"); } - let status = child - .wait() - .map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?; + let status = child.wait().map_err(wrap_io_err!("Spawning"))?; if !status.success() { - return Err(format!( - "failed to run {:?}: exited with status {}", - command, status - )); + return Err(Error::Command(command, status)); } Ok(()) } -pub fn serialize_and_write(file_path: &Path, content: &T) -> Result<(), String> { +pub fn serialize_and_write(file_path: &Path, content: &T) -> Result<()> { let toml_content = toml::to_string(content).map_err(|err| { - format!( + wrap_other_err!( "Failed to serialize content for '{}': {}", file_path.display(), err - ) + )() })?; - fs::write(file_path, toml_content) - .map_err(|err| format!("Failed to write to file '{}': {}", file_path.display(), err))?; + fs::write(file_path, toml_content).map_err(wrap_io_err!(file_path, "Writing to file"))?; Ok(()) } -pub fn offline_check_exists(path: &PathBuf) -> Result<(), String> { +pub fn offline_check_exists(path: &PathBuf) -> Result<()> { if !path.exists() { - return Err(format!( - "'{path}' is not exist and unable to continue in offline mode", + bail_other_err!( + "{path:?} is not exist and unable to continue in offline mode", path = path.display(), - ))?; + ); } Ok(()) } -pub fn download_wget(url: &str, dest: &PathBuf, logger: &PtyOut) -> Result<(), String> { +pub fn download_wget(url: &str, dest: &PathBuf, logger: &PtyOut) -> Result<()> { if !dest.is_file() { let dest_tmp = PathBuf::from(format!("{}.tmp", dest.display())); let mut command = Command::new("wget"); @@ -315,21 +267,19 @@ pub fn download_wget(url: &str, dest: &PathBuf, logger: &PtyOut) -> Result<(), S Ok(()) } -pub fn read_to_string(path: &Path) -> crate::Result { - fs::read_to_string(path).map_err(wrap_io_err!(path, "Reading file to string")) +pub fn read_to_string(path: &Path) -> Result { + fs::read_to_string(path).map_err(wrap_io_err!(path, "Reading file")) } /// get commit rev and return if it's detached or not -pub fn get_git_head_rev(dir: &PathBuf) -> Result<(String, bool), String> { +pub fn get_git_head_rev(dir: &PathBuf) -> Result<(String, bool)> { let git_head = dir.join(".git/HEAD"); - let head_str = fs::read_to_string(&git_head) - .map_err(|e| format!("unable to read {path}: {e}", path = git_head.display()))?; + let head_str = read_to_string(&git_head)?; if head_str.starts_with("ref: ") { let entry = head_str["ref: ".len()..].trim_end(); let git_ref = dir.join(".git").join(entry); let ref_str = if git_ref.is_file() { - fs::read_to_string(&git_ref) - .map_err(|e| format!("unable to read {path}: {e}", path = git_ref.display()))? + read_to_string(&git_ref)? } else { get_git_ref_entry(dir, entry)? }; @@ -340,44 +290,35 @@ pub fn get_git_head_rev(dir: &PathBuf) -> Result<(String, bool), String> { } /// get commit from "rev" which either a full commit hash or a tag name -pub fn get_git_tag_rev(dir: &PathBuf, tag: &str) -> Result { +pub fn get_git_tag_rev(dir: &PathBuf, tag: &str) -> Result { if tag.len() == 40 && tag.chars().all(|f| f.is_ascii_hexdigit()) { return Ok(tag.to_string()); } get_git_ref_entry(dir, &format!("refs/tags/{tag}")) } -pub fn get_git_ref_entry(dir: &PathBuf, entry: &str) -> Result { + +pub fn get_git_ref_entry(dir: &PathBuf, entry: &str) -> Result { let git_refs = dir.join(".git/packed-refs"); - let refs_str = fs::read_to_string(&git_refs) - .map_err(|e| format!("unable to read {path}: {e}", path = git_refs.display()))?; + let refs_str = read_to_string(&git_refs)?; for line in refs_str.lines() { if line.contains(entry) { let sha = line .split_whitespace() .next() - .ok_or_else(|| "packed-refs line is malformed.".to_string())?; + .ok_or_else(wrap_other_err!("Packed-refs line is malformed"))?; return Ok(sha.to_string()); } } - Err(format!("Could not find a rev for {}", entry)) + Err(wrap_other_err!("Could not find a rev for {}", entry)()) } /// get commit rev after fetch -pub fn get_git_fetch_rev( - dir: &PathBuf, - remote_url: &str, - remote_branch: &str, -) -> Result { +pub fn get_git_fetch_rev(dir: &PathBuf, remote_url: &str, remote_branch: &str) -> Result { let git_fetch_head = dir.join(".git/FETCH_HEAD"); - let fetch_head_content = fs::read_to_string(&git_fetch_head).map_err(|e| { - format!( - "unable to read {path}: {e}", - path = git_fetch_head.display() - ) - })?; + let fetch_head_content = read_to_string(&git_fetch_head)?; let expected_comment_part = format!("branch '{}' of {}", remote_branch, remote_url); @@ -386,26 +327,25 @@ pub fn get_git_fetch_rev( let sha = line .split_whitespace() .next() - .ok_or_else(|| "FETCH_HEAD line is malformed.".to_string())?; + .ok_or_else(wrap_other_err!("FETCH_HEAD line is malformed"))?; return Ok(sha.to_string()); } } - Err(format!( + Err(wrap_other_err!( "Could not find a fetch target for tracking {}", expected_comment_part - )) + )()) } /// (local_branch_name, remote_branch, remote_name, remote_url) /// -> ("fix_stuff", "master", "origin", "https://gitlab.redox-os.org/willnode/redox") -pub fn get_git_remote_tracking(dir: &PathBuf) -> Result<(String, String, String, String), String> { +pub fn get_git_remote_tracking(dir: &PathBuf) -> Result<(String, String, String, String)> { let git_head = dir.join(".git/HEAD"); let git_config = dir.join(".git/config"); - let head_content = fs::read_to_string(&git_head) - .map_err(|e| format!("unable to read {path}: {e}", path = git_head.display()))?; + let head_content = read_to_string(&git_head)?; if !head_content.starts_with("ref: ") { let sha = head_content.trim_end().to_string(); @@ -415,8 +355,7 @@ pub fn get_git_remote_tracking(dir: &PathBuf) -> Result<(String, String, String, let local_branch_path = head_content["ref: ".len()..].trim_end(); let local_branch_name = get_git_branch_name(local_branch_path)?; - let config_content = fs::read_to_string(&git_config) - .map_err(|e| format!("unable to read {path}: {e}", path = git_config.display()))?; + let config_content = read_to_string(&git_config)?; let branch_section = format!("[branch \"{}\"]", local_branch_name); let mut remote_name: Option = None; @@ -446,8 +385,10 @@ pub fn get_git_remote_tracking(dir: &PathBuf) -> Result<(String, String, String, } } - let remote_name_str = remote_name - .ok_or_else(|| format!("Branch '{}' is not tracking a remote.", local_branch_name))?; + let remote_name_str = remote_name.ok_or_else(wrap_other_err!( + "Branch {:?} is not tracking a remote", + local_branch_name + ))?; let remote_branch_str = remote_branch.unwrap_or("".into()); let remote_section = format!("[remote \"{}\"]", remote_name_str); @@ -476,12 +417,10 @@ pub fn get_git_remote_tracking(dir: &PathBuf) -> Result<(String, String, String, } } - let remote_url_str = remote_url.ok_or_else(|| { - format!( - "Could not find URL for remote '{}' in .git/config.", - remote_name_str - ) - })?; + let remote_url_str = remote_url.ok_or_else(wrap_other_err!( + "Could not find URL for remote {:?} in .git/config.", + remote_name_str + ))?; Ok(( local_branch_name, @@ -498,11 +437,14 @@ pub(crate) fn chop_dot_git(url: &str) -> &str { url } -fn get_git_branch_name(local_branch_path: &str) -> Result { +fn get_git_branch_name(local_branch_path: &str) -> Result { // TODO: incorrectly handle branch with slashes Ok(local_branch_path .split('/') .last() - .ok_or_else(|| format!("Failed to parse branch name of {:?}", local_branch_path))? + .ok_or_else(wrap_other_err!( + "Failed to parse branch name of {:?}", + local_branch_path + ))? .to_string()) } diff --git a/src/cook/pty.rs b/src/cook/pty.rs index 208359a6e..373817e3b 100644 --- a/src/cook/pty.rs +++ b/src/cook/pty.rs @@ -1,4 +1,3 @@ -use anyhow::{Error, bail}; use libc::{self, winsize}; use std::fs::File; use std::io::{Read, Write}; @@ -15,6 +14,8 @@ use std::{ pub use std::os::unix::io::RawFd; +use crate::{Error, Result, wrap_io_err}; + #[macro_export] macro_rules! log_to_pty { ($logger:expr, $($arg:tt)+) => { @@ -66,10 +67,10 @@ pub fn flush_pty(logger: &mut PtyOut) { let _ = file.flush(); } -pub fn spawn_to_pipe(command: &mut Command, stdout_pipe: &PtyOut) -> Result { +pub fn spawn_to_pipe(command: &mut Command, stdout_pipe: &PtyOut) -> Result { match stdout_pipe { Some(stdout) => stdout.0.spawn_command(command.into()), - None => Ok(command.spawn()?), + None => Ok(command.spawn().map_err(wrap_io_err!("Spawning"))?), } } @@ -107,7 +108,7 @@ impl Default for PtySize { } } -fn openpty(size: PtySize) -> anyhow::Result<(UnixMasterPty, UnixSlavePty)> { +fn openpty(size: PtySize) -> Result<(UnixMasterPty, UnixSlavePty)> { let mut master: RawFd = -1; let mut slave: RawFd = -1; @@ -131,7 +132,7 @@ fn openpty(size: PtySize) -> anyhow::Result<(UnixMasterPty, UnixSlavePty)> { }; if result != 0 { - bail!("failed to openpty: {:?}", io::Error::last_os_error()); + return Err(Error::from_last_io_error("Opening openpty")); } let master = UnixMasterPty { @@ -159,7 +160,7 @@ pub struct PtyPair { } impl UnixPtySystem { - fn openpty(&self, size: PtySize) -> anyhow::Result { + fn openpty(&self, size: PtySize) -> Result { let (master, slave) = openpty(size)?; Ok(PtyPair { master: master, @@ -177,7 +178,7 @@ impl std::ops::Deref for PtyFd { } impl Read for PtyFd { - fn read(&mut self, buf: &mut [u8]) -> Result { + fn read(&mut self, buf: &mut [u8]) -> io::Result { match self.0.read(buf) { Err(ref e) if e.raw_os_error() == Some(libc::EIO) => { // EIO indicates that the slave pty has been closed. @@ -192,7 +193,7 @@ impl Read for PtyFd { } impl PtyFd { - fn resize(&self, size: PtySize) -> Result<(), Error> { + fn resize(&self, size: PtySize) -> Result<()> { let ws_size = winsize { ws_row: size.rows, ws_col: size.cols, @@ -208,16 +209,13 @@ impl PtyFd { ) } != 0 { - bail!( - "failed to ioctl(TIOCSWINSZ): {:?}", - io::Error::last_os_error() - ); + return Err(Error::from_last_io_error("ioctl resize (TIOCSWINSZ)")); } Ok(()) } - fn get_size(&self) -> Result { + fn get_size(&self) -> Result { let mut size: winsize = unsafe { mem::zeroed() }; if unsafe { libc::ioctl( @@ -227,10 +225,7 @@ impl PtyFd { ) } != 0 { - bail!( - "failed to ioctl(TIOCGWINSZ): {:?}", - io::Error::last_os_error() - ); + return Err(Error::from_last_io_error("ioctl get size (TIOCGWINSZ)")); } Ok(PtySize { rows: size.ws_row, @@ -240,12 +235,12 @@ impl PtyFd { }) } - fn spawn_command(&self, cmd: &mut Command) -> anyhow::Result { + fn spawn_command(&self, cmd: &mut Command) -> Result { unsafe { cmd // .stdin(self.as_stdio()?) - .stdout(self.try_clone()?) - .stderr(self.try_clone()?) + .stdout(self.try_clone().map_err(wrap_io_err!("Cloning pty"))?) + .stderr(self.try_clone().map_err(wrap_io_err!("Cloning pty"))?) .pre_exec(move || { // Clean up a few things before we exec the program // Clear out any potentially problematic signal @@ -273,7 +268,7 @@ impl PtyFd { }) }; - let mut child = cmd.spawn()?; + let mut child = cmd.spawn().map_err(wrap_io_err!("Spawning cmd"))?; // Ensure that we close out the slave fds that Child retains; // they are not what we need (we need the master side to reference @@ -287,8 +282,8 @@ impl PtyFd { Ok(child) } - fn flush(&mut self) -> std::io::Result<()> { - self.0.flush() + fn flush(&mut self) -> Result<()> { + self.0.flush().map_err(wrap_io_err!("Flushing pty")) } } @@ -305,46 +300,44 @@ pub struct UnixSlavePty { } /// Helper function to set the close-on-exec flag for a raw descriptor -fn cloexec(fd: RawFd) -> Result<(), Error> { +fn cloexec(fd: RawFd) -> Result<()> { let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; if flags == -1 { - bail!( - "fcntl to read flags failed: {:?}", - io::Error::last_os_error() - ); + return Err(Error::from_last_io_error("fcntl to read flags")); } let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) }; if result == -1 { - bail!( - "fcntl to set CLOEXEC failed: {:?}", - io::Error::last_os_error() - ); + return Err(Error::from_last_io_error("fcntl to set CLOEXEC")); } Ok(()) } impl UnixSlavePty { - fn spawn_command(&self, builder: &mut Command) -> Result { + fn spawn_command(&self, builder: &mut Command) -> Result { Ok(self.fd.spawn_command(builder)?) } - fn flush(&mut self) -> Result<(), anyhow::Error> { - Ok(self.fd.flush()?) + fn flush(&mut self) -> Result<()> { + self.fd.flush() } } impl UnixMasterPty { #[allow(unused)] - fn resize(&self, size: PtySize) -> Result<(), Error> { + fn resize(&self, size: PtySize) -> Result<()> { self.fd.resize(size) } #[allow(unused)] - fn get_size(&self) -> Result { + fn get_size(&self) -> Result { self.fd.get_size() } - fn try_clone_reader(&self) -> Result, Error> { - let fd = PtyFd(self.fd.try_clone()?); + fn try_clone_reader(&self) -> Result> { + let fd = PtyFd( + self.fd + .try_clone() + .map_err(wrap_io_err!("Cloning pty fd"))?, + ); Ok(Box::new(fd)) } } diff --git a/src/lib.rs b/src/lib.rs index 7e3676a37..d7284a32a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,9 +18,14 @@ pub fn is_redox() -> bool { // Errors +use std::fmt::Display; use std::io; use std::path::PathBuf; +use std::process::{Command, ExitStatus}; +/// Error types used through cookbook. +/// +/// When writing IO context, don't use "Failed at XXX". Look at impl Display for suitable word to use. #[derive(Debug)] pub enum Error { Io { @@ -28,11 +33,68 @@ pub enum Error { path: Option, context: &'static str, }, + FileIo { + source: io::Error, + src: PathBuf, + dst: PathBuf, + context: &'static str, + }, + Command(Command, ExitStatus), Package(pkg::PackageError), Pkgar(pkgar::Error), Other(String), } +impl Error { + pub fn from_last_io_error(context: &'static str) -> Error { + wrap_io_err!(context)(io::Error::last_os_error()) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Io { + source, + path, + context, + } => { + if let Some(path) = path { + write!(f, "{context} failed at \"{}\": {}", path.display(), source) + } else { + write!(f, "{context} failed: {}", source) + } + } + Error::FileIo { + source, + src, + dst, + context, + } => { + write!( + f, + "{context} failed from \"{}\" to \"{}\": {}", + src.display(), + dst.display(), + source + ) + } + Error::Command(command, exit_status) => { + write!( + f, + "Failed to run [{:?}]: exited with status {}", + command, exit_status + ) + } + Error::Package(package_error) => write!(f, "{}", package_error), + Error::Pkgar(error) => write!(f, "{}", error), + Error::Other(context) => { + write!(f, "{context}") + } + } + } +} + macro_rules! wrap_io_err { ($context:expr) => { |source| crate::Error::Io { @@ -48,14 +110,45 @@ macro_rules! wrap_io_err { context: $context, } }; + ($src:expr, $dst:expr, $context:expr) => { + |source| crate::Error::FileIo { + source, + src: $src.to_path_buf(), + dst: $dst.to_path_buf(), + context: $context, + } + }; } +macro_rules! wrap_other_err { + ($($arg:tt)*) => { + || crate::Error::Other(format!($($arg)*)) + }; +} + +macro_rules! bail_other_err { + ($($arg:tt)*) => { + return Err(crate::Error::Other(format!($($arg)*))) + }; +} + +impl From<&'static str> for Error { + fn from(value: &'static str) -> Self { + Error::Other(value.to_string()) + } +} impl From for Error { fn from(value: String) -> Self { Error::Other(value) } } +impl From for String { + fn from(val: Error) -> Self { + format!("{}", val) + } +} + impl From for Error { fn from(value: pkg::PackageError) -> Self { Error::Package(value) @@ -79,6 +172,28 @@ impl From for Error { } } +impl From for Error { + fn from(value: walkdir::Error) -> Self { + if value.io_error().is_some() { + let path = value.path().map(|s| s.to_path_buf()); + Error::Io { + source: value.into_io_error().unwrap(), + path: path, + context: "Walkdir error", + } + } else { + wrap_other_err!( + "Walkdir file system loop found at {:?}", + value.path().map(|s| s.to_string_lossy().to_string()), + )() + } + } +} + pub(crate) type Result = std::result::Result; pub(crate) use wrap_io_err; + +pub(crate) use wrap_other_err; + +pub(crate) use bail_other_err;