#![forbid(unsafe_code)] #![allow(missing_docs)] use std::{ env, fs::{self, OpenOptions}, io::{self, Read as _, Write as _}, net::TcpStream, os::unix::fs::OpenOptionsExt as _, path::{Path, PathBuf}, process::ExitCode, thread, time::Duration, }; fn main() -> ExitCode { match execute() { Ok(()) => ExitCode::SUCCESS, Err(error) => { let _ignored = writeln!(io::stderr().lock(), "fake child: {error}"); ExitCode::FAILURE } } } fn execute() -> Result<(), Box> { let mut arguments = env::args_os().skip(1); let behavior = arguments.next().ok_or("missing behavior")?; if behavior == "exit-success" { return Ok(()); } if behavior == "network-probe" { let address = arguments.next().ok_or("missing probe address")?; TcpStream::connect(address.to_str().ok_or("probe address is not UTF-8")?)?; return Ok(()); } let request = PathBuf::from(arguments.next().ok_or("missing request path")?); let response = PathBuf::from(arguments.next().ok_or("missing response path")?); match behavior.to_str().ok_or("behavior is not UTF-8")? { "success" => write_response(&request, &response, false, true), "mismatch" => write_response(&request, &response, true, true), "temporary-only" => write_response(&request, &response, false, false), "no-response" => Ok(()), "sleep" => { let milliseconds = arguments .next() .ok_or("missing sleep milliseconds")? .to_str() .ok_or("sleep value is not UTF-8")? .parse::()?; thread::sleep(Duration::from_millis(milliseconds)); write_response(&request, &response, false, true) } "flood" => { let count = arguments .next() .ok_or("missing flood byte count")? .to_str() .ok_or("flood value is not UTF-8")? .parse::()?; write_repeated(io::stdout().lock(), b'o', count)?; write_repeated(io::stderr().lock(), b'e', count)?; write_response(&request, &response, false, true) } _ => Err("unknown behavior".into()), } } fn write_repeated(mut writer: impl io::Write, byte: u8, mut remaining: usize) -> io::Result<()> { let buffer = vec![byte; 64 * 1024]; while remaining > 0 { let count = remaining.min(buffer.len()); writer.write_all(&buffer[..count])?; remaining -= count; } writer.flush() } fn write_response( request_path: &Path, response_path: &Path, mismatch: bool, publish: bool, ) -> Result<(), Box> { let mut request_bytes = Vec::new(); fs::File::open(request_path)?.read_to_end(&mut request_bytes)?; let request: serde_json::Value = serde_json::from_slice(&request_bytes)?; let invocation_id = if mismatch { serde_json::Value::String("ffffffffffffffffffffffffffffffff".to_owned()) } else { request["invocation_id"].clone() }; let response = serde_json::json!({ "protocol_version": request["protocol_version"], "invocation_id": invocation_id, "operation": request["operation"], "result": { "status": "success", "data": { "ready": true, "components": {} } } }); let bytes = serde_json::to_vec(&response)?; let temporary = response_path.with_file_name("response.json.tmp"); let mut file = OpenOptions::new() .write(true) .create_new(true) .mode(0o600) .open(&temporary)?; file.write_all(&bytes)?; file.sync_all()?; if publish { fs::rename(temporary, response_path)?; } Ok(()) }