errno handling

master
Avril 4 years ago
parent 3d157e51d8
commit afa1d93212
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -122,6 +122,8 @@ fn print_stats()
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
}).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

@ -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<T>
where T: fmt::Debug
{
errno: i32,
msg: OnceCell<String>,
pub internal: T,
}
impl<T> Errno<T>
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<U,F>(self, fun: F) -> Errno<U>
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<T> std::error::Error for Errno<T>
where T: error::Error + 'static
{
fn source(&self) -> Option<&(dyn error::Error + 'static)>
{
Some(&self.internal)
}
}
impl<T> std::fmt::Display for Errno<T>
where T: error::Error + 'static
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{}: {} ({})", self.internal, self.message(), self.errno)
}
}
impl<T> Errno<T>
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<T> From<T> for Errno<T>
where T: error::Error + 'static
{
#[inline] fn from(from: T) -> Self
{
Self::new(from)
}
}
pub trait ResultExt<T,V>
{
/// Call `map_inner()` on the inner error of `Errno<T>` if it is `Err`, otherwise return the same `Ok`.
fn map_inner<U,F>(self, fun: F) -> Result<V,Errno<U>>
where F: FnOnce(T) -> U,
U: error::Error + 'static;
}
impl<T,V> ResultExt<T,V> for Result<V,Errno<T>>
where T: error::Error + 'static
{
fn map_inner<U,F>(self, fun: F) -> Result<V,Errno<U>>
where F: FnOnce(T) -> U,
U: error::Error + 'static
{
self.map_err(|e| e.map_inner(fun))
}
}

@ -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<pipe::Error> 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<usize, Error>
{
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<usize, Error>
{
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<T: ?Sized>(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<T>(fd: i32) -> Result<T, Error>
{
use malloc_array::heap;
let mut buf = heap![unsafe 0u8; std::mem::size_of::<T>()];
let read = pipe_read(fd, &mut buf)?;
if read == buf.len() {
let ptr = buf.reinterpret::<T>();
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<F: FnOnce(Parent)>(as_uid: Option<u32>, as_gid: Option<u32>, into: F) -> Result<Child, Error> {
pub async fn detach_closure<F: FnOnce(Parent)>(as_uid: Option<u32>, as_gid: Option<u32>, into: F) -> Result<Child, Errno<Error>> {
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<F: FnOnce(Parent)>(as_uid: Option<u32>, 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<F: FnOnce(Parent)>(as_uid: Option<u32>, 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<i32, Error>
pub async fn waitpid(&self) -> Result<i32, Errno<Error>>
{
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<Error>>
{
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);
},
}
}
}

@ -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
{

@ -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<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::Create.into())
}
}
/// Write to a pipe
pub(super) fn pipe_write(output: i32, buf: impl AsRef<[u8]>) -> Result<usize, Errno<Error>>
{
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<usize, Errno<Error>>
{
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<T: ?Sized>(fd: i32, value: &T) -> Result<(), Errno<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::Broken.into())
}
}
pub(super) unsafe fn pipe_read_value<T>(fd: i32) -> Result<T, Errno<Error>>
{
use malloc_array::heap;
let mut buf = heap![unsafe 0u8; std::mem::size_of::<T>()];
let read = pipe_read(fd, &mut buf)?;
if read == buf.len() {
let ptr = buf.reinterpret::<T>();
Ok(ptr.as_ptr().read())
} else {
Err(Error::Broken.into())
}
}

Loading…
Cancel
Save