You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
7.3 KiB
316 lines
7.3 KiB
//! Forking utils
|
|
|
|
use super::*;
|
|
use errno::Errno;
|
|
|
|
use libc::{
|
|
fork,
|
|
setgid,
|
|
setuid,
|
|
};
|
|
use std::{
|
|
fmt,
|
|
};
|
|
use cfg_if::cfg_if;
|
|
|
|
use super::pipe;
|
|
|
|
/// Forking error
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
|
|
#[repr(u32)]
|
|
pub enum Error {
|
|
Fork = 1,
|
|
SetUid = 2,
|
|
SetGid = 3,
|
|
|
|
Pipe = 4,
|
|
PipeRead = 5,
|
|
PipeWrite = 6,
|
|
PipeBroken = 7,
|
|
WaitPid = 8,
|
|
|
|
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
|
|
{
|
|
use Error::*;
|
|
match from {
|
|
x if x == Fork as u32 => Fork,
|
|
x if x == SetUid as u32 => SetUid,
|
|
x if x == SetGid as u32 => SetGid,
|
|
x if x == Pipe as u32 => Pipe,
|
|
x if x == PipeRead as u32 => PipeRead,
|
|
x if x == PipeWrite as u32 => PipeWrite,
|
|
x if x == PipeBroken as u32 => PipeBroken,
|
|
x if x == WaitPid as u32 => WaitPid,
|
|
_ => Self::Unknown,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<u32> for Error
|
|
{
|
|
#[inline] fn from(from: u32) -> Self
|
|
{
|
|
Self::from_u32(from)
|
|
}
|
|
}
|
|
|
|
impl From<Error> for u32
|
|
{
|
|
#[inline] fn from(from: Error) -> Self
|
|
{
|
|
from as Self
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for Error{}
|
|
impl std::fmt::Display for Error
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
match self {
|
|
Self::Fork => write!(f, "fork() failed"),
|
|
Self::Pipe => write!(f, "pipe() failed"),
|
|
Self::PipeBroken => write!(f, "broken pipe with child"),
|
|
Self::PipeRead => write!(f, "pipe read failed"),
|
|
Self::PipeWrite => write!(f, "pipe write failed"),
|
|
Self::SetGid => write!(f, "child reported setgid() failed"),
|
|
Self::SetUid => write!(f, "child reported setuid() failed"),
|
|
Self::WaitPid => write!(f, "waitpid() failed unexpectedly"),
|
|
_ => write!(f, "child reported unknown error"),
|
|
}
|
|
}
|
|
}
|
|
|
|
use pipe::Pipe;
|
|
|
|
/// Represents the detached child
|
|
#[derive(Debug)]
|
|
pub struct Child
|
|
{
|
|
pid: i32,
|
|
|
|
comm: Pipe,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Parent {
|
|
pid: i32,
|
|
|
|
comm: Pipe,
|
|
}
|
|
|
|
/// Run a closure as a fork and then exit, optionally trying to set `uid` and `gid`.
|
|
///
|
|
/// # Notes
|
|
/// This seems to corrupt the async runtime for the child, do not use it from the child.
|
|
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().map_inner(|x| Error::from(x))?;
|
|
let (mut ttx,mut trx) = pipe::multi().map_err(|x| Error::from(x))?;
|
|
|
|
let (comm_p, comm_c) = pipe::multi().map_err(|x| Error::from(x))?;
|
|
let child = unsafe{fork()};
|
|
if child == 0 {
|
|
// Is child
|
|
|
|
use tokio::prelude::*;
|
|
let complete = || {
|
|
async {
|
|
unsafe {
|
|
if let Ok(_) = ttx.write_u32(!0u32).await
|
|
{
|
|
into(Parent{pid: libc::getppid(),comm:comm_c});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
unsafe {
|
|
loop {
|
|
if let Some(as_uid) = as_uid {
|
|
// Set UID
|
|
if setuid(as_uid) != 0 {
|
|
let _ = ttx.write_u32(Error::SetUid.into()).await;
|
|
break;
|
|
}
|
|
}
|
|
if let Some(as_gid) = as_gid {
|
|
// Set GID
|
|
if setgid(as_gid) != 0 {
|
|
let _ = ttx.write_u32(Error::SetGid.into()).await;
|
|
break;
|
|
}
|
|
}
|
|
|
|
complete().await;
|
|
break;
|
|
}
|
|
}
|
|
std::process::exit(0);
|
|
} else if child > 0 {
|
|
// Fork succeeded
|
|
use tokio::prelude::*;
|
|
|
|
let err: u32 = unsafe{
|
|
timeout!(trx.read_u32(), tokio::time::Duration::from_secs(1)).unwrap_or(Ok(Error::Unknown as u32)).unwrap_or(Error::Unknown as u32)
|
|
};
|
|
if err == !0u32 {
|
|
Ok(Child{pid:child, comm: comm_p})
|
|
} else {
|
|
Err(Error::from(err).into())
|
|
}
|
|
} else {
|
|
let rval = Error::Fork.into();
|
|
Err(rval)
|
|
}
|
|
}
|
|
|
|
impl Child
|
|
{
|
|
/// Call `waitpid` on this child. Returns the status code if possible, or `Error` if error.
|
|
///
|
|
/// `Child` implements `Future`, and waiting on that directly is probably preferable to calling this method as it does no blocking.
|
|
///
|
|
/// # 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.
|
|
#[deprecated] 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;
|
|
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)
|
|
}
|
|
});
|
|
waiter.await.expect("Waiter panicked")
|
|
} else {
|
|
let pid = self.pid;
|
|
let mut status: i32 = 0;
|
|
if unsafe{libc::waitpid(pid, &mut status as *mut i32, 1)} == pid { // We can't afford to block here
|
|
Ok(status)
|
|
} else {
|
|
Err(Error::WaitPid.into())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Wait for the child process to end, ignoring any other signals.
|
|
///
|
|
/// `Child` implements `Future`, and waiting on that directly is probably preferable to calling this method as it does no blocking.
|
|
///
|
|
/// # 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.
|
|
#[deprecated] pub async fn wait(&self) -> Result<(), Errno<Error>>
|
|
{
|
|
loop {
|
|
#[allow(deprecated)]
|
|
match self.waitpid().await {
|
|
Ok(v) => {
|
|
// 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) => {
|
|
break Err(e);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
use futures::Future;
|
|
|
|
|
|
use std::{
|
|
pin::Pin,
|
|
task::{
|
|
Context,
|
|
Poll,
|
|
},
|
|
};
|
|
impl Future for Child
|
|
{
|
|
type Output = Result<(), Errno<Error>>;
|
|
|
|
fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output>
|
|
{
|
|
let pid = self.pid;
|
|
let future = async {
|
|
loop {
|
|
let mut status: i32 = 0;
|
|
if unsafe{libc::waitpid(pid, &mut status as *mut i32, 1)} == pid {
|
|
if status == 0 {
|
|
tokio::task::yield_now().await;
|
|
} else {
|
|
//We got a signal, but we don't care.
|
|
debug!("Got status {} from child {}, ignoring. ", status, pid);
|
|
tokio::task::yield_now().await;
|
|
}
|
|
} else {
|
|
let err: Errno<Error> = Errno::from(Error::WaitPid).into();
|
|
if err.error() == 0 {
|
|
tokio::task::yield_now().await;
|
|
} else if err.error() == 10 {
|
|
// Child exited
|
|
break Ok(());
|
|
}else {
|
|
break Err(err);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
tokio::pin!(future);
|
|
future.poll(_cx)
|
|
}
|
|
}
|
|
|
|
// -- boilerplate
|
|
|
|
impl Child {
|
|
/// Get the pipe for talking to parent
|
|
pub fn pipe(&mut self) -> &mut Pipe
|
|
{
|
|
&mut self.comm
|
|
}
|
|
}
|
|
|
|
impl Parent {
|
|
/// Get the pipe for talking to child
|
|
pub fn pipe(&mut self) -> &mut Pipe
|
|
{
|
|
&mut self.comm
|
|
}
|
|
}
|