diff --git a/src/main.rs b/src/main.rs index 4f1e572..1ae70f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -122,6 +122,8 @@ fn print_stats() #[tokio::main] async fn main() -> Result<(), Box> { + log::init(log::Level::Debug); + let child = sys::fork::detach_closure(None, None, |parent| { println!("Parent: {:?}, us: {}", parent, sys::get_pid()); std::thread::sleep_ms(3000); @@ -129,10 +131,9 @@ async fn main() -> Result<(), Box> { }).await?; println!("Child: {:?}", child); - println!("Waitpid: {:?}", child.wait().await?); + println!("Waitpid: {:?}", child.wait().await.map_err(|x| x.to_string())); // end test - log::init(log::Level::Debug); log::Logger::global().add_hook(log::custom::LogFileHook::new(log::Level::Debug, "test.log", "test.err.log", false)); debug!("Logger initialised"); //TODO: Parse config first diff --git a/src/sys/errno.rs b/src/sys/errno.rs new file mode 100644 index 0000000..718eb20 --- /dev/null +++ b/src/sys/errno.rs @@ -0,0 +1,150 @@ +//! Errno handling + +use super::*; + +use std::{ + error, + fmt, +}; +use libc::__errno_location; +use once_cell::sync::OnceCell; + +const MESSAGE_BUF_SIZE: usize = 512; + +/// Get `errno` value. +#[inline] pub fn raw() -> i32 +{ + unsafe{*__errno_location()} +} + +fn get_message(errno: i32) -> String +{ + use libc::{ + strerror_r, + }; + + use std::ffi::CStr; + + let mut buffer = [0u8; MESSAGE_BUF_SIZE+1]; + let cstr = unsafe { + strerror_r(errno, &mut buffer[0] as *mut u8 as *mut libc::c_char, MESSAGE_BUF_SIZE); + CStr::from_ptr(&buffer[0] as *const u8 as *const libc::c_char) + }; + + cstr.to_string_lossy().into_owned() +} + +#[derive(Debug)] +/// Errno wrapper for Rust errors +pub struct Errno +where T: fmt::Debug +{ + errno: i32, + msg: OnceCell, + pub internal: T, +} + +impl Errno +where T: error::Error + 'static +{ + /// Get the message for this errno + pub fn message(&self) -> &str + { + &self.msg.get_or_init(|| get_message(self.errno))[..] + } + + /// Get the errno of this instance + pub fn error(&self) -> i32 + { + self.errno + } + + /// Consume this instance, returning the wrapped value + pub fn into_inner(self) -> T + { + self.internal + } + + /// Map the inner value + pub fn map_inner(self, fun: F) -> Errno + where F: FnOnce(T) -> U, + U: error::Error + 'static + { + Errno { + internal: fun(self.internal), + errno: self.errno, + msg: self.msg, + } + } + + /// Check if this `errno` value is 0 (`Success`) + pub fn is_success(&self) -> bool + { + self.errno == 0 + } +} + +impl std::error::Error for Errno +where T: error::Error + 'static +{ + fn source(&self) -> Option<&(dyn error::Error + 'static)> + { + Some(&self.internal) + } +} + +impl std::fmt::Display for Errno +where T: error::Error + 'static +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}: {} ({})", self.internal, self.message(), self.errno) + } +} + +impl Errno +where T: error::Error + 'static +{ + /// Create a new errno wrapper with specific `errno` value + pub fn with_errno(err: T, errno: i32) -> Self + { + Self { + errno, + msg: OnceCell::new(), + internal: err + } + } + /// Create a new errno wrapper with the current `errno` value + #[inline] pub fn new(err: T) -> Self + { + Self::with_errno(err, raw()) + } +} + +impl From for Errno +where T: error::Error + 'static +{ + #[inline] fn from(from: T) -> Self + { + Self::new(from) + } +} + +pub trait ResultExt +{ + /// Call `map_inner()` on the inner error of `Errno` if it is `Err`, otherwise return the same `Ok`. + fn map_inner(self, fun: F) -> Result> + where F: FnOnce(T) -> U, + U: error::Error + 'static; +} + +impl ResultExt for Result> +where T: error::Error + 'static +{ + fn map_inner(self, fun: F) -> Result> + where F: FnOnce(T) -> U, + U: error::Error + 'static + { + self.map_err(|e| e.map_inner(fun)) + } +} diff --git a/src/sys/fork.rs b/src/sys/fork.rs index 59557fa..9803149 100644 --- a/src/sys/fork.rs +++ b/src/sys/fork.rs @@ -1,6 +1,7 @@ //! Forking utils use super::*; +use errno::Errno; use libc::{ fork, @@ -13,8 +14,15 @@ use std::{ use crate::util::PhantomDrop; use cfg_if::cfg_if; +use super::pipe::{ + self, + unix_pipe, + pipe_read_value, + pipe_write_value, +}; + /// Forking error -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] #[repr(u32)] pub enum Error { Fork = 1, @@ -30,6 +38,22 @@ pub enum Error { Unknown = 0, } +impl From for Error +{ + fn from(from: pipe::Error) -> Self + { + use pipe::Error::*; + match from { + Create => Self::Pipe, + Read => Self::PipeRead, + Write => Self::PipeWrite, + Broken => Self::PipeBroken, + _ => Self::Unknown, + } + } +} + + impl Error { #[inline] fn from_u32(from: u32) -> Self @@ -96,84 +120,14 @@ pub struct Parent { pid: i32, } -/// Create with `pipe()` -fn unix_pipe() -> Result<(i32,i32), Error> -{ - use libc::{ - pipe, - }; - let mut pipe_fd: [libc::c_int; 2] = [0;2]; - if unsafe{pipe(&mut pipe_fd[0] as *mut libc::c_int)} == 0{ - Ok((pipe_fd[0], pipe_fd[1])) - } else { - Err(Error::Pipe) - } -} - -fn pipe_write(output: i32, buf: impl AsRef<[u8]>) -> Result -{ - let buf = buf.as_ref(); - let len = buf.len(); - - let read = unsafe{libc::write(output, &buf[0] as *const u8 as *const libc::c_void, len)}; - - if read < 0 { - Err(Error::PipeWrite) - } else { - Ok(read as usize) - } -} - - -fn pipe_read(input: i32, mut buf: impl AsMut<[u8]>) -> Result -{ - let buf = buf.as_mut(); - let len = buf.len(); - - let read = unsafe{libc::read(input, &mut buf[0] as *mut u8 as *mut libc::c_void, len)}; - - if read < 0 { - Err(Error::PipeRead) - } else { - Ok(read as usize) - } -} - -unsafe fn pipe_write_value(fd: i32, value: &T) -> Result<(), Error> -{ - let sz = std::mem::size_of_val(value); - - let write = pipe_write(fd, std::slice::from_raw_parts(value as *const T as *const u8, sz))?; - if write == sz { - Ok(()) - } else { - Err(Error::PipeBroken) - } -} - -unsafe fn pipe_read_value(fd: i32) -> Result -{ - use malloc_array::heap; - - let mut buf = heap![unsafe 0u8; std::mem::size_of::()]; - - let read = pipe_read(fd, &mut buf)?; - - if read == buf.len() { - let ptr = buf.reinterpret::(); - Ok(ptr.as_ptr().read()) - } else { - Err(Error::PipeBroken) - } -} /// Run a closure as a fork and then exit, optionally trying to set `uid` and `gid`. /// /// # Notes /// This fork does not set up the async runtime. Using Tokio from the child process will require manually setting up the runtime -pub async fn detach_closure(as_uid: Option, as_gid: Option, into: F) -> Result { +pub async fn detach_closure(as_uid: Option, as_gid: Option, into: F) -> Result> { - let (rx, tx) = unix_pipe()?; + let (rx, tx) = unix_pipe().map_inner(|x| Error::from(x))?; let child = unsafe{fork()}; if child == 0 { @@ -238,7 +192,7 @@ pub async fn detach_closure(as_uid: Option, as_gid: Opti task, }; let waiter = task::spawn_blocking(move || { - pipe_read_value(rx) + pipe_read_value(rx).map_inner(|e| Error::from(e)) }); waiter.await.expect("Panic while waiting for child status")? @@ -252,14 +206,15 @@ pub async fn detach_closure(as_uid: Option, as_gid: Opti if err == !0u32 { Ok(Child{pid:child}) } else { - Err(err.into()) + Err(Error::from(err).into()) } } else { + let rval = Error::Fork.into(); unsafe { libc::close(tx); libc::close(rx); } - Err(Error::Fork) + Err(rval) } } @@ -270,52 +225,52 @@ impl Child /// # Notes /// - When `threaded` feature is disabled, this function will not block at all, instead returning error if there is not status available, this is to prevent deadlock. /// - This function (at present) will return `Error` when the child process exits, too. This is because we don't have `errno` access yet, I'm working on it. - pub async fn waitpid(&self) -> Result + pub async fn waitpid(&self) -> Result> { cfg_if! { if #[cfg(feature="threaded")] { let pid = self.pid; let waiter = tokio::task::spawn_blocking(move || { let mut status: i32 = 0; - (unsafe{libc::waitpid(pid, &mut status as *mut i32, 0)}, status) + let (out, status) = (unsafe{libc::waitpid(pid, &mut status as *mut i32, 0)}, status); + if out != pid { + Err(Errno::from(Error::WaitPid)) + } else { + Ok(status) + } }); - if let (0, status) = waiter.await.expect("Waiter panicked") { - Ok(status) - } else { - Err(Error::WaitPid) - } + waiter.await.expect("Waiter panicked") } else { let mut status: i32 = 0; - if unsafe{libc::waitpid(self.pid, &mut status as *mut i32, 1)} == 0 { // We can't afford to block here + if unsafe{libc::waitpid(self.pid, &mut status as *mut i32, 1)} == pid { // We can't afford to block here Ok(status) } else { - Err(Error::WaitPid) + Err(Error::WaitPid.into()) } - } } } - /// Wait for the child process to end, ignoring any other signals + /// Wait for the child process to end, ignoring any other signals. /// /// # Notes /// - When `threaded` feature is disabled, this function will return immediately, this is an error-handling bug because we don't have `errno` access as of yet. /// - This function will call `waitpid` untill it returns status `ECHILD` (child has exited). Other status values are ignored, if any. - pub async fn wait(&self) -> Result<(), Error> + pub async fn wait(&self) -> Result<(), Errno> { loop { match self.waitpid().await { Ok(v) => { - // we don't want this, we want it to end - debug!("Got status {} from child {}, ignoring", v, self.pid); + // keep going until we get `no child process' + debug!("Got status {} from child {}, ignoring. ", v, self.pid); + }, + Err(e) if e.internal == Error::WaitPid && e.error() == 10 /* No child processes `ECHILD` */ => { + //debug!("Error: {}", e); + break Ok(()); }, Err(e) => { - if let Error::WaitPid = e { - return Ok(()); - } else { - return Err(e); - } - } + break Err(e); + }, } } } diff --git a/src/sys/mod.rs b/src/sys/mod.rs index c17bddc..904ba2d 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -4,8 +4,12 @@ use super::*; pub mod user; +pub mod pipe; pub mod fork; +pub mod errno; +use errno::ResultExt; + /// Get pid of current process #[inline] pub fn get_pid() -> i32 { diff --git a/src/sys/pipe.rs b/src/sys/pipe.rs index ba6558f..aa30b25 100644 --- a/src/sys/pipe.rs +++ b/src/sys/pipe.rs @@ -1,3 +1,105 @@ -//! `pipe()` wrapper +//! `pipe()` related operations use super::*; +use errno::Errno; +use std::{ + fmt, +}; + +/// Represents an error on `pipe()` related operations +#[derive(Debug)] +pub enum Error { + + Create, + Read, + Write, + Broken, + + Unknown, +} +impl std::error::Error for Error{} +impl std::fmt::Display for Error +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match self { + Self::Create => write!(f, "failed to create pipe"), + Self::Read => write!(f, "failed to read from pipe"), + Self::Write => write!(f, "failed to write to pipe"), + Self::Broken => write!(f, "broken pipe"), + _ => write!(f, "unknown error"), + } + } +} + +/// Create with `pipe()` +pub(super) fn unix_pipe() -> Result<(i32,i32), Errno> +{ + use libc::{ + pipe, + }; + let mut pipe_fd: [libc::c_int; 2] = [0;2]; + if unsafe{pipe(&mut pipe_fd[0] as *mut libc::c_int)} == 0{ + Ok((pipe_fd[0], pipe_fd[1])) + } else { + Err(Error::Create.into()) + } +} + +/// Write to a pipe +pub(super) fn pipe_write(output: i32, buf: impl AsRef<[u8]>) -> Result> +{ + let buf = buf.as_ref(); + let len = buf.len(); + + let read = unsafe{libc::write(output, &buf[0] as *const u8 as *const libc::c_void, len)}; + + if read < 0 { + Err(Error::Write.into()) + } else { + Ok(read as usize) + } +} + + +pub(super) fn pipe_read(input: i32, mut buf: impl AsMut<[u8]>) -> Result> +{ + let buf = buf.as_mut(); + let len = buf.len(); + + let read = unsafe{libc::read(input, &mut buf[0] as *mut u8 as *mut libc::c_void, len)}; + + if read < 0 { + Err(Error::Read.into()) + } else { + Ok(read as usize) + } +} + +pub(super) unsafe fn pipe_write_value(fd: i32, value: &T) -> Result<(), Errno> +{ + let sz = std::mem::size_of_val(value); + + let write = pipe_write(fd, std::slice::from_raw_parts(value as *const T as *const u8, sz))?; + if write == sz { + Ok(()) + } else { + Err(Error::Broken.into()) + } +} + +pub(super) unsafe fn pipe_read_value(fd: i32) -> Result> +{ + use malloc_array::heap; + + let mut buf = heap![unsafe 0u8; std::mem::size_of::()]; + + let read = pipe_read(fd, &mut buf)?; + + if read == buf.len() { + let ptr = buf.reinterpret::(); + Ok(ptr.as_ptr().read()) + } else { + Err(Error::Broken.into()) + } +}