specification and initial launching
This commit is contained in:
parent
565c1567c4
commit
969bf3a9ee
115
Cargo.lock
generated
115
Cargo.lock
generated
@ -46,14 +46,34 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"os_str_bytes",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clone-shim"
|
name = "clone-shim"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"exitcode",
|
||||||
|
"ipnetwork",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"nix",
|
"nix",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -70,6 +90,18 @@ dependencies = [
|
|||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exitcode"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
@ -85,6 +117,31 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipnetwork"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.117"
|
version = "0.2.117"
|
||||||
@ -128,6 +185,15 @@ dependencies = [
|
|||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.36"
|
version = "1.0.36"
|
||||||
@ -163,6 +229,49 @@ version = "0.6.25"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
@ -183,6 +292,12 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.30"
|
version = "1.0.30"
|
||||||
|
@ -9,6 +9,12 @@ edition = "2021"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
clap = "3"
|
||||||
|
exitcode = "1"
|
||||||
|
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
ipnetwork = "0.18"
|
||||||
|
|
||||||
libc = "0.2.117"
|
libc = "0.2.117"
|
||||||
nix = "0.23.1"
|
nix = "0.23.1"
|
||||||
|
18
build.rs
Normal file
18
build.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let output = Command::new("git")
|
||||||
|
.args(&["rev-parse", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let mut git_hash = String::from_utf8(output.stdout).unwrap();
|
||||||
|
git_hash.truncate(16);
|
||||||
|
let clean_status = Command::new("git")
|
||||||
|
.args(&["diff", "--exit-code"])
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
|
if !clean_status.success() {
|
||||||
|
git_hash.push_str("-dirty");
|
||||||
|
}
|
||||||
|
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
|
||||||
|
}
|
20
src/error.rs
20
src/error.rs
@ -1,7 +1,27 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("{msg}: {src}")]
|
#[error("{msg}: {src}")]
|
||||||
Nix { msg: &'static str, src: nix::Error },
|
Nix { msg: &'static str, src: nix::Error },
|
||||||
|
|
||||||
|
#[error("io: {0}")]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
|
||||||
|
#[error("json: {0}")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("bad specification type: only .json files are supported")]
|
||||||
|
BadSpecType,
|
||||||
|
|
||||||
|
#[error("too many pipes: a pipe must have one reader and one writer: {0}")]
|
||||||
|
TooManyPipes(String),
|
||||||
|
|
||||||
|
#[error("read only pipe: a pipe must have one reader and one writer: {0}")]
|
||||||
|
ReadOnlyPipe(String),
|
||||||
|
|
||||||
|
#[error("write only pipe: a pipe must have one reader and one writer: {0}")]
|
||||||
|
WriteOnlyPipe(String),
|
||||||
}
|
}
|
||||||
|
140
src/main.rs
140
src/main.rs
@ -1,28 +1,142 @@
|
|||||||
use log::info;
|
use log::{debug, error, info};
|
||||||
|
|
||||||
mod clone;
|
mod clone;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod specification;
|
||||||
|
|
||||||
use clone::{clone3, CloneArgs, CloneFlags};
|
use clone::{clone3, CloneArgs, CloneFlags};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
use specification::{Arg, Entrypoint, Pipe, Specification, Trigger};
|
||||||
|
|
||||||
use nix::unistd::Pid;
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
use clap::{App, AppSettings};
|
||||||
let env = env_logger::Env::new().filter_or("LOG", "info");
|
use nix::unistd::{self, Pid};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
std::process::exit(match run() {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("launched successfully");
|
||||||
|
exitcode::OK
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("error: {}", e);
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> Result<(), Error> {
|
||||||
|
// process arguments
|
||||||
|
let matches = App::new("clone-shim")
|
||||||
|
.version(env!("GIT_HASH"))
|
||||||
|
.author("Jake Hillion <jake@hillion.co.uk>")
|
||||||
|
.about("Launch a multi entrypoint app, cloning as requested by an external specification or the ELF.")
|
||||||
|
.arg(clap::Arg::new("spec").long("specification").short('s').help("Provide the specification as an external JSON file.").takes_value(true))
|
||||||
|
.setting(AppSettings::TrailingVarArg)
|
||||||
|
.arg(clap::Arg::new("verbose").long("verbose").short('v').help("Use verbose logging.").takes_value(false))
|
||||||
|
.arg(clap::Arg::new("binary").index(1).help("Binary and arguments to launch with the shim").required(true).multiple_values(true))
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let (binary, trailing) = {
|
||||||
|
let mut argv = matches.values_of("binary").unwrap();
|
||||||
|
|
||||||
|
let binary = argv.next().unwrap();
|
||||||
|
let trailing: Vec<&str> = argv.collect();
|
||||||
|
|
||||||
|
(binary, trailing)
|
||||||
|
};
|
||||||
|
|
||||||
|
// setup logging
|
||||||
|
let env = env_logger::Env::new().filter_or(
|
||||||
|
"LOG",
|
||||||
|
if matches.is_present("verbose") {
|
||||||
|
"debug"
|
||||||
|
} else {
|
||||||
|
"warn"
|
||||||
|
},
|
||||||
|
);
|
||||||
env_logger::init_from_env(env);
|
env_logger::init_from_env(env);
|
||||||
|
|
||||||
info!("getting started");
|
// parse the specification
|
||||||
|
let spec: Specification = if let Some(m) = matches.value_of("spec") {
|
||||||
if clone3(CloneArgs::new(CloneFlags::empty())).map_err(|e| Error::Nix {
|
if m.ends_with(".json") {
|
||||||
msg: "clone3",
|
let f = std::fs::File::open(m)?;
|
||||||
src: e,
|
Ok(serde_json::from_reader(f)?)
|
||||||
})? != Pid::from_raw(0)
|
} else {
|
||||||
{
|
Err(Error::BadSpecType)
|
||||||
info!("hello from the child");
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("hello from the parent");
|
unimplemented!("reading spec from the elf is unimplemented")
|
||||||
|
}?;
|
||||||
|
|
||||||
|
debug!("specification read: {:?}", &spec);
|
||||||
|
spec.validate()?;
|
||||||
|
|
||||||
|
// create all the pipes
|
||||||
|
let (pipes, _) = spec.pipes();
|
||||||
|
let mut read_pipes = HashMap::new();
|
||||||
|
let mut write_pipes = HashMap::new();
|
||||||
|
|
||||||
|
for pipe in pipes {
|
||||||
|
info!("creating pipe pair `{}`", pipe);
|
||||||
|
|
||||||
|
let (read, write) = unistd::pipe().map_err(|e| Error::Nix {
|
||||||
|
msg: "pipe",
|
||||||
|
src: e,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// safe to create files given the successful return of pipe(2)
|
||||||
|
read_pipes.insert(pipe.to_string(), unsafe { File::from_raw_fd(read) });
|
||||||
|
write_pipes.insert(pipe.to_string(), unsafe { File::from_raw_fd(write) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawn all processes
|
||||||
|
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) => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pipe_trigger(pipe: File, entry: &Entrypoint) {}
|
||||||
|
129
src/specification.rs
Normal file
129
src/specification.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use ipnetwork::{Ipv4Network, Ipv6Network};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Specification {
|
||||||
|
pub entrypoints: HashMap<String, Entrypoint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Entrypoint {
|
||||||
|
pub trigger: Trigger,
|
||||||
|
pub args: Vec<Arg>,
|
||||||
|
pub permissions: HashSet<Permissions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Trigger {
|
||||||
|
Startup,
|
||||||
|
Pipe(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Arg {
|
||||||
|
/// The binary name, or argv[0], of the original program start
|
||||||
|
BinaryName,
|
||||||
|
|
||||||
|
/// The name of this entrypoint
|
||||||
|
Entrypoint,
|
||||||
|
|
||||||
|
/// A chosen end of a named pipe
|
||||||
|
Pipe(Pipe),
|
||||||
|
|
||||||
|
/// The rest of argv[1..], 0 or more arguments
|
||||||
|
Trailing,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Pipe {
|
||||||
|
Rx(String),
|
||||||
|
Tx(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Permissions {
|
||||||
|
Filesystem {
|
||||||
|
host_path: PathBuf,
|
||||||
|
final_path: PathBuf,
|
||||||
|
},
|
||||||
|
Network {
|
||||||
|
network: Network,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Network {
|
||||||
|
InternetV4,
|
||||||
|
InternetV6,
|
||||||
|
PrivateV4(Ipv4Network),
|
||||||
|
PrivateV6(Ipv6Network),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Specification {
|
||||||
|
pub fn pipes(&self) -> (Vec<&str>, Vec<&str>) {
|
||||||
|
let mut read = Vec::new();
|
||||||
|
let mut write = Vec::new();
|
||||||
|
|
||||||
|
for (_, entry) in &self.entrypoints {
|
||||||
|
match &entry.trigger {
|
||||||
|
Trigger::Startup => {}
|
||||||
|
Trigger::Pipe(s) => read.push(s.as_str()),
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in &entry.args {
|
||||||
|
match arg {
|
||||||
|
Arg::BinaryName => {}
|
||||||
|
Arg::Entrypoint => {}
|
||||||
|
Arg::Pipe(p) => match p {
|
||||||
|
Pipe::Rx(s) => read.push(s.as_str()),
|
||||||
|
Pipe::Tx(s) => write.push(s.as_str()),
|
||||||
|
},
|
||||||
|
Arg::Trailing => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(read, write)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self) -> Result<(), Error> {
|
||||||
|
// validate pipes match
|
||||||
|
let (read, write) = self.pipes();
|
||||||
|
let mut read_set = HashSet::with_capacity(read.len());
|
||||||
|
|
||||||
|
for pipe in read {
|
||||||
|
if read_set.insert(pipe) {
|
||||||
|
return Err(Error::TooManyPipes(pipe.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut write_set = HashSet::with_capacity(write.len());
|
||||||
|
for pipe in write {
|
||||||
|
if write_set.insert(pipe) {
|
||||||
|
return Err(Error::TooManyPipes(pipe.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for pipe in read_set {
|
||||||
|
if !write_set.remove(pipe) {
|
||||||
|
return Err(Error::ReadOnlyPipe(pipe.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for pipe in write_set {
|
||||||
|
return Err(Error::WriteOnlyPipe(pipe.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user