diff --git a/src/spawner/args.rs b/src/spawner/args.rs new file mode 100644 index 0000000..655052c --- /dev/null +++ b/src/spawner/args.rs @@ -0,0 +1,176 @@ +use super::{Spawner, TriggerData}; +use crate::specification::{Arg, FileSocket, Pipe}; +use crate::{Error, Result}; + +use std::ffi::CString; +use std::fs::File; +use std::net::TcpListener; +use std::os::unix::io::IntoRawFd; + +/** + * perform initial processing with ambient authority + * for things like network sockets. + */ +pub struct PreparedArgs(Vec); + +impl PreparedArgs { + pub fn prepare_ambient(args: &[Arg]) -> Result { + let mut v = Vec::with_capacity(args.len()); + + for arg in args { + v.push(PreparedArg::prepare_ambient(arg)?); + } + + Ok(PreparedArgs(v)) + } + + pub(super) fn prepare_void_mut( + self, + spawner: &mut Spawner, + entrypoint: &str, + trigger: &mut TriggerData, + ) -> Result> { + let mut v = Vec::new(); + + for arg in self.0 { + v.extend(arg.prepare_void_mut(spawner, entrypoint, trigger)?) + } + + Ok(v) + } + + pub(super) fn prepare_void( + self, + spawner: &Spawner, + entrypoint: &str, + trigger: &mut TriggerData, + ) -> Result> { + let mut v = Vec::new(); + + for arg in self.0 { + v.extend(arg.prepare_void(spawner, entrypoint, trigger)?) + } + + Ok(v) + } +} +enum PreparedArg { + /// The binary name, or argv[0], of the original program start + BinaryName, + + /// The name of this entrypoint + Entrypoint, + + /// A file descriptor for a file on the filesystem in the launching namespace + File(File), + + /// A chosen end of a named pipe + Pipe(Pipe), + + /// File socket + FileSocket(FileSocket), + + /// A value specified by the trigger + /// NOTE: Only valid if the trigger is of type Pipe(...) or FileSocket(...) + Trigger, + + /// A TCP Listener + TcpListener { socket: TcpListener }, + + /// The rest of argv[1..], 0 or more arguments + Trailing, +} + +impl PreparedArg { + /** + * Process the parts of the argument which must be processed + * with ambient authority + * + * Leave the remainder untouched so they can be processed in parallel + * (in the child process) and to reduce authority + */ + fn prepare_ambient(arg: &Arg) -> Result { + Ok(match arg { + Arg::File(path) => PreparedArg::File(File::open(path)?), + + Arg::TcpListener { addr } => PreparedArg::TcpListener { + socket: TcpListener::bind(addr)?, + }, + + Arg::BinaryName => PreparedArg::BinaryName, + Arg::Entrypoint => PreparedArg::Entrypoint, + Arg::Pipe(p) => PreparedArg::Pipe(p.clone()), + Arg::FileSocket(s) => PreparedArg::FileSocket(s.clone()), + Arg::Trigger => PreparedArg::Trigger, + Arg::Trailing => PreparedArg::Trailing, + }) + } + + /** + * Complete argument preparation in the void + */ + fn prepare_void_mut( + self, + spawner: &mut Spawner, + entrypoint: &str, + trigger: &mut TriggerData, + ) -> Result> { + match self { + PreparedArg::Pipe(p) => match p { + Pipe::Rx(s) => { + let pipe = spawner.pipes.get_mut(&s).unwrap().take_read()?; + Ok(vec![CString::new(pipe.into_raw_fd().to_string()).unwrap()]) + } + Pipe::Tx(s) => { + let pipe = spawner.pipes.get_mut(&s).unwrap().take_write()?; + Ok(vec![CString::new(pipe.into_raw_fd().to_string()).unwrap()]) + } + }, + + PreparedArg::FileSocket(s) => match s { + FileSocket::Rx(s) => { + let pipe = spawner.sockets.get_mut(&s).unwrap().take_read()?; + Ok(vec![CString::new(pipe.into_raw_fd().to_string()).unwrap()]) + } + FileSocket::Tx(s) => { + let pipe = spawner.sockets.get_mut(&s).unwrap().take_write()?; + Ok(vec![CString::new(pipe.into_raw_fd().to_string()).unwrap()]) + } + }, + + arg => arg.prepare_void(spawner, entrypoint, trigger), + } + } + + /** + * Complete argument preparation in the void + */ + fn prepare_void( + self, + spawner: &Spawner, + entrypoint: &str, + trigger: &mut TriggerData, + ) -> Result> { + match self { + PreparedArg::BinaryName => Ok(vec![CString::new(spawner.binary).unwrap()]), + PreparedArg::Entrypoint => Ok(vec![CString::new(entrypoint).unwrap()]), + + PreparedArg::Pipe(p) => Err(Error::BadPipe(p.get_name().to_string())), + PreparedArg::FileSocket(s) => Err(Error::BadFileSocket(s.get_name().to_string())), + + PreparedArg::File(f) => Ok(vec![CString::new(f.into_raw_fd().to_string()).unwrap()]), + + PreparedArg::Trigger => Ok(trigger.args()), + + PreparedArg::TcpListener { socket } => { + Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()]) + } + + PreparedArg::Trailing => Ok(spawner + .trailing + .iter() + .map(|s| CString::new(*s).unwrap()) + .collect()), + } + } +} diff --git a/src/spawner.rs b/src/spawner/mod.rs similarity index 69% rename from src/spawner.rs rename to src/spawner/mod.rs index 95aa768..67e948d 100644 --- a/src/spawner.rs +++ b/src/spawner/mod.rs @@ -1,17 +1,18 @@ use log::{debug, error, info}; -use super::specification::{ - Arg, Entrypoint, Environment, FileSocket, Pipe, Specification, Trigger, -}; -use super::{PipePair, SocketPair}; +mod args; + +use args::PreparedArgs; + +use crate::specification::{Entrypoint, Environment, Specification, Trigger}; use crate::void::VoidBuilder; use crate::{Error, Result}; +use crate::{PipePair, SocketPair}; use std::collections::HashMap; use std::ffi::CString; use std::fs::File; use std::io::Read; -use std::net::TcpListener; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; use std::path::PathBuf; @@ -60,16 +61,18 @@ impl<'a> Spawner<'a> { match &entrypoint.trigger { Trigger::Startup => { - let binary = PathBuf::from(self.binary).canonicalize()?; - let mut builder = VoidBuilder::new(); + + let binary = PathBuf::from(self.binary).canonicalize()?; builder.mount(binary, "/entrypoint"); self.prepare_env(&mut builder, &entrypoint.environment); + let args = PreparedArgs::prepare_ambient(&entrypoint.args)?; + let closure = || { - let args = self - .prepare_args(name, &entrypoint.args, &mut TriggerData::None) + let args = args + .prepare_void_mut(self, name, &mut TriggerData::None) .unwrap(); if let Err(e) = unistd::execv(&CString::new("/entrypoint").unwrap(), &args) @@ -159,11 +162,14 @@ impl<'a> Spawner<'a> { } } + let args = PreparedArgs::prepare_ambient(&spec.args)?; + let closure = || { let pipe_trigger = std::str::from_utf8(&buf[0..read_bytes]).unwrap(); - let args = self - .prepare_args_ref(name, &spec.args, &mut TriggerData::Pipe(pipe_trigger)) + + let args = args + .prepare_void(self, name, &mut TriggerData::Pipe(pipe_trigger)) .unwrap(); if let Err(e) = unistd::execv(&CString::new("/entrypoint").unwrap(), &args) @@ -220,13 +226,11 @@ impl<'a> Spawner<'a> { } } + let args = PreparedArgs::prepare_ambient(&spec.args)?; + let closure = || { - let args = self - .prepare_args_ref( - name, - &spec.args, - &mut TriggerData::FileSocket(fds), - ) + let args = args + .prepare_void(self, name, &mut TriggerData::FileSocket(fds)) .unwrap(); if let Err(e) = @@ -268,100 +272,4 @@ impl<'a> Spawner<'a> { } } } - - fn prepare_args( - &mut self, - entrypoint: &str, - args: &[Arg], - trigger: &mut TriggerData, - ) -> Result> { - let mut out = Vec::new(); - for arg in args { - out.extend(self.prepare_arg(entrypoint, arg, trigger)?); - } - - Ok(out) - } - - fn prepare_args_ref( - &self, - entrypoint: &str, - args: &[Arg], - trigger: &mut TriggerData, - ) -> Result> { - let mut out = Vec::new(); - for arg in args { - out.extend(self.prepare_arg_ref(entrypoint, arg, trigger)?); - } - - Ok(out) - } - - fn prepare_arg( - &mut self, - entrypoint: &str, - arg: &Arg, - trigger: &mut TriggerData, - ) -> Result> { - match arg { - Arg::Pipe(p) => match p { - Pipe::Rx(s) => { - let pipe = self.pipes.get_mut(s).unwrap().take_read()?; - Ok(vec![CString::new(pipe.into_raw_fd().to_string()).unwrap()]) - } - Pipe::Tx(s) => { - let pipe = self.pipes.get_mut(s).unwrap().take_write()?; - Ok(vec![CString::new(pipe.into_raw_fd().to_string()).unwrap()]) - } - }, - - Arg::FileSocket(s) => match s { - FileSocket::Rx(s) => { - let pipe = self.sockets.get_mut(s).unwrap().take_read()?; - Ok(vec![CString::new(pipe.into_raw_fd().to_string()).unwrap()]) - } - FileSocket::Tx(s) => { - let pipe = self.sockets.get_mut(s).unwrap().take_write()?; - Ok(vec![CString::new(pipe.into_raw_fd().to_string()).unwrap()]) - } - }, - - a => self.prepare_arg_ref(entrypoint, a, trigger), - } - } - - fn prepare_arg_ref( - &self, - entrypoint: &str, - arg: &Arg, - trigger: &mut TriggerData, - ) -> Result> { - match arg { - Arg::BinaryName => Ok(vec![CString::new(self.binary).unwrap()]), - Arg::Entrypoint => Ok(vec![CString::new(entrypoint).unwrap()]), - - Arg::Pipe(p) => Err(Error::BadPipe(p.get_name().to_string())), - Arg::FileSocket(s) => Err(Error::BadFileSocket(s.get_name().to_string())), - - Arg::File(p) => { - let f = File::open(p)?.into_raw_fd(); - Ok(vec![CString::new(f.to_string()).unwrap()]) - } - - Arg::Trigger => Ok(trigger.args()), - - Arg::TcpListener { addr } => { - let listener = TcpListener::bind(addr)?; - let listener = listener.into_raw_fd(); - - Ok(vec![CString::new(listener.to_string()).unwrap()]) - } - - Arg::Trailing => Ok(self - .trailing - .iter() - .map(|s| CString::new(*s).unwrap()) - .collect()), - } - } } diff --git a/src/specification.rs b/src/specification.rs index 453d37d..f5cfa3c 100644 --- a/src/specification.rs +++ b/src/specification.rs @@ -78,7 +78,7 @@ impl Arg { } } -#[derive(Serialize, Deserialize, PartialEq, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub enum Pipe { Rx(String), Tx(String), @@ -93,7 +93,7 @@ impl Pipe { } } -#[derive(Serialize, Deserialize, PartialEq, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub enum FileSocket { Rx(String), Tx(String),