#![allow(dead_code)]
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
pub struct RunningBinary {
child: Child,
}
impl Drop for RunningBinary {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
pub fn sandbox(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("forjar-etxtbsy-{name}"));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).expect("create sandbox");
dir
}
pub fn hold_running(src: &str, dest: &Path) -> RunningBinary {
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).expect("parent dir");
}
let _ = fs::remove_file(dest);
fs::copy(src, dest).unwrap_or_else(|e| panic!("copy {src} -> {}: {e}", dest.display()));
fs::set_permissions(dest, fs::Permissions::from_mode(0o755)).expect("chmod 755");
let guard = RunningBinary {
child: spawn_retrying_etxtbsy(dest),
};
wait_until_busy(dest, guard.child.id());
guard
}
fn spawn_retrying_etxtbsy(dest: &Path) -> Child {
let mut last = String::new();
for _ in 0..200 {
match Command::new(dest)
.arg("600")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
{
Ok(c) => return c,
Err(e) if e.raw_os_error() == Some(26) => {
last = e.to_string();
std::thread::sleep(std::time::Duration::from_millis(10));
}
Err(e) => panic!("spawn {}: {e}", dest.display()),
}
}
panic!("spawn {} kept returning ETXTBSY: {last}", dest.display());
}
fn wait_until_busy(dest: &Path, pid: u32) {
let exe = PathBuf::from(format!("/proc/{pid}/exe"));
for _ in 0..200 {
if exe.exists() {
if let Ok(target) = fs::read_link(&exe) {
if target == dest {
return;
}
}
} else if !PathBuf::from("/proc").exists() {
std::thread::sleep(std::time::Duration::from_millis(200));
return;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
panic!(
"fixture never became busy: /proc/{pid}/exe did not resolve to {}",
dest.display()
);
}
pub fn curl_stub(dir: &Path, asset: &Path, url: &str) -> PathBuf {
let stub_dir = dir.join("stub-bin");
fs::create_dir_all(&stub_dir).expect("stub dir");
let stub = stub_dir.join("curl");
fs::write(
&stub,
format!(
"#!/bin/sh\n\
out=''\n\
while [ $# -gt 0 ]; do\n\
\x20 case \"$1\" in\n\
\x20\x20\x20 -o) out=\"$2\"; shift 2 ;;\n\
\x20\x20\x20 -*) shift ;;\n\
\x20\x20\x20 *) shift ;;\n\
\x20 esac\n\
done\n\
if [ -n \"$out\" ]; then\n\
\x20 cat '{asset}' > \"$out\"\n\
else\n\
\x20 printf '%s\\n' ' \"browser_download_url\": \"{url}\"'\n\
fi\n",
asset = asset.display(),
url = url,
),
)
.expect("write curl stub");
fs::set_permissions(&stub, fs::Permissions::from_mode(0o755)).expect("chmod stub");
stub_dir
}