118 lines
3.8 KiB
Rust
118 lines
3.8 KiB
Rust
#![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<dyn std::error::Error>> {
|
|
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::<u64>()?;
|
|
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::<usize>()?;
|
|
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<dyn std::error::Error>> {
|
|
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(())
|
|
}
|