- Core modules run on the host-side preview1 over the shared VFS (D41). - wasi:http default handler in the linker; anonymous HTTPS git clone works. Sockets stay denied until P2. - Each process carries the conversation (from Exec, inherited by children, shown in /proc/processes) and a host-assigned SAUSAGE_PID. - Spawn requests choose pipe, inherit or null per stream. - /config/services.yaml supervisor: live edits, restarts, crash limit 5/10 s, graceful stop (stdin EOF, then kill after 5 s) (D44). - Compile caches keyed by content; PATH=/usr/local/bin:/bin. world.rs also carries T13's optional read-only /bin mount (D43).
128 lines
4.8 KiB
Rust
128 lines
4.8 KiB
Rust
//! Launches world components and pumps each child's bounded streams independently.
|
|
#![allow(clippy::all, missing_docs)]
|
|
use crate::{
|
|
ExecutionResult,
|
|
openfiles::OpenFile,
|
|
processes::{ChildProcess, LaunchRequest},
|
|
};
|
|
use std::io::{Read, Write};
|
|
mod bindings {
|
|
wit_bindgen::generate!({ path: "sausage-process", world: "program", generate_all });
|
|
}
|
|
use bindings::{
|
|
sausage::process::host,
|
|
wasi::io::streams::{InputStream, StreamError},
|
|
};
|
|
pub(crate) type ProcessId = i32;
|
|
fn drain(stream: &mut Option<InputStream>) -> std::io::Result<Vec<u8>> {
|
|
let Some(reader) = stream.as_ref() else {
|
|
return Ok(Vec::new());
|
|
};
|
|
match reader.read(8192) {
|
|
Ok(bytes) => Ok(bytes),
|
|
Err(StreamError::Closed) => {
|
|
*stream = None;
|
|
Ok(Vec::new())
|
|
}
|
|
Err(_) => Err(std::io::Error::other("child stream failed")),
|
|
}
|
|
}
|
|
pub(crate) fn spawn(mut request: LaunchRequest) -> std::io::Result<ChildProcess> {
|
|
let child = host::spawn(&host::Request {
|
|
program: request.program.to_string_lossy().into_owned(),
|
|
argv: std::iter::once(request.argv0).chain(request.args).collect(),
|
|
env: request.env.into_iter().collect(),
|
|
cwd: request.working_dir.to_string_lossy().into_owned(),
|
|
group: match request.process_group {
|
|
crate::processes::ProcessGroup::Join(group) => Some(group as u32),
|
|
crate::processes::ProcessGroup::Inherit => None,
|
|
_ => Some(0),
|
|
},
|
|
stdin: host::Stdio::Pipe,
|
|
stdout: host::Stdio::Pipe,
|
|
stderr: host::Stdio::Pipe,
|
|
})
|
|
.map_err(std::io::Error::other)?;
|
|
let pid = child.pid() as i32;
|
|
let group = child.group() as i32;
|
|
let mut send = Some(child.stdin());
|
|
let mut receive = Some(child.stdout());
|
|
let mut errors = Some(child.stderr());
|
|
let mut input = request.open_files.remove_fd(0);
|
|
if matches!(input, Some(OpenFile::Stdin(_))) {
|
|
input = None;
|
|
}
|
|
let mut output = request.open_files.remove_fd(1);
|
|
let mut error = request.open_files.remove_fd(2);
|
|
let pump = tokio::spawn(async move {
|
|
let mut pending = Vec::new();
|
|
loop {
|
|
if pending.is_empty() {
|
|
if let Some(reader) = input.as_mut() {
|
|
let mut buf = [0; 8192];
|
|
match reader.read(&mut buf) {
|
|
Ok(0) => input = None,
|
|
Ok(n) => pending.extend_from_slice(&buf[..n]),
|
|
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
|
|
Err(e) => return Err(e),
|
|
}
|
|
}
|
|
}
|
|
if let Some(writer) = send.as_ref() {
|
|
match writer.check_write() {
|
|
Ok(n) if n > 0 => {
|
|
let n = (n as usize).min(pending.len());
|
|
if n != 0 {
|
|
writer
|
|
.write(&pending[..n])
|
|
.map_err(|_| std::io::Error::other("child stdin failed"))?;
|
|
writer
|
|
.flush()
|
|
.map_err(|_| std::io::Error::other("child stdin flush failed"))?;
|
|
pending.drain(..n);
|
|
} else if input.is_none() {
|
|
// A completed flush must precede EOF, or the writer drops queued bytes.
|
|
send = None;
|
|
}
|
|
}
|
|
Ok(_) => {}
|
|
Err(_) => {
|
|
send = None;
|
|
input = None;
|
|
pending.clear();
|
|
}
|
|
}
|
|
}
|
|
if output.is_none() {
|
|
receive = None;
|
|
}
|
|
let out = drain(&mut receive)?;
|
|
let err = drain(&mut errors)?;
|
|
if let Some(writer) = output.as_mut() {
|
|
if let Err(e) = writer.write_all(&out) {
|
|
if e.kind() != std::io::ErrorKind::BrokenPipe {
|
|
return Err(e);
|
|
}
|
|
output = None;
|
|
}
|
|
}
|
|
if let Some(writer) = error.as_mut() {
|
|
writer.write_all(&err)?;
|
|
}
|
|
if receive.is_none() && errors.is_none() {
|
|
if let Some(code) = child.status() {
|
|
return Ok(ExecutionResult::new(code as u8));
|
|
}
|
|
}
|
|
tokio::task::yield_now().await;
|
|
}
|
|
});
|
|
Ok(
|
|
ChildProcess::from_future(async move { pump.await.map_err(std::io::Error::other)? })
|
|
.native_child(Some(pid), Some(group)),
|
|
)
|
|
}
|
|
pub(crate) fn exec(_request: LaunchRequest) -> std::io::Error {
|
|
std::io::ErrorKind::Unsupported.into()
|
|
}
|