189 lines
5.6 KiB
Rust
189 lines
5.6 KiB
Rust
use std::ffi::{OsStr, OsString};
|
|
use std::fs::File;
|
|
use std::io::{self, Write};
|
|
use std::path::PathBuf;
|
|
|
|
use rustix::fs::{FileType, Mode, OFlags, ResolveFlags, fstat, openat2};
|
|
|
|
use crate::IgnoredWorkspace;
|
|
use crate::filesystem::RouterConfiguration;
|
|
|
|
pub const PRIVATE_ROUTER_ROLE: &str = "--lazaret-private-ignored-workspace-router-v1";
|
|
|
|
#[must_use]
|
|
pub fn dispatch_private_router(arguments: &[OsString]) -> Option<io::Result<()>> {
|
|
if arguments
|
|
.first()
|
|
.is_none_or(|argument| argument != PRIVATE_ROUTER_ROLE)
|
|
{
|
|
return None;
|
|
}
|
|
Some(parse(arguments).and_then(run))
|
|
}
|
|
|
|
struct Arguments {
|
|
workspace_fd: i32,
|
|
workspace_label: PathBuf,
|
|
isolated_fd: i32,
|
|
mountpoint: PathBuf,
|
|
global_excludes: Option<PathBuf>,
|
|
isolate_ignored: bool,
|
|
threads: usize,
|
|
}
|
|
|
|
fn parse(arguments: &[OsString]) -> io::Result<Arguments> {
|
|
let mut arguments = arguments.iter().skip(1);
|
|
let workspace_fd = number(named(&mut arguments, "--workspace-fd")?)?;
|
|
let workspace_label = PathBuf::from(named(&mut arguments, "--workspace-label")?);
|
|
let isolated_fd = number(named(&mut arguments, "--isolated-fd")?)?;
|
|
let mountpoint = PathBuf::from(named(&mut arguments, "--mountpoint")?);
|
|
let global_excludes = match named(&mut arguments, "--global-excludes")? {
|
|
value if value.is_empty() => None,
|
|
value => Some(PathBuf::from(value)),
|
|
};
|
|
let isolate_ignored = match named(&mut arguments, "--isolate-ignored")?.to_str() {
|
|
Some("true") => true,
|
|
Some("false") => false,
|
|
_ => return Err(io::Error::other("router isolation mode is invalid")),
|
|
};
|
|
let threads = number(named(&mut arguments, "--threads")?)?;
|
|
if arguments.next().is_some() || threads == 0 {
|
|
return Err(io::Error::other("router arguments are invalid"));
|
|
}
|
|
Ok(Arguments {
|
|
workspace_fd,
|
|
workspace_label,
|
|
isolated_fd,
|
|
mountpoint,
|
|
global_excludes,
|
|
isolate_ignored,
|
|
threads,
|
|
})
|
|
}
|
|
|
|
fn run(arguments: Arguments) -> io::Result<()> {
|
|
let workspace = inherited_exact_directory(arguments.workspace_fd, &arguments.workspace_label)?;
|
|
let isolated = inherited(arguments.isolated_fd)?;
|
|
IgnoredWorkspace::serve_from_authorities(
|
|
RouterConfiguration {
|
|
workspace_label: arguments.workspace_label,
|
|
workspace,
|
|
isolated_state: isolated,
|
|
mountpoint: arguments.mountpoint,
|
|
global_excludes: arguments.global_excludes,
|
|
isolate_ignored: arguments.isolate_ignored,
|
|
threads: arguments.threads,
|
|
},
|
|
|| {
|
|
let mut output = io::stdout().lock();
|
|
output.write_all(b"ready\n")?;
|
|
output.flush()
|
|
},
|
|
)
|
|
}
|
|
|
|
fn inherited_exact_directory(descriptor: i32, label: &std::path::Path) -> io::Result<File> {
|
|
let inherited = inherited(descriptor)?;
|
|
let expected = fstat(&inherited)?;
|
|
let reopened = File::from(openat2(
|
|
rustix::fs::CWD,
|
|
label,
|
|
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOFOLLOW,
|
|
Mode::empty(),
|
|
ResolveFlags::NO_MAGICLINKS | ResolveFlags::NO_SYMLINKS,
|
|
)?);
|
|
let observed = fstat(&reopened)?;
|
|
if expected.st_dev != observed.st_dev
|
|
|| expected.st_ino != observed.st_ino
|
|
|| !FileType::from_raw_mode(observed.st_mode).is_dir()
|
|
{
|
|
return Err(io::Error::other(
|
|
"namespace-local workspace differs from inherited authority",
|
|
));
|
|
}
|
|
Ok(reopened)
|
|
}
|
|
|
|
fn inherited(descriptor: i32) -> io::Result<File> {
|
|
if descriptor < 0 {
|
|
return Err(io::Error::other("router descriptor is invalid"));
|
|
}
|
|
File::open(format!("/proc/self/fd/{descriptor}"))
|
|
}
|
|
|
|
fn named<'a>(
|
|
arguments: &mut impl Iterator<Item = &'a OsString>,
|
|
name: &str,
|
|
) -> io::Result<&'a OsString> {
|
|
if arguments.next().is_none_or(|argument| argument != name) {
|
|
return Err(io::Error::other(format!(
|
|
"router argument {name} is missing"
|
|
)));
|
|
}
|
|
arguments
|
|
.next()
|
|
.ok_or_else(|| io::Error::other(format!("router argument {name} has no value")))
|
|
}
|
|
|
|
fn number<T>(value: &OsStr) -> io::Result<T>
|
|
where
|
|
T: std::str::FromStr,
|
|
{
|
|
value
|
|
.to_str()
|
|
.and_then(|value| value.parse().ok())
|
|
.ok_or_else(|| io::Error::other("router numeric argument is invalid"))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn exact_private_arguments_parse() {
|
|
let arguments = valid_arguments();
|
|
let parsed = parse(&arguments).unwrap();
|
|
assert_eq!(parsed.workspace_fd, 3);
|
|
assert_eq!(parsed.isolated_fd, 4);
|
|
assert_eq!(parsed.threads, 2);
|
|
assert!(parsed.isolate_ignored);
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_private_arguments_are_refused() {
|
|
let mut extra = valid_arguments();
|
|
extra.push("unexpected".into());
|
|
assert!(parse(&extra).is_err());
|
|
|
|
let mut zero_threads = valid_arguments();
|
|
*zero_threads.last_mut().unwrap() = "0".into();
|
|
assert!(parse(&zero_threads).is_err());
|
|
|
|
let mut missing = valid_arguments();
|
|
missing.remove(2);
|
|
assert!(parse(&missing).is_err());
|
|
}
|
|
|
|
fn valid_arguments() -> Vec<OsString> {
|
|
[
|
|
PRIVATE_ROUTER_ROLE,
|
|
"--workspace-fd",
|
|
"3",
|
|
"--workspace-label",
|
|
"/workspace",
|
|
"--isolated-fd",
|
|
"4",
|
|
"--mountpoint",
|
|
"/runtime/workspace",
|
|
"--global-excludes",
|
|
"",
|
|
"--isolate-ignored",
|
|
"true",
|
|
"--threads",
|
|
"2",
|
|
]
|
|
.into_iter()
|
|
.map(OsString::from)
|
|
.collect()
|
|
}
|
|
}
|