fork() unsafe?

master
Avril 4 years ago
parent dd041b6571
commit 08a45331d1
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -18,7 +18,8 @@ threaded = ["tokio/rt-threaded"]
debug_logger = [] debug_logger = []
# Hot-reload config files # Hot-reload config files
watcher = ["threaded"] # When `threaded` is off, this should spawn in a child process with `fork`.
watcher = []
# FS watcher will have infinite backlog, instead of ignoring if it goes over its backlog. # FS watcher will have infinite backlog, instead of ignoring if it goes over its backlog.
# This can help DoS, but potentially cause OOM. # This can help DoS, but potentially cause OOM.

@ -2,3 +2,5 @@ Log filtering rules in config file. For `Trace`s, `Level`s, sublevels, etc.
Log normalise source path in terminal print Log normalise source path in terminal print
Log have #[cfg(feature="threaded")] for deferring write to background worker Log have #[cfg(feature="threaded")] for deferring write to background worker
Warning: I dunno how safe calling fork() in an async context is... Which is currently what we're doing. Make it return an actual future instead, and do no awaiting on the child. Set pipe to block for child, but not for parent, etc.

@ -267,8 +267,9 @@ pub fn watch(path: impl AsRef<Path>) -> Oneesan
Ok(event) => if let Err(err) = { Ok(event) => if let Err(err) = {
#[cfg(feature="watcher_unlimited")] {tx.send(event)} #[cfg(feature="watcher_unlimited")] {tx.send(event)}
#[cfg(not(feature="watcher_unlimited"))] {tx.try_send(event)} #[cfg(not(feature="watcher_unlimited"))] {tx.try_send(event)}
} } {
{warn!("Watcher failed to pass event: {}", err)}, warn!("Watcher failed to pass event: {}", err);
},
Err(e) => error!("Watcher returned error: {}", e), Err(e) => error!("Watcher returned error: {}", e),
} }
}).expect("Failed to initialise watcher"); }).expect("Failed to initialise watcher");

@ -0,0 +1 @@
avril@flan-laptop.58785:1596643711

@ -126,16 +126,16 @@ impl Hook for LogFileHook
.write(true) .write(true)
.open(path).await { .open(path).await {
Ok(file) => file, Ok(file) => file,
Err(err) => { Err(_err) => {
crate::warn!("Could not open logfile {:?} for writing: {}", path, err); //crate::warn!("Could not open logfile {:?} for writing: {}", path, err);
continue; continue;
}, },
}; };
internal.fix_perms(&mut file); internal.fix_perms(&mut file);
if let Err(err) = write_log(&mut file, command).await { if let Err(_err) = write_log(&mut file, command).await {
crate::warn!("Failed writing to logfile {:?}: {}", path, err); //crate::warn!("Failed writing to logfile {:?}: {}", path, err);
} }
} }
}); });

@ -32,6 +32,8 @@ mod hot;
mod context; mod context;
mod job; mod job;
mod stat;
//This is a test function, when we have a real job server, remove it. //This is a test function, when we have a real job server, remove it.
async fn do_thing_every() -> Result<(mpsc::Sender<()>, task::JoinHandle<()>), Box<dyn std::error::Error>> async fn do_thing_every() -> Result<(mpsc::Sender<()>, task::JoinHandle<()>), Box<dyn std::error::Error>>
{ {
@ -66,58 +68,7 @@ async fn do_thing_every() -> Result<(mpsc::Sender<()>, task::JoinHandle<()>), Bo
Ok((tx, handle)) Ok((tx, handle))
} }
fn print_stats()
{
use recolored::Colorize;
lazy_static! {
static ref AUTHORS: String = env!("CARGO_PKG_AUTHORS").replace( ":", ", ");
};
#[cfg(debug_assertions)]
lazy_static!{
static ref BUILD_IDENT: recolored::ColoredString = "debug".bright_blue();
}
#[cfg(not(debug_assertions))]
lazy_static!{
static ref BUILD_IDENT: recolored::ColoredString = "release".bright_red();
}
#[allow(unused_imports)]
use std::ops::Deref;
status!("This is the lolicron daemon version {} by {} ({} build)", env!("CARGO_PKG_VERSION"), &AUTHORS[..], BUILD_IDENT.deref());
status!("---");
status!("Compiled with ({} ({}), {} ({})):", "on".bright_red(), "default".red(), "off".bright_blue(), "default".blue());
#[cfg(nightly)] status!(" +nightly".bright_red());
#[cfg(debug_assertions)] status!(" +debug_assertions".red());
status!("features:");
#[cfg(feature="threaded")] status!(" +threaded".red());
#[cfg(not(feature="threaded"))] status!(" -threaded".bright_blue());
#[cfg(feature="watcher")] status!(" +watcher".red());
#[cfg(not(feature="watcher"))] status!(" -watcher".bright_blue());
#[cfg(feature="debug_logger")] status!(" +debug_logger".red());
#[cfg(not(feature="debug_logger"))] status!(" -debug_logger".bright_blue());
#[cfg(feature="watcher_unlimited")] status!(" +watcher_unlimited".bright_red());
#[cfg(not(feature="watcher_unlimited"))] status!(" -watcher_unlimited".blue());
#[cfg(feature="watcher_timeout")] status!(" +watcher_timeout".red());
#[cfg(not(feature="watcher_timeout"))] status!(" -watcher_timeout".bright_blue());
status!("");
config::build::stat();
status!("GPl'd with <3");
status!("Please enjoy");
status!("---");
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -125,23 +76,28 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
log::init(log::Level::Debug); log::init(log::Level::Debug);
let child = sys::fork::detach_closure(None, None, |parent| { let child = sys::fork::detach_closure(None, None, |parent| {
println!("Parent: {:?}, us: {}", parent, sys::get_pid()); println!("Parent: {:?}, us: {}", parent, sys::get_pid());
std::thread::sleep_ms(3000); //let pipe = parent.pipe();
//use tokio::prelude::*;
//pipe.write_all(b"Hello there").await.expect("io error c");
//tokio::time::delay_for(tokio::time::Duration::from_secs(3)).await;
//std::thread::sleep_ms(3000);
println!("Exiting child"); println!("Exiting child");
}).await?; }).await?;
println!("Child: {:?}", child); println!("Child: {:?}", child);
//println!("Waitpid: {:?}", child.wait().await.map_err(|x| x.to_string()));
println!("Waitpid nb {:?}", child.await.map_err(|x| x.to_string())); println!("Waitpid nb {:?}", child.await.map_err(|x| x.to_string()));
// end test // end test
log::Logger::global().add_hook(log::custom::LogFileHook::new(log::Level::Debug, "test.log", "test.err.log", false)); 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 debug!("Logger initialised"); //TODO: Parse config first
print_stats();
//debug!("{:?}",sys::user::get_users());
stat::print_stats();
//debug!("{:?}",sys::user::get_users());
#[cfg(feature="watcher")] #[cfg(feature="watcher")]
{ {
let oneesan = hot::watch("."); let oneesan = hot::watch(".");

@ -0,0 +1,56 @@
//! Print splash shit
use super::*;
pub fn print_stats()
{
use recolored::Colorize;
lazy_static! {
static ref AUTHORS: String = env!("CARGO_PKG_AUTHORS").replace( ":", ", ");
};
#[cfg(debug_assertions)]
lazy_static!{
static ref BUILD_IDENT: recolored::ColoredString = "debug".bright_blue();
}
#[cfg(not(debug_assertions))]
lazy_static!{
static ref BUILD_IDENT: recolored::ColoredString = "release".bright_red();
}
#[allow(unused_imports)]
use std::ops::Deref;
status!("This is the lolicron daemon version {} by {} ({} build)", env!("CARGO_PKG_VERSION"), &AUTHORS[..], BUILD_IDENT.deref());
status!("---");
status!("Compiled with ({} ({}), {} ({})):", "on".bright_red(), "default".red(), "off".bright_blue(), "default".blue());
#[cfg(nightly)] status!(" +nightly".bright_red());
#[cfg(debug_assertions)] status!(" +debug_assertions".red());
status!("features:");
#[cfg(feature="threaded")] status!(" +threaded".red());
#[cfg(not(feature="threaded"))] status!(" -threaded".bright_blue());
#[cfg(feature="watcher")] status!(" +watcher".red());
#[cfg(not(feature="watcher"))] status!(" -watcher".bright_blue());
#[cfg(feature="debug_logger")] status!(" +debug_logger".red());
#[cfg(not(feature="debug_logger"))] status!(" -debug_logger".bright_blue());
#[cfg(feature="watcher_unlimited")] status!(" +watcher_unlimited".bright_red());
#[cfg(not(feature="watcher_unlimited"))] status!(" -watcher_unlimited".blue());
#[cfg(feature="watcher_timeout")] status!(" +watcher_timeout".red());
#[cfg(not(feature="watcher_timeout"))] status!(" -watcher_timeout".bright_blue());
status!("");
config::build::stat();
status!("GPl'd with <3");
status!("Please enjoy");
status!("---");
}

@ -11,15 +11,9 @@ use libc::{
use std::{ use std::{
fmt, fmt,
}; };
use crate::util::PhantomDrop;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use super::pipe::{ use super::pipe;
self,
unix_pipe,
pipe_read_value,
pipe_write_value,
};
/// Forking error /// Forking error
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
@ -127,88 +121,59 @@ pub struct Parent {
} }
/// Run a closure as a fork and then exit, optionally trying to set `uid` and `gid`. /// 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>> { 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 (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 (comm_p, comm_c) = pipe::multi().map_err(|x| Error::from(x))?;
let child = unsafe{fork()}; let child = unsafe{fork()};
if child == 0 { if child == 0 {
// Is child // Is child
let complete = move || { use tokio::prelude::*;
unsafe { let complete = || {
if let Ok(_) = pipe_write_value(tx, &!0u32) async {
{ unsafe {
//if let Ok(mut rt) = new_runtime() { if let Ok(_) = ttx.write_u32(!0u32).await
// rt.block_on(async move { {
into(Parent{pid: libc::getppid(),comm:comm_c}); into(Parent{pid: libc::getppid(),comm:comm_c});
// }); }
// }
} }
} }
}; };
let complete_err = move |err: Error| { unsafe {
unsafe{let _ = pipe_write_value(tx, &u32::from(err));} loop {
}; if let Some(as_uid) = as_uid {
// Set UID
{ if setuid(as_uid) != 0 {
let _guard = PhantomDrop::new((), move |_| { let _ = ttx.write_u32(Error::SetUid.into()).await;
unsafe { break;
libc::close(tx);
}
});// Close pipe sender when we're done here
unsafe {
loop {
if let Some(as_uid) = as_uid {
// Set UID
if setuid(as_uid) != 0 {
complete_err(Error::SetUid);
break;
}
} }
if let Some(as_gid) = as_gid { }
// Set GID if let Some(as_gid) = as_gid {
if setgid(as_gid) != 0 { // Set GID
complete_err(Error::SetGid); if setgid(as_gid) != 0 {
break; let _ = ttx.write_u32(Error::SetGid.into()).await;
} break;
} }
complete();
break;
} }
complete().await;
break;
} }
} }
std::process::exit(0); std::process::exit(0);
} else if child > 0 { } else if child > 0 {
// Fork succeeded // Fork succeeded
let _guard = PhantomDrop::new((), move |_| { use tokio::prelude::*;
unsafe {
libc::close(rx);
}
});
let err: u32 = unsafe{ let err: u32 = unsafe{
cfg_if! { timeout!(trx.read_u32(), tokio::time::Duration::from_secs(1)).unwrap_or(Ok(Error::Unknown as u32)).unwrap_or(Error::Unknown as u32)
if #[cfg(feature="threaded")] {
use tokio::{
task,
};
let waiter = task::spawn_blocking(move || {
pipe_read_value(rx).map_inner(|e| Error::from(e))
});
waiter.await.expect("Panic while waiting for child status")?
} else {
unsafe {
pipe_read_value(rx)?
}
}
}
}; };
if err == !0u32 { if err == !0u32 {
Ok(Child{pid:child, comm: comm_p}) Ok(Child{pid:child, comm: comm_p})
@ -217,10 +182,6 @@ pub async fn detach_closure<F: FnOnce(Parent)>(as_uid: Option<u32>, as_gid: Opti
} }
} else { } else {
let rval = Error::Fork.into(); let rval = Error::Fork.into();
unsafe {
libc::close(tx);
libc::close(rx);
}
Err(rval) Err(rval)
} }
} }
@ -250,8 +211,9 @@ impl Child
}); });
waiter.await.expect("Waiter panicked") waiter.await.expect("Waiter panicked")
} else { } else {
let pid = self.pid;
let mut status: i32 = 0; let mut status: i32 = 0;
if unsafe{libc::waitpid(self.pid, &mut status as *mut i32, 1)} == pid { // We can't afford to block here if unsafe{libc::waitpid(pid, &mut status as *mut i32, 1)} == pid { // We can't afford to block here
Ok(status) Ok(status)
} else { } else {
Err(Error::WaitPid.into()) Err(Error::WaitPid.into())

@ -10,6 +10,7 @@ pub mod pipe;
pub mod fork; pub mod fork;
pub mod errno; pub mod errno;
#[allow(unused_imports)]
use errno::ResultExt; use errno::ResultExt;
/// Get pid of current process /// Get pid of current process

@ -237,89 +237,99 @@ impl AsyncWrite for WriteHalf
// as far as i can tell this is a no-op with `write()`? // as far as i can tell this is a no-op with `write()`?
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), AsyncError>> #[inline] fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Result<(), AsyncError>>
{ {
let fd = self.0; unsafe {
if let Poll::Ready(res) = self.poll_flush(cx) { libc::close(self.0);
unsafe{libc::close(fd)};
Poll::Ready(res)
} else {
Poll::Pending
} }
Poll::Ready(Ok(()))
} }
fn poll_write(self: Pin<&mut Self>, _cx: &mut Context, buf: &[u8]) -> Poll<Result<usize, AsyncError>> fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<Result<usize, AsyncError>>
{ {
use libc::{ use libc::{
poll, poll,
write, write,
pollfd, pollfd,
c_void,
}; };
let mut fd = pollfd { use tokio::task::yield_now;
fd: self.0,
revents: 0, let future = async {
events: POLL_OUT, loop {
}; let mut fd = pollfd {
let poll = unsafe { fd: self.0,
poll(&mut fd as *mut pollfd, 1, 0) revents: 0,
}; events: POLL_OUT,
Poll::Ready(if poll < 0 {
Err(AsyncError::from_raw_os_error(errno::raw()))
} else if poll > 0 {
if fd.revents & POLL_OUT == POLL_OUT {
let wr = unsafe {
write(self.0, &buf[0] as *const u8 as *const libc::c_void, buf.len())
}; };
if wr < 0 { let poll = unsafe {
Err(AsyncError::from_raw_os_error(errno::raw())) poll(&mut fd as *mut pollfd, 1, 0)
} else { };
Ok(wr as usize) if poll < 0 {
break Err(AsyncError::from_raw_os_error(errno::raw()));
} else if poll > 0 {
if fd.revents & POLL_OUT == POLL_OUT {
// Write ready
let wr = unsafe{write(self.0, &buf[0] as *const u8 as *const c_void, buf.len())};
if wr < 0 {
break Err(AsyncError::from_raw_os_error(errno::raw()));
}
else {
break Ok(wr as usize);
}
}
} }
} else { // Either no poll, or no POLLOUT event
Err(AsyncError::from_raw_os_error(errno::raw())) yield_now().await;
} }
} else { };
return Poll::Pending; tokio::pin!(future);
}) future.poll(cx)
} }
} }
impl AsyncRead for ReadHalf impl AsyncRead for ReadHalf
{ {
fn poll_read(self: Pin<&mut Self>, _cx: &mut Context, buf: &mut [u8]) -> Poll<Result<usize, AsyncError>> fn poll_read(self: Pin<&mut Self>, cx: &mut Context, buf: &mut [u8]) -> Poll<Result<usize, AsyncError>>
{ {
use libc::{ use libc::{
poll, poll,
read, read,
pollfd, pollfd,
c_void,
}; };
let mut fd = pollfd { use tokio::task::yield_now;
fd: self.0,
revents: 0, let future = async {
events: POLL_IN, loop {
}; let mut fd = pollfd {
let poll = unsafe { fd: self.0,
poll(&mut fd as *mut pollfd, 1, 0) revents: 0,
}; events: POLL_IN,
};
Poll::Ready(if poll < 0 { let poll = unsafe {
Err(AsyncError::from_raw_os_error(errno::raw())) poll(&mut fd as *mut pollfd, 1, 0)
} else if poll > 0 {
if fd.revents & POLL_IN == POLL_IN {
let wr = unsafe {
read(self.0, &mut buf[0] as *mut u8 as *mut libc::c_void, buf.len())
}; };
if wr < 0 { if poll < 0 {
Err(AsyncError::from_raw_os_error(errno::raw())) break Err(AsyncError::from_raw_os_error(errno::raw()));
} else { } else if poll > 0 {
Ok(wr as usize) if fd.revents & POLL_IN == POLL_IN {
// Read ready
let wr = unsafe{read(self.0, &mut buf[0] as *mut u8 as *mut c_void, buf.len())};
if wr < 0 {
break Err(AsyncError::from_raw_os_error(errno::raw()));
}
else {
break Ok(wr as usize);
}
}
} }
} else { // Either no poll, or no POLLIN event
Err(AsyncError::from_raw_os_error(errno::raw())) yield_now().await;
} }
} else { };
return Poll::Pending; tokio::pin!(future);
}) future.poll(cx)
} }
} }

@ -3,3 +3,49 @@
const _: &[bool; ((($ex) == true) as usize)] = &[true]; const _: &[bool; ((($ex) == true) as usize)] = &[true];
} }
} }
#[macro_export] macro_rules! timeout {
($fut:expr, $dur:expr) => {
{
let dur = $dur;
tokio::select! {
output = $fut => {
Ok(output)
}
_ = tokio::time::delay_for(dur) => {
Err($crate::util::TimeoutError::from(dur))
}
}
}
}
}
/// Returned from timeout macro
#[derive(Debug)]
pub struct TimeoutError(tokio::time::Duration);
impl std::error::Error for TimeoutError{}
impl std::fmt::Display for TimeoutError
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
write!(f, "timeout of {} ms reached", self.0.as_millis())
}
}
impl From<tokio::time::Duration> for TimeoutError
{
fn from(from: tokio::time::Duration) -> Self
{
TimeoutError(from)
}
}
impl TimeoutError
{
/// Get the timeout that this error lapsed on
#[inline] pub fn timeout(&self) -> &tokio::time::Duration
{
&self.0
}
}

Loading…
Cancel
Save