pipe triggers
This commit is contained in:
parent
2972f9603b
commit
17ec5b819c
1
.clang-format
Normal file
1
.clang-format
Normal file
@ -0,0 +1 @@
|
|||||||
|
BreakBeforeBraces: Attach
|
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -66,6 +66,7 @@ name = "clone-shim"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"close_fds",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"exitcode",
|
"exitcode",
|
||||||
"ipnetwork",
|
"ipnetwork",
|
||||||
@ -77,6 +78,16 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "close_fds"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3bc416f33de9d59e79e57560f450d21ff8393adcf1cdfc3e6d8fb93d5f88a2ed"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -18,3 +18,5 @@ ipnetwork = "0.18"
|
|||||||
|
|
||||||
libc = "0.2.117"
|
libc = "0.2.117"
|
||||||
nix = "0.23.1"
|
nix = "0.23.1"
|
||||||
|
|
||||||
|
close_fds = "0.3.2"
|
||||||
|
@ -10,3 +10,12 @@ To run this example:
|
|||||||
|
|
||||||
cargo build --example basic
|
cargo build --example basic
|
||||||
cargo run -- -s examples/basic/spec.json target/debug/examples/basic
|
cargo run -- -s examples/basic/spec.json target/debug/examples/basic
|
||||||
|
|
||||||
|
### examples/pipes
|
||||||
|
|
||||||
|
The pipes example shows some of the power of the shim by using pipes. The process "pipe_sender" sends two messages down a pipe that it's given by the shim. These two messages each spawn a completely isolated process, "pipe_receiver", that receives that message.
|
||||||
|
|
||||||
|
To run this example:
|
||||||
|
|
||||||
|
cargo build --example pipes
|
||||||
|
cargo run -- -s examples/pipes/spec.json target/debug/examples/pipes
|
||||||
|
42
examples/pipes/main.rs
Normal file
42
examples/pipes/main.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
use std::os::unix::io::FromRawFd;
|
||||||
|
|
||||||
|
let mut args = std::env::args();
|
||||||
|
|
||||||
|
let _bin = args.next();
|
||||||
|
|
||||||
|
match args.next() {
|
||||||
|
Some(s) => match s.as_str() {
|
||||||
|
"pipe_sender" => {
|
||||||
|
let fd: i32 = args.next().unwrap().parse().unwrap();
|
||||||
|
pipe_sender(unsafe { File::from_raw_fd(fd) })
|
||||||
|
}
|
||||||
|
"pipe_receiver" => {
|
||||||
|
let pipe_data = args.next().unwrap();
|
||||||
|
pipe_receiver(pipe_data.as_str())
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
},
|
||||||
|
None => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pipe_sender(mut tx_pipe: File) {
|
||||||
|
println!("hello from pipe_sender!");
|
||||||
|
|
||||||
|
let data = b"some data";
|
||||||
|
let bytes_written = tx_pipe.write(&data[..]).unwrap();
|
||||||
|
assert!(bytes_written == data.len());
|
||||||
|
|
||||||
|
let data = b"some more data";
|
||||||
|
let bytes_written = tx_pipe.write(&data[..]).unwrap();
|
||||||
|
assert!(bytes_written == data.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pipe_receiver(rx_data: &str) {
|
||||||
|
println!("hello from pid: {}", std::process::id());
|
||||||
|
println!("received data: {}", rx_data);
|
||||||
|
}
|
25
examples/pipes/spec.json
Normal file
25
examples/pipes/spec.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"entrypoints": {
|
||||||
|
"pipe_sender": {
|
||||||
|
"args": [
|
||||||
|
"BinaryName",
|
||||||
|
"Entrypoint",
|
||||||
|
{
|
||||||
|
"Pipe": {
|
||||||
|
"Tx": "messages"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pipe_receiver": {
|
||||||
|
"trigger": {
|
||||||
|
"Pipe": "messages"
|
||||||
|
},
|
||||||
|
"args": [
|
||||||
|
"BinaryName",
|
||||||
|
"Entrypoint",
|
||||||
|
"PipeTrigger"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,9 @@ pub enum Error {
|
|||||||
#[error("bad specification type: only .json files are supported")]
|
#[error("bad specification type: only .json files are supported")]
|
||||||
BadSpecType,
|
BadSpecType,
|
||||||
|
|
||||||
|
#[error("bad pipe trigger: this entrypoint is not triggered by a pipe")]
|
||||||
|
BadPipeTrigger,
|
||||||
|
|
||||||
#[error("too many pipes: a pipe must have one reader and one writer: {0}")]
|
#[error("too many pipes: a pipe must have one reader and one writer: {0}")]
|
||||||
TooManyPipes(String),
|
TooManyPipes(String),
|
||||||
|
|
||||||
|
101
src/main.rs
101
src/main.rs
@ -2,19 +2,20 @@ use log::{debug, error, info};
|
|||||||
|
|
||||||
mod clone;
|
mod clone;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod spawner;
|
||||||
mod specification;
|
mod specification;
|
||||||
|
|
||||||
use clone::{clone3, CloneArgs, CloneFlags};
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use specification::{Arg, Pipe, Specification, Trigger};
|
use spawner::Spawner;
|
||||||
|
use specification::Specification;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CString;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
use std::os::unix::io::FromRawFd;
|
||||||
|
|
||||||
use clap::{App, AppSettings};
|
use clap::{App, AppSettings};
|
||||||
use nix::unistd::{self, Pid};
|
use nix::fcntl::OFlag;
|
||||||
|
use nix::unistd::{self};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
std::process::exit(match run() {
|
std::process::exit(match run() {
|
||||||
@ -78,65 +79,51 @@ fn run() -> Result<(), Error> {
|
|||||||
|
|
||||||
// create all the pipes
|
// create all the pipes
|
||||||
let (pipes, _) = spec.pipes();
|
let (pipes, _) = spec.pipes();
|
||||||
let mut read_pipes = HashMap::new();
|
let pipes = create_pipes(pipes)?;
|
||||||
let mut write_pipes = HashMap::new();
|
|
||||||
|
|
||||||
for pipe in pipes {
|
// spawn all processes
|
||||||
|
let spawner = Spawner {
|
||||||
|
spec: &spec,
|
||||||
|
pipes: &pipes,
|
||||||
|
binary,
|
||||||
|
trailing: &trailing,
|
||||||
|
};
|
||||||
|
|
||||||
|
spawner.spawn()?;
|
||||||
|
|
||||||
|
// TODO: Fix this dirty hack to prevent files dropping (and closing)
|
||||||
|
// switch to Option<File> and use a mut ref to take, then call
|
||||||
|
// IntoRawFd if used
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PipePair {
|
||||||
|
read: File,
|
||||||
|
write: File,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pipes(names: Vec<&str>) -> Result<HashMap<String, PipePair>, Error> {
|
||||||
|
let mut pipes = HashMap::new();
|
||||||
|
|
||||||
|
for pipe in names {
|
||||||
info!("creating pipe pair `{}`", pipe);
|
info!("creating pipe pair `{}`", pipe);
|
||||||
|
|
||||||
let (read, write) = unistd::pipe().map_err(|e| Error::Nix {
|
let (read, write) = unistd::pipe2(OFlag::O_DIRECT).map_err(|e| Error::Nix {
|
||||||
msg: "pipe",
|
msg: "pipe2",
|
||||||
src: e,
|
src: e,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// safe to create files given the successful return of pipe(2)
|
// safe to create files given the successful return of pipe(2)
|
||||||
read_pipes.insert(pipe.to_string(), unsafe { File::from_raw_fd(read) });
|
pipes.insert(
|
||||||
write_pipes.insert(pipe.to_string(), unsafe { File::from_raw_fd(write) });
|
pipe.to_string(),
|
||||||
|
PipePair {
|
||||||
|
read: unsafe { File::from_raw_fd(read) },
|
||||||
|
write: unsafe { File::from_raw_fd(write) },
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// spawn all processes
|
Ok(pipes)
|
||||||
for (name, entry) in &spec.entrypoints {
|
|
||||||
info!("spawning entrypoint `{}`", name.as_str());
|
|
||||||
|
|
||||||
match &entry.trigger {
|
|
||||||
Trigger::Startup => {
|
|
||||||
if clone3(CloneArgs::new(CloneFlags::empty())).map_err(|e| Error::Nix {
|
|
||||||
msg: "clone3",
|
|
||||||
src: e,
|
|
||||||
})? == Pid::from_raw(0)
|
|
||||||
{
|
|
||||||
let mut args = Vec::new();
|
|
||||||
for arg in &entry.args {
|
|
||||||
match arg {
|
|
||||||
Arg::BinaryName => args.push(CString::new(binary).unwrap()),
|
|
||||||
Arg::Entrypoint => args.push(CString::new(name.as_str()).unwrap()),
|
|
||||||
Arg::Pipe(p) => args.push(match p {
|
|
||||||
Pipe::Rx(s) => {
|
|
||||||
CString::new(read_pipes[s].as_raw_fd().to_string()).unwrap()
|
|
||||||
}
|
|
||||||
Pipe::Tx(s) => {
|
|
||||||
CString::new(write_pipes[s].as_raw_fd().to_string()).unwrap()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Arg::Trailing => {
|
|
||||||
args.extend(trailing.iter().map(|s| CString::new(*s).unwrap()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unistd::execv(&CString::new(binary).unwrap(), &args).map_err(|e| {
|
|
||||||
Error::Nix {
|
|
||||||
msg: "execv",
|
|
||||||
src: e,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Trigger::Pipe(_s) => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
170
src/spawner.rs
Normal file
170
src/spawner.rs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
use log::{debug, error, info};
|
||||||
|
|
||||||
|
use super::specification::{Arg, Entrypoint, Permission, Pipe, Specification, Trigger};
|
||||||
|
use super::PipePair;
|
||||||
|
use crate::clone::{clone3, CloneArgs, CloneFlags};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||||
|
|
||||||
|
use close_fds::CloseFdsBuilder;
|
||||||
|
use nix::unistd::{self, Pid};
|
||||||
|
|
||||||
|
const BUFFER_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
pub struct Spawner<'a> {
|
||||||
|
pub spec: &'a Specification,
|
||||||
|
pub pipes: &'a HashMap<String, PipePair>,
|
||||||
|
pub binary: &'a str,
|
||||||
|
pub trailing: &'a Vec<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Spawner<'a> {
|
||||||
|
pub fn spawn(&self) -> Result<(), Error> {
|
||||||
|
for (name, entrypoint) in &self.spec.entrypoints {
|
||||||
|
info!("spawning entrypoint `{}`", name.as_str());
|
||||||
|
|
||||||
|
match &entrypoint.trigger {
|
||||||
|
Trigger::Startup => {
|
||||||
|
if clone3(CloneArgs::new(Self::clone_flags(
|
||||||
|
&mut entrypoint.permissions.iter(),
|
||||||
|
)))
|
||||||
|
.map_err(|e| Error::Nix {
|
||||||
|
msg: "clone3",
|
||||||
|
src: e,
|
||||||
|
})? == Pid::from_raw(0)
|
||||||
|
{
|
||||||
|
let args = self.prepare_args(name, &entrypoint.args, None);
|
||||||
|
|
||||||
|
unistd::execv(&CString::new(self.binary).unwrap(), &args).map_err(|e| {
|
||||||
|
Error::Nix {
|
||||||
|
msg: "execv",
|
||||||
|
src: e,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Trigger::Pipe(s) => {
|
||||||
|
// TODO: Consider typing the pipes so CLONE_FILES is only when necessary
|
||||||
|
if clone3(CloneArgs::new(CloneFlags::CLONE_FILES)).map_err(|e| Error::Nix {
|
||||||
|
msg: "clone3",
|
||||||
|
src: e,
|
||||||
|
})? == Pid::from_raw(0)
|
||||||
|
{
|
||||||
|
// Rust's ownership is out of the window now we've cloned
|
||||||
|
// Unsafely move to a new owned pipe
|
||||||
|
let pipe = unsafe { File::from_raw_fd(self.pipes[s].read.as_raw_fd()) };
|
||||||
|
|
||||||
|
let mut closer = CloseFdsBuilder::new();
|
||||||
|
let keep = [pipe.as_raw_fd()];
|
||||||
|
closer.keep_fds(&keep);
|
||||||
|
unsafe {
|
||||||
|
closer.closefrom(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.pipe_trigger(pipe, entrypoint, name) {
|
||||||
|
Ok(()) => std::process::exit(exitcode::OK),
|
||||||
|
Err(e) => {
|
||||||
|
error!("error in pipe_trigger: {}", e);
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pipe_trigger(&self, mut pipe: File, spec: &Entrypoint, name: &str) -> Result<(), Error> {
|
||||||
|
let mut buf = [0_u8; BUFFER_SIZE];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let read_bytes = pipe.read(&mut buf)?;
|
||||||
|
|
||||||
|
if read_bytes == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("triggering from pipe read");
|
||||||
|
|
||||||
|
if clone3(CloneArgs::new(Self::clone_flags(
|
||||||
|
&mut spec.permissions.iter(),
|
||||||
|
)))
|
||||||
|
.map_err(|e| Error::Nix {
|
||||||
|
msg: "clone3",
|
||||||
|
src: e,
|
||||||
|
})? == Pid::from_raw(0)
|
||||||
|
{
|
||||||
|
let pipe_trigger = std::str::from_utf8(&buf[0..read_bytes]).unwrap();
|
||||||
|
let args = self.prepare_args(name, &spec.args, Some(pipe_trigger));
|
||||||
|
|
||||||
|
unistd::execv(&CString::new(self.binary).unwrap(), &args).map_err(|e| {
|
||||||
|
Error::Nix {
|
||||||
|
msg: "execv",
|
||||||
|
src: e,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_args(
|
||||||
|
&self,
|
||||||
|
entrypoint: &str,
|
||||||
|
args: &[Arg],
|
||||||
|
pipe_trigger: Option<&str>,
|
||||||
|
) -> Vec<CString> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for arg in args {
|
||||||
|
match arg {
|
||||||
|
Arg::BinaryName => out.push(CString::new(self.binary).unwrap()),
|
||||||
|
Arg::Entrypoint => out.push(CString::new(entrypoint).unwrap()),
|
||||||
|
Arg::Pipe(p) => out.push(match p {
|
||||||
|
Pipe::Rx(s) => {
|
||||||
|
CString::new(self.pipes[s].read.as_raw_fd().to_string()).unwrap()
|
||||||
|
}
|
||||||
|
Pipe::Tx(s) => {
|
||||||
|
CString::new(self.pipes[s].write.as_raw_fd().to_string()).unwrap()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Arg::PipeTrigger => {
|
||||||
|
out.push(CString::new(pipe_trigger.as_ref().unwrap().to_string()).unwrap())
|
||||||
|
}
|
||||||
|
Arg::TcpListener { port: _port } => unimplemented!(),
|
||||||
|
|
||||||
|
Arg::Trailing => {
|
||||||
|
out.extend(self.trailing.iter().map(|s| CString::new(*s).unwrap()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_flags(perms: &mut dyn Iterator<Item = &Permission>) -> CloneFlags {
|
||||||
|
let mut flags = CloneFlags::empty();
|
||||||
|
|
||||||
|
flags |= CloneFlags::CLONE_NEWCGROUP; // new cgroup namespace
|
||||||
|
flags |= CloneFlags::CLONE_NEWIPC; // new IPC namespace
|
||||||
|
flags |= CloneFlags::CLONE_NEWNET; // new empty network namespace
|
||||||
|
flags |= CloneFlags::CLONE_NEWNS; // new separate mount namespace
|
||||||
|
flags |= CloneFlags::CLONE_NEWPID; // new PID namespace
|
||||||
|
flags |= CloneFlags::CLONE_NEWUSER; // new user namespace
|
||||||
|
flags |= CloneFlags::CLONE_NEWUTS; // new UTS namespace
|
||||||
|
|
||||||
|
for perm in perms {
|
||||||
|
match perm {
|
||||||
|
Permission::PropagateFiles => flags |= CloneFlags::CLONE_FILES,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
use log::debug;
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
@ -20,11 +22,10 @@ pub struct Entrypoint {
|
|||||||
pub args: Vec<Arg>,
|
pub args: Vec<Arg>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub permissions: HashSet<Permissions>,
|
pub permissions: HashSet<Permission>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum Trigger {
|
pub enum Trigger {
|
||||||
Startup,
|
Startup,
|
||||||
Pipe(String),
|
Pipe(String),
|
||||||
@ -36,7 +37,7 @@ impl Default for Trigger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
// #[serde(tag = "type")]
|
// #[serde(tag = "type")]
|
||||||
pub enum Arg {
|
pub enum Arg {
|
||||||
/// The binary name, or argv[0], of the original program start
|
/// The binary name, or argv[0], of the original program start
|
||||||
@ -48,6 +49,13 @@ pub enum Arg {
|
|||||||
/// A chosen end of a named pipe
|
/// A chosen end of a named pipe
|
||||||
Pipe(Pipe),
|
Pipe(Pipe),
|
||||||
|
|
||||||
|
/// The value of a pipe trigger
|
||||||
|
/// NOTE: Only valid if the trigger is of type Pipe(...)
|
||||||
|
PipeTrigger,
|
||||||
|
|
||||||
|
/// A TCP Listener
|
||||||
|
TcpListener { port: u16 },
|
||||||
|
|
||||||
/// The rest of argv[1..], 0 or more arguments
|
/// The rest of argv[1..], 0 or more arguments
|
||||||
Trailing,
|
Trailing,
|
||||||
}
|
}
|
||||||
@ -58,16 +66,14 @@ impl Arg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum Pipe {
|
pub enum Pipe {
|
||||||
Rx(String),
|
Rx(String),
|
||||||
Tx(String),
|
Tx(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
||||||
#[serde(tag = "type")]
|
pub enum Permission {
|
||||||
pub enum Permissions {
|
|
||||||
Filesystem {
|
Filesystem {
|
||||||
host_path: PathBuf,
|
host_path: PathBuf,
|
||||||
final_path: PathBuf,
|
final_path: PathBuf,
|
||||||
@ -75,10 +81,10 @@ pub enum Permissions {
|
|||||||
Network {
|
Network {
|
||||||
network: Network,
|
network: Network,
|
||||||
},
|
},
|
||||||
|
PropagateFiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum Network {
|
pub enum Network {
|
||||||
InternetV4,
|
InternetV4,
|
||||||
InternetV6,
|
InternetV6,
|
||||||
@ -98,18 +104,17 @@ impl Specification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for arg in &entry.args {
|
for arg in &entry.args {
|
||||||
match arg {
|
if let Arg::Pipe(p) = arg {
|
||||||
Arg::BinaryName => {}
|
match p {
|
||||||
Arg::Entrypoint => {}
|
|
||||||
Arg::Pipe(p) => match p {
|
|
||||||
Pipe::Rx(s) => read.push(s.as_str()),
|
Pipe::Rx(s) => read.push(s.as_str()),
|
||||||
Pipe::Tx(s) => write.push(s.as_str()),
|
Pipe::Tx(s) => write.push(s.as_str()),
|
||||||
},
|
}
|
||||||
Arg::Trailing => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("read pipes: {:?}", &read);
|
||||||
|
debug!("write pipes: {:?}", &write);
|
||||||
(read, write)
|
(read, write)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,14 +124,14 @@ impl Specification {
|
|||||||
let mut read_set = HashSet::with_capacity(read.len());
|
let mut read_set = HashSet::with_capacity(read.len());
|
||||||
|
|
||||||
for pipe in read {
|
for pipe in read {
|
||||||
if read_set.insert(pipe) {
|
if !read_set.insert(pipe) {
|
||||||
return Err(Error::TooManyPipes(pipe.to_string()));
|
return Err(Error::TooManyPipes(pipe.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut write_set = HashSet::with_capacity(write.len());
|
let mut write_set = HashSet::with_capacity(write.len());
|
||||||
for pipe in write {
|
for pipe in write {
|
||||||
if write_set.insert(pipe) {
|
if !write_set.insert(pipe) {
|
||||||
return Err(Error::TooManyPipes(pipe.to_string()));
|
return Err(Error::TooManyPipes(pipe.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +146,16 @@ impl Specification {
|
|||||||
return Err(Error::WriteOnlyPipe(pipe.to_string()));
|
return Err(Error::WriteOnlyPipe(pipe.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate pipe trigger arguments make sense
|
||||||
|
for entrypoint in self.entrypoints.values() {
|
||||||
|
if entrypoint.args.contains(&Arg::PipeTrigger) {
|
||||||
|
match entrypoint.trigger {
|
||||||
|
Trigger::Pipe(_) => {}
|
||||||
|
_ => return Err(Error::BadPipeTrigger),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user