113 lines
4.5 KiB
Rust
113 lines
4.5 KiB
Rust
//! Ordinary argv/stdin/stdout fixture: deliberately has no Dever protocol.
|
|
use std::io::{Read, Write};
|
|
use std::path::Path;
|
|
use std::time::Duration;
|
|
|
|
fn main() {
|
|
match run() {
|
|
Ok(code) => std::process::exit(code),
|
|
Err(error) => {
|
|
eprintln!("{error}");
|
|
if matches!(
|
|
std::env::args().nth(1).as_deref(),
|
|
Some("tree" | "wait" | "heartbeat")
|
|
) && let Some(directory) = std::env::args().nth(2)
|
|
{
|
|
let _ = std::fs::write(
|
|
Path::new(&directory).join("fixture-error"),
|
|
error.to_string(),
|
|
);
|
|
}
|
|
std::process::exit(91);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run() -> Result<i32, Box<dyn std::error::Error>> {
|
|
let mut arguments = std::env::args().skip(1);
|
|
let mode = arguments.next();
|
|
match mode.as_deref() {
|
|
Some("echo") => {
|
|
for argument in arguments {
|
|
std::io::stdout().write_all(argument.as_bytes())?;
|
|
std::io::stdout().write_all(&[0])?;
|
|
}
|
|
}
|
|
Some("stdin") => {
|
|
std::io::copy(&mut std::io::stdin(), &mut std::io::stdout())?;
|
|
std::io::stderr().write_all(&[0, 255, 128, 10])?;
|
|
}
|
|
Some("exit") => return Ok(arguments.next().ok_or("missing exit code")?.parse()?),
|
|
Some("fds") => {
|
|
let descriptors = std::fs::read_dir("/proc/self/fd")?
|
|
.map(|entry| entry.map(|entry| entry.path()))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
for descriptor in descriptors {
|
|
let Some(number) = descriptor
|
|
.file_name()
|
|
.and_then(|name| name.to_str())
|
|
.and_then(|name| name.parse::<u32>().ok())
|
|
else {
|
|
continue;
|
|
};
|
|
if number > 2 && std::fs::read_link(&descriptor).is_ok() {
|
|
writeln!(std::io::stdout(), "{number}")?;
|
|
}
|
|
}
|
|
}
|
|
Some("duplex") => {
|
|
let count: usize = arguments.next().ok_or("missing output length")?.parse()?;
|
|
let stdout = std::thread::spawn(move || std::io::stdout().write_all(&vec![254; count]));
|
|
let stderr = std::thread::spawn(move || std::io::stderr().write_all(&vec![253; count]));
|
|
let mut input = Vec::new();
|
|
std::io::stdin().read_to_end(&mut input)?;
|
|
stdout.join().map_err(|_| "stdout thread panicked")??;
|
|
stderr.join().map_err(|_| "stderr thread panicked")??;
|
|
}
|
|
Some("wait") | Some("tree") | Some("tree-exit") => {
|
|
let directory = arguments.next().ok_or("missing marker directory")?;
|
|
let directory = Path::new(&directory);
|
|
let mut starts = std::fs::OpenOptions::new()
|
|
.create(true)
|
|
.append(true)
|
|
.open(directory.join("starts"))?;
|
|
starts.write_all(b"started\n")?;
|
|
let mut child = std::process::Command::new(std::env::current_exe()?)
|
|
.arg("heartbeat")
|
|
.arg(directory)
|
|
.spawn()?;
|
|
std::fs::write(directory.join("ready"), b"ready")?;
|
|
if mode.as_deref() == Some("tree-exit") {
|
|
let deadline = std::time::Instant::now() + Duration::from_secs(2);
|
|
while !directory.join("heartbeat").exists() {
|
|
if std::time::Instant::now() >= deadline {
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
return Err("descendant did not start".into());
|
|
}
|
|
std::thread::sleep(Duration::from_millis(5));
|
|
}
|
|
return Ok(0);
|
|
}
|
|
let status = child.wait()?;
|
|
return Ok(status.code().unwrap_or(92));
|
|
}
|
|
Some("heartbeat") => {
|
|
let directory = arguments.next().ok_or("missing marker directory")?;
|
|
let lifetime = std::fs::OpenOptions::new()
|
|
.create(true)
|
|
.truncate(false)
|
|
.write(true)
|
|
.open(Path::new(&directory).join("lifetime.lock"))?;
|
|
lifetime.lock()?;
|
|
let marker = Path::new(&directory).join("heartbeat");
|
|
for count in 0..3000 {
|
|
std::fs::write(&marker, count.to_string())?;
|
|
std::thread::sleep(Duration::from_millis(10));
|
|
}
|
|
}
|
|
_ => return Err("unknown ordinary command mode".into()),
|
|
}
|
|
Ok(0)
|
|
}
|