use std::io;
use std::process::ExitCode;
use super::protocol::{self, FrameRead, MAX_REQUEST_BYTES, Request, Response};
pub fn worker_main(mode: &str, rest: &[String]) -> ExitCode {
if mode == "sandbox-helper" {
super::helper::helper_main(rest);
}
match run_mode(mode) {
Ok(response) => match emit(&response) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("worker_write_failed: {e}");
ExitCode::from(1)
}
},
Err(exit) => exit.into(),
}
}
struct HardExit(u8);
impl From<HardExit> for ExitCode {
fn from(exit: HardExit) -> ExitCode {
ExitCode::from(exit.0)
}
}
fn read_request() -> Result<Request, HardExit> {
let mut stdin = io::stdin().lock();
match protocol::read_frame(&mut stdin, MAX_REQUEST_BYTES) {
Ok(FrameRead::Complete(payload)) => serde_json::from_slice(&payload).map_err(|e| {
eprintln!("worker_bad_request: {e}");
HardExit(2)
}),
Ok(other) => {
eprintln!("worker_bad_request: no complete request frame ({other:?})");
Err(HardExit(2))
}
Err(e) => {
eprintln!("worker_read_failed: {e}");
Err(HardExit(2))
}
}
}
fn emit(response: &Response) -> io::Result<()> {
let mut stdout = io::stdout().lock();
protocol::write_frame(&mut stdout, response)
}
fn run_mode(mode: &str) -> Result<Response, HardExit> {
match mode {
"pdf" => production::pdf(read_request()?),
"probe-net" => production::probe_net(read_request()?),
"probe-seccomp" => production::probe_seccomp(read_request()?),
#[cfg(all(unix, feature = "records-worker"))]
"records" => records::records(read_request()?),
#[cfg(all(unix, feature = "image-ocr"))]
"image-ocr" => image_ocr::image_ocr(read_request()?),
#[cfg(all(unix, feature = "svg-provider"))]
"svg-raster" => svg_raster::svg_raster(read_request()?),
#[cfg(all(unix, feature = "image-metadata"))]
"image-metadata" => image_metadata::image_metadata(read_request()?),
#[cfg(all(unix, feature = "image-ocr", feature = "test-adapters"))]
"image-ocr-fake" => image_ocr::image_ocr_fake(read_request()?),
#[cfg(all(unix, feature = "audio-asr"))]
"asr" => audio::asr(read_request()?),
#[cfg(all(unix, feature = "audio-asr", feature = "test-adapters"))]
"asr-fake" => audio::asr_fake(read_request()?),
#[cfg(feature = "test-adapters")]
"harness-hold" => harness::hold(),
#[cfg(feature = "test-adapters")]
"harness-hold-closed" => harness::hold_closed(),
#[cfg(feature = "test-adapters")]
"harness-setsid-hold" => harness::setsid_hold(),
#[cfg(feature = "test-adapters")]
"harness-hold-balloon" => harness::hold_balloon(),
#[cfg(feature = "test-adapters")]
"harness-child-probe" => harness::child_probe(),
#[cfg(feature = "test-adapters")]
other => harness::run(other, read_request()?),
#[cfg(not(feature = "test-adapters"))]
_ => {
eprintln!("worker_unknown_mode");
Err(HardExit(2))
}
}
}
mod production {
use super::*;
use crate::convert::pdf::convert_pdf;
use crate::runner::protocol::bodies::{PdfOk, PdfRequest, ProbeNetRequest, ProbeReport};
pub(super) fn pdf(request: Request) -> Result<Response, HardExit> {
let body: PdfRequest = parse(&request)?;
let bytes = match std::fs::read(&body.input) {
Ok(bytes) => bytes,
Err(e) => {
return Ok(Response::err(
"io",
format!("cannot read the staged input {}: {e}", body.input),
));
}
};
match convert_pdf(&bytes) {
Ok(conversion) => Ok(Response::ok(
serde_json::to_value(PdfOk {
text: conversion.text,
warnings: conversion.warnings,
segments: conversion.segments,
recovered: conversion.recovered,
})
.expect("PdfOk serializes"),
)),
Err(error) => Ok(Response::err(error.code, error.message)),
}
}
pub(super) fn probe_net(request: Request) -> Result<Response, HardExit> {
let body: ProbeNetRequest = parse(&request)?;
let report = ProbeReport {
attempts: super::net::attempt_all(&body),
};
Ok(Response::ok(
serde_json::to_value(report).expect("ProbeReport serializes"),
))
}
pub(super) fn probe_seccomp(_request: Request) -> Result<Response, HardExit> {
let outcome = std::net::UdpSocket::bind("127.0.0.1:0")
.map(|_| "socket created, the filter did not engage".to_string())
.unwrap_or_else(|e| format!("socket refused by errno: {e}"));
Ok(Response::ok(serde_json::json!({ "outcome": outcome })))
}
fn parse<T: serde::de::DeserializeOwned>(request: &Request) -> Result<T, HardExit> {
serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})
}
}
#[cfg(all(unix, feature = "records-worker"))]
mod records {
use std::path::Path;
use super::*;
use crate::convert::records::{Ceilings, convert_records};
use crate::runner::protocol::bodies::{RecordsOk, RecordsRequest};
pub(super) fn records(request: Request) -> Result<Response, HardExit> {
let body: RecordsRequest =
serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})?;
let ceilings = Ceilings {
max_records: body.max_records,
max_tables: body.max_tables,
max_output_bytes: body.max_output_bytes,
};
match convert_records(Path::new(&body.input), &body.format, &ceilings) {
Ok(conversion) => Ok(Response::ok(
serde_json::to_value(RecordsOk {
text: conversion.text,
warnings: conversion.warnings,
segments: conversion.segments,
})
.expect("RecordsOk serializes"),
)),
Err(error) => Ok(Response::err(error.code, error.message)),
}
}
}
#[cfg(all(unix, feature = "image-ocr"))]
mod image_ocr {
use super::*;
use crate::convert::image_ocr::recognize;
use crate::runner::protocol::bodies::OcrRequest;
pub(super) fn image_ocr(request: Request) -> Result<Response, HardExit> {
let body: OcrRequest = serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})?;
match recognize(&body) {
Ok(ok) => Ok(Response::ok(
serde_json::to_value(ok).expect("OcrOk serializes"),
)),
Err(error) => Ok(Response::err(error.code, error.message)),
}
}
#[cfg(feature = "test-adapters")]
pub(super) fn image_ocr_fake(request: Request) -> Result<Response, HardExit> {
use crate::convert::image_ocr::recognize_fake;
let body: OcrRequest = serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})?;
match recognize_fake(&body) {
Ok(ok) => Ok(Response::ok(
serde_json::to_value(ok).expect("OcrOk serializes"),
)),
Err(error) => Ok(Response::err(error.code, error.message)),
}
}
}
#[cfg(all(unix, feature = "audio-asr"))]
mod audio {
use super::*;
use crate::convert::audio_asr;
use crate::runner::protocol::bodies::AsrRequest;
pub(super) fn asr(request: Request) -> Result<Response, HardExit> {
let body: AsrRequest = serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})?;
match audio_asr::transcribe(&body) {
Ok(ok) => Ok(Response::ok(
serde_json::to_value(ok).expect("AsrOk serializes"),
)),
Err(error) => Ok(Response::err(error.code, error.message)),
}
}
#[cfg(feature = "test-adapters")]
pub(super) fn asr_fake(request: Request) -> Result<Response, HardExit> {
let body: AsrRequest = serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})?;
match audio_asr::transcribe_fake(&body) {
Ok(ok) => Ok(Response::ok(
serde_json::to_value(ok).expect("AsrOk serializes"),
)),
Err(error) => Ok(Response::err(error.code, error.message)),
}
}
}
#[cfg(all(unix, feature = "svg-provider"))]
mod svg_raster {
use super::*;
use crate::convert::svg_raster::rasterize;
use crate::runner::protocol::bodies::SvgRasterRequest;
pub(super) fn svg_raster(request: Request) -> Result<Response, HardExit> {
let body: SvgRasterRequest =
serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})?;
match rasterize(&body) {
Ok(ok) => Ok(Response::ok(
serde_json::to_value(ok).expect("SvgRasterOk serializes"),
)),
Err(error) => Ok(Response::err(error.code, error.message)),
}
}
}
#[cfg(all(unix, feature = "image-metadata"))]
mod image_metadata {
use std::path::Path;
use super::*;
use crate::convert::image_metadata::extract::{Ceilings, extract};
use crate::runner::protocol::bodies::{ImageMetadataOk, ImageMetadataRequest};
pub(super) fn image_metadata(request: Request) -> Result<Response, HardExit> {
let body: ImageMetadataRequest =
serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})?;
let ceilings = Ceilings {
max_decompressed_bytes: body.max_decompressed_bytes,
max_xml_depth: body.max_xml_depth,
max_xml_events: body.max_xml_events,
max_boxes: body.max_boxes,
max_rows: body.max_rows,
max_output_bytes: body.max_output_bytes,
};
match extract(Path::new(&body.input), &body.format, &ceilings) {
Ok(rows) => Ok(Response::ok(
serde_json::to_value(ImageMetadataOk { rows }).expect("ImageMetadataOk serializes"),
)),
Err(error) => Ok(Response::err(error.code, error.message)),
}
}
}
mod net {
use std::io;
use std::net::{SocketAddr, TcpStream, UdpSocket};
use std::time::Duration;
use crate::runner::protocol::bodies::{ProbeAttempt, ProbeName, ProbeNetRequest, ProbeOutcome};
const TIMEOUT: Duration = Duration::from_millis(600);
pub(super) fn attempt_all(request: &ProbeNetRequest) -> Vec<ProbeAttempt> {
vec![
tcp(ProbeName::Ipv4Tcp, &request.tcp4_target),
tcp(ProbeName::Ipv6Tcp, &request.tcp6_target),
unix_socket(&request.unix_target),
udp_send(&request.tcp4_target),
]
}
fn classify(name: ProbeName, result: io::Result<()>) -> ProbeAttempt {
let (outcome, error_kind) = match result {
Ok(()) => (ProbeOutcome::Connected, io::ErrorKind::Other),
Err(e)
if matches!(
e.kind(),
io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
) =>
{
(ProbeOutcome::Timeout, e.kind())
}
Err(e) => (ProbeOutcome::Denied, e.kind()),
};
ProbeAttempt {
name,
outcome,
error_kind,
}
}
fn tcp(name: ProbeName, target: &str) -> ProbeAttempt {
let address = match target.parse::<SocketAddr>() {
Ok(address) => address,
Err(_) => {
return ProbeAttempt {
name,
outcome: ProbeOutcome::InvalidTarget,
error_kind: io::ErrorKind::InvalidInput,
};
}
};
classify(
name,
TcpStream::connect_timeout(&address, TIMEOUT).map(drop),
)
}
#[cfg(target_os = "linux")]
fn unix_socket(target: &str) -> ProbeAttempt {
use std::os::linux::net::SocketAddrExt;
let result = std::os::unix::net::SocketAddr::from_abstract_name(target.as_bytes())
.and_then(|address| std::os::unix::net::UnixStream::connect_addr(&address))
.map(drop);
classify(ProbeName::UnixSocket, result)
}
#[cfg(not(target_os = "linux"))]
fn unix_socket(target: &str) -> ProbeAttempt {
classify(
ProbeName::UnixSocket,
std::os::unix::net::UnixStream::connect(target).map(drop),
)
}
fn udp_send(target: &str) -> ProbeAttempt {
let result = (|| {
let socket = UdpSocket::bind("0.0.0.0:0")?;
socket.connect(target)?;
socket.send(b"probe")?;
Ok(())
})();
classify(ProbeName::UdpSend, result)
}
}
#[cfg(feature = "test-adapters")]
mod harness {
use std::io::Write;
use super::*;
use crate::runner::protocol::bodies::{
OcrInput, OcrOk, OcrRequest, OcrSpan, ScreenState, VideoOk, VideoRequest,
};
pub(super) fn run(mode: &str, request: Request) -> Result<Response, HardExit> {
match mode {
"ocr" => ocr(request),
"video" => video(request),
"harness-echo" => Ok(Response::ok(request.payload)),
"harness-env" => harness_env(),
"harness-escape" => harness_escape(request),
"harness-crash" => harness_crash(),
"harness-sleep" => harness_sleep(),
"harness-balloon" => harness_balloon(),
"harness-oversized" => harness_oversized(),
"harness-trailing" => harness_trailing(),
"harness-truncated" => harness_truncated(),
"harness-stderr-flood" => harness_stderr_flood(),
"harness-survivor" => harness_survivor(request),
"harness-balloon-survivor" => harness_balloon_survivor(request),
"harness-spawn-storm" => harness_spawn_storm(request),
"harness-fd-check" => harness_fd_check(request),
"harness-controls" => harness_controls(request),
"harness-spawn-probe" => harness_spawn_probe(request),
_ => {
eprintln!("worker_unknown_mode");
Err(HardExit(2))
}
}
}
pub(super) fn hold() -> ! {
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}
pub(super) fn hold_closed() -> ! {
for fd in 0..=2 {
let _ = nix::unistd::close(fd);
}
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}
pub(super) fn hold_balloon() -> ! {
for fd in 0..=2 {
let _ = nix::unistd::close(fd);
}
let mut balloon = vec![0u8; 192 * 1024 * 1024];
for index in (0..balloon.len()).step_by(4096) {
balloon[index] = (index % 251) as u8;
}
let _ = std::fs::write("ballooned", b"up");
loop {
std::hint::black_box(&balloon);
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}
fn harness_balloon_survivor(request: Request) -> Result<Response, HardExit> {
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct SurvivorRequest {
program: String,
}
let body: SurvivorRequest = parse(&request)?;
if let Err(e) = std::process::Command::new(&body.program)
.arg("harness-hold-balloon")
.spawn()
{
return Ok(Response::err(
"spawn_failed",
format!("cannot spawn the balloon: {e}"),
));
}
for _ in 0..400 {
if std::fs::metadata("ballooned").is_ok() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(5));
}
Ok(Response::ok(serde_json::json!({ "spawned": true })))
}
pub(super) fn setsid_hold() -> ! {
let _ = nix::unistd::setsid();
std::thread::sleep(std::time::Duration::from_secs(5));
std::process::exit(0);
}
fn ocr(request: Request) -> Result<Response, HardExit> {
let body: OcrRequest = parse(&request)?;
confirm_input(&body.input)?;
let label = match body.kind {
OcrInput::Image => "image".to_string(),
OcrInput::PageRender { page, dpi } => format!("page {page} at {dpi} dpi"),
};
let ok = OcrOk {
spans: vec![
OcrSpan {
text: format!("recognized from {label}"),
confidence: 0.94,
},
OcrSpan {
text: "faint line".to_string(),
confidence: 0.31,
},
],
warnings: Vec::new(),
};
Ok(Response::ok(serde_json::to_value(ok).expect("OcrOk")))
}
fn video(request: Request) -> Result<Response, HardExit> {
let body: VideoRequest = parse(&request)?;
confirm_input(&body.input)?;
let ok = VideoOk {
states: vec![
ScreenState {
first_seen_seconds: 2.0,
text: "Title slide".to_string(),
},
ScreenState {
first_seen_seconds: 8.0,
text: "Quarterly revenue".to_string(),
},
ScreenState {
first_seen_seconds: 20.0,
text: "Quarterly revenue".to_string(),
},
],
duration_seconds: Some(30.0),
};
Ok(Response::ok(serde_json::to_value(ok).expect("VideoOk")))
}
fn harness_env() -> Result<Response, HardExit> {
let vars: Vec<[String; 2]> = std::env::vars().map(|(k, v)| [k, v]).collect();
Ok(Response::ok(serde_json::json!({ "env": vars })))
}
fn harness_escape(request: Request) -> Result<Response, HardExit> {
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct EscapeRequest {
read_path: String,
#[serde(default)]
list_path: Option<String>,
}
let body: EscapeRequest = parse(&request)?;
let read = std::fs::read(&body.read_path)
.map(|bytes| format!("read succeeded with {} bytes", bytes.len()))
.unwrap_or_else(|e| format!("read denied: {e}"));
let list = body.list_path.as_ref().map(|path| {
std::fs::read_dir(path)
.map(|entries| format!("list succeeded with {} entries", entries.count()))
.unwrap_or_else(|e| format!("list denied: {e}"))
});
let target = std::env::temp_dir().join("text-mirror-escape-probe");
let write = std::fs::write(&target, b"escaped")
.map(|_| "write succeeded".to_string())
.unwrap_or_else(|e| format!("write denied: {e}"));
Ok(Response::ok(serde_json::json!({
"read": read,
"write": write,
"list": list,
})))
}
fn harness_survivor(request: Request) -> Result<Response, HardExit> {
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct SurvivorRequest {
program: String,
}
let body: SurvivorRequest = parse(&request)?;
match std::process::Command::new(&body.program)
.arg("harness-hold-closed")
.spawn()
{
Ok(child) => Ok(Response::ok(
serde_json::json!({ "survivor_pid": child.id() }),
)),
Err(e) => Ok(Response::err(
"spawn_failed",
format!("cannot spawn the survivor: {e}"),
)),
}
}
fn harness_spawn_storm(request: Request) -> Result<Response, HardExit> {
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct StormRequest {
program: String,
attempts: u32,
}
let body: StormRequest = parse(&request)?;
let mut spawned = 0u32;
let mut failed = 0u32;
let mut first_error = String::new();
let mut children = Vec::new();
for _ in 0..body.attempts {
match std::process::Command::new(&body.program)
.arg("harness-hold-closed")
.spawn()
{
Ok(child) => {
spawned += 1;
children.push(child);
}
Err(e) => {
failed += 1;
if first_error.is_empty() {
first_error = e.to_string();
}
}
}
}
Ok(Response::ok(serde_json::json!({
"spawned": spawned,
"failed": failed,
"first_error": first_error,
})))
}
fn harness_controls(request: Request) -> Result<Response, HardExit> {
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct ControlsRequest {
temp_root: String,
home: String,
cache_root: String,
outbound: String,
loopback: String,
shell_profile: String,
profile_file: String,
privileged: String,
program: String,
}
let body: ControlsRequest = parse(&request)?;
Ok(Response::ok(serde_json::json!({
"write-temp-root": attempt_write(&std::path::Path::new(&body.temp_root).join("text-mirror-control")),
"write-home": attempt_write(&std::path::Path::new(&body.home).join("text-mirror-control")),
"write-cache-root": attempt_write(&std::path::Path::new(&body.cache_root).join("text-mirror-control")),
"tcp-outbound": attempt_connect(&body.outbound),
"tcp-loopback-connect": attempt_connect(&body.loopback),
"tcp-listen": outcome(std::net::TcpListener::bind("127.0.0.1:0").map(|_| ())),
"read-shell-profile": outcome(std::fs::read(&body.shell_profile).map(|_| ())),
"read-resident-profile": outcome(std::fs::read(&body.profile_file).map(|_| ())),
"read-privileged": outcome(std::fs::read(&body.privileged).map(|_| ())),
"exec-undeclared": outcome(
std::process::Command::new(&body.program)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|_| ())
),
"write-inside-jail": attempt_write(std::path::Path::new("control-inside-jail")),
})))
}
fn attempt_write(path: &std::path::Path) -> String {
outcome(std::fs::write(path, b"control").map(|_| ()))
}
fn attempt_connect(target: &str) -> String {
let Ok(address) = target.parse::<std::net::SocketAddr>() else {
return "invalid target".to_string();
};
outcome(
std::net::TcpStream::connect_timeout(&address, std::time::Duration::from_millis(600))
.map(|_| ()),
)
}
fn outcome(result: std::io::Result<()>) -> String {
match result {
Ok(()) => "allowed".to_string(),
Err(e) => format!("denied: {e}"),
}
}
fn harness_spawn_probe(request: Request) -> Result<Response, HardExit> {
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct SpawnProbeRequest {
program: String,
temp_root: String,
privileged: String,
}
let body: SpawnProbeRequest = parse(&request)?;
let status = std::process::Command::new(&body.program)
.arg("harness-child-probe")
.env("PROBE_TEMP_ROOT", &body.temp_root)
.env("PROBE_PRIVILEGED", &body.privileged)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
let Ok(status) = status else {
return Ok(Response::err(
"spawn_failed",
"the descendant could not be spawned".to_string(),
));
};
let report = std::fs::read_to_string("child-probe.json").unwrap_or_default();
Ok(Response::ok(serde_json::json!({
"descendant_exited_cleanly": status.success(),
"report": serde_json::from_str::<serde_json::Value>(&report).unwrap_or(serde_json::Value::Null),
})))
}
pub(super) fn child_probe() -> ! {
let temp_root = std::env::var("PROBE_TEMP_ROOT").unwrap_or_default();
let privileged = std::env::var("PROBE_PRIVILEGED").unwrap_or_default();
let report = serde_json::json!({
"write-temp-root": attempt_write(&std::path::Path::new(&temp_root).join("text-mirror-descendant")),
"read-privileged": outcome(std::fs::read(&privileged).map(|_| ())),
"write-inside-jail": attempt_write(std::path::Path::new("descendant-inside-jail")),
});
let _ = std::fs::write("child-probe.json", report.to_string());
std::process::exit(0)
}
fn harness_fd_check(request: Request) -> Result<Response, HardExit> {
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct FdCheckRequest {
fd: i32,
}
let body: FdCheckRequest = parse(&request)?;
let open = std::fs::metadata(format!("/dev/fd/{}", body.fd)).is_ok();
Ok(Response::ok(serde_json::json!({ "open": open })))
}
fn harness_crash() -> Result<Response, HardExit> {
let response = Response::ok(serde_json::json!({ "note": "plausible but doomed" }));
let mut stdout = io::stdout().lock();
let _ = protocol::write_frame(&mut stdout, &response);
std::process::exit(3);
}
fn harness_sleep() -> Result<Response, HardExit> {
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}
fn harness_balloon() -> Result<Response, HardExit> {
const BALLOON_BYTES: usize = 192 * 1024 * 1024;
let mut balloon = vec![0u8; BALLOON_BYTES];
for index in (0..balloon.len()).step_by(4096) {
balloon[index] = (index % 251) as u8;
}
std::thread::sleep(std::time::Duration::from_secs(30));
drop(balloon);
Err(HardExit(0))
}
fn harness_oversized() -> Result<Response, HardExit> {
let mut stdout = io::stdout().lock();
let _ = stdout.write_all(&u32::MAX.to_be_bytes());
let _ = stdout.flush();
std::thread::sleep(std::time::Duration::from_secs(30));
Err(HardExit(0))
}
fn harness_trailing() -> Result<Response, HardExit> {
let response = Response::ok(serde_json::json!({ "note": "then noise" }));
let mut stdout = io::stdout().lock();
let _ = protocol::write_frame(&mut stdout, &response);
let _ = stdout.write_all(b"trailing noise past the frame");
let _ = stdout.flush();
std::thread::sleep(std::time::Duration::from_secs(30));
Err(HardExit(0))
}
fn harness_truncated() -> Result<Response, HardExit> {
let mut stdout = io::stdout().lock();
let _ = stdout.write_all(&64u32.to_be_bytes());
let _ = stdout.write_all(b"{\"partial\":");
let _ = stdout.flush();
std::process::exit(0);
}
fn harness_stderr_flood() -> Result<Response, HardExit> {
let chunk = [b'x'; 8192];
let mut stderr = io::stderr().lock();
for _ in 0..4096 {
if stderr.write_all(&chunk).is_err() {
break;
}
}
let _ = stderr.flush();
std::thread::sleep(std::time::Duration::from_secs(30));
Err(HardExit(0))
}
fn confirm_input(name: &str) -> Result<(), HardExit> {
if std::fs::metadata(name).is_ok() {
Ok(())
} else {
eprintln!("worker_missing_input: {name}");
Err(HardExit(4))
}
}
fn parse<T: serde::de::DeserializeOwned>(request: &Request) -> Result<T, HardExit> {
serde_json::from_value(request.payload.clone()).map_err(|e| {
eprintln!("worker_bad_payload: {e}");
HardExit(2)
})
}
}