diff --git a/app/igneum-app/src/bin/prove-verify.rs b/app/igneum-app/src/bin/prove-verify.rs index 2e77abf45..ea20ccef7 100644 --- a/app/igneum-app/src/bin/prove-verify.rs +++ b/app/igneum-app/src/bin/prove-verify.rs @@ -48,22 +48,22 @@ fn quiet(cmd: &mut Command) -> &mut Command { cmd } -fn shell_quote(s: &str) -> String { - format!("'{}'", s.replace('\'', "'\\''")) +/// The drive-letter mapping (src/wslhost.rs). A `wslpath -a` round trip through wsl.exe was dropped on 5 October +/// 2026: a path with a space on that command line is split by the shell inside the distribution. +fn to_wsl(p: &Path) -> String { + wslhost::wsl_path(p) } -/// `wslpath -a ` inside the distribution; the drive-letter mapping when wslpath did not answer. -fn to_wsl(p: &Path) -> String { - let out = quiet(&mut Command::new(wsl_exe())).args(["-d", wslhost::DISTRO, "--", "wslpath", "-a"]).arg(p).stdin(Stdio::null()).output(); - if let Ok(o) = out { - if o.status.success() { - let s = String::from_utf8_lossy(&o.stdout).trim().to_string(); - if s.starts_with('/') { - return s; - } +/// The script file: runs the probe or the host (`write_script` under %LOCALAPPDATA%\igneum\wsl, removed after the +/// run); exits 2 when the file cannot be written. +fn script_file(stem: &str, body: &str) -> wslhost::ScriptFile { + match wslhost::write_script(stem, body) { + Ok(f) => f, + Err(e) => { + eprintln!("igneum-prove-verify: cannot write the {stem} script under {}: {e}", wslhost::script_dir().display()); + std::process::exit(NO_HOST); } } - wslhost::wsl_path(p) } /// The host's arguments with `--proof ` rewritten for WSL. Pure, so it has a test. @@ -91,12 +91,10 @@ pub fn rewrite_args String>(args: &[String], to_wsl: F) -> Vec String { +pub fn run_script(bin_dir: &Path) -> String { let lookup = wslhost::lookup_script(bin_dir); - let args: Vec = host_args.iter().map(|a| shell_quote(a)).collect(); format!( - "h=$({lookup}); if [ -n \"$h\" ]; then exec \"$h\" {}; fi; echo 'igneum-prove-verify: no igneum-prove-host in WSL2 (looked at: {})' >&2; exit {NO_HOST}", - args.join(" "), + "h=$({lookup}); if [ -n \"$h\" ]; then exec \"$h\" \"$@\"; fi; echo 'igneum-prove-verify: no igneum-prove-host in WSL2 (looked at: {})' >&2; exit {NO_HOST}", wslhost::candidates_text(bin_dir).replace('\'', "'\\''") ) } @@ -109,7 +107,8 @@ fn main() { return; } if args.iter().any(|a| a == "--probe") { - let out = quiet(&mut Command::new(wsl_exe())).args(["-d", wslhost::DISTRO, "--", "bash", "-lc", &wslhost::lookup_script(&bin_dir)]).stdin(Stdio::null()).output(); + let file = script_file("verify-probe", &wslhost::lookup_script(&bin_dir)); + let out = quiet(&mut wslhost::command(&wsl_exe(), wslhost::DISTRO, None, &file.path, true, &[])).stdin(Stdio::null()).output(); let host = out.ok().filter(|o| o.status.success()).map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()).unwrap_or_default(); if host.is_empty() { eprintln!("igneum-prove-verify: no igneum-prove-host in WSL2 ({}) (looked at: {})", wslhost::DISTRO, wslhost::candidates_text(&bin_dir)); @@ -119,8 +118,9 @@ fn main() { return; } let host_args = rewrite_args(&args, to_wsl); - let script = run_script(&bin_dir, &host_args); - let status = quiet(&mut Command::new(wsl_exe())).args(["-d", wslhost::DISTRO, "--", "bash", "-lc", &script]).stdin(Stdio::null()).status(); + let file = script_file("verify-run", &run_script(&bin_dir)); + let argv: Vec<&str> = host_args.iter().map(|a| a.as_str()).collect(); + let status = quiet(&mut wslhost::command(&wsl_exe(), wslhost::DISTRO, None, &file.path, true, &argv)).stdin(Stdio::null()).status(); match status { Ok(st) => std::process::exit(st.code().unwrap_or(1)), Err(e) => { @@ -145,9 +145,12 @@ mod tests { #[test] fn the_script_execs_the_first_host_and_exits_2_without_one() { - let s = run_script(Path::new("C:\\Igneum"), &["--mode".into(), "verify".into(), "--statement".into(), "0xab".into()]); + let s = run_script(Path::new("C:\\Igneum")); assert!(s.starts_with("h=$(for f in '/mnt/c/Igneum/wsl2/bin/igneum-prove-host' ~/igneum-prove/proving/igneum-prove/target/release/igneum-prove-host ~/igneum-prove/target/release/igneum-prove-host '/opt/igneum/igneum-prove-host'; do"), "{s}"); - assert!(s.contains("exec \"$h\" '--mode' 'verify' '--statement' '0xab'; fi;")); + assert!(s.contains("exec \"$h\" \"$@\"; fi;"), "{s}"); + // the arguments travel on the command line as $1, $2... with no double quote anywhere + let line = wslhost::bash_line(Path::new("C:\\Users\\x\\AppData\\Local\\igneum\\wsl\\verify-run-1-0.sh"), true, &["--mode", "verify", "--proof", "/mnt/c/Users/x/p.bin", "--statement", "0xab"]); + assert_eq!(line, "bash -l '/mnt/c/Users/x/AppData/Local/igneum/wsl/verify-run-1-0.sh' '--mode' 'verify' '--proof' '/mnt/c/Users/x/p.bin' '--statement' '0xab'"); assert!(s.ends_with("exit 2")); assert!(s.contains("looked at: /mnt/c/Igneum/wsl2/bin/igneum-prove-host, ~/igneum-prove/proving/")); } diff --git a/app/igneum-app/src/jobrun.rs b/app/igneum-app/src/jobrun.rs index 322461ad1..1d669f1cc 100644 --- a/app/igneum-app/src/jobrun.rs +++ b/app/igneum-app/src/jobrun.rs @@ -833,12 +833,8 @@ fn probe(req: &str, wsl_user: &str) -> Result { users.push(DEFAULT_WSL_USER); users.push(""); for u in users { - let mut c = Command::new(&wsl); - c.args(["-d", DEFAULT_DISTRO]); - if !u.is_empty() { - c.args(["-u", u]); - } - c.args(["--", "bash", "-lc", PROVER_PROBE]); + let file = crate::wslhost::write_script("prover-probe", PROVER_PROBE).map_err(|e| format!("cannot write the WSL probe script: {e}"))?; + let mut c = crate::wslhost::command(&wsl, DEFAULT_DISTRO, Some(u), &file.path, true, &[]); let (code, out) = run_capture(&mut c, Duration::from_secs(90)); if code == Some(0) { return Ok(format!("toolchain in {DEFAULT_DISTRO}{}: {}", if u.is_empty() { " (default user)".to_string() } else { format!(" as {u}") }, short_out(&out))); @@ -1387,7 +1383,8 @@ fn run_shard_benchmark(shared: &Arc, job: &Job, sink: &Sink, data_root: let shard = fixtures[0].clone(); let blocks = fixtures[1..].join(" "); // what this run sees inside WSL: the account's own Ubuntu, so say who we are there - let (ccode, cout) = run_capture(Command::new(crate::platform::tool("wsl")).args(["-d", &distro, "-u", &user, "--", "bash", "-c", "echo \"wsl user $(id -un) uid $(id -u) home $HOME\"; nvidia-smi -L 2>&1 | head -1; ls -d \"$HOME/.sp1\" \"$HOME/igneum-prove\" 2>&1 | head -2"]), Duration::from_secs(60)); + let ctx = crate::wslhost::write_script("wsl-context", "echo \"wsl user $(id -un) uid $(id -u) home $HOME\"; nvidia-smi -L 2>&1 | head -1; ls -d \"$HOME/.sp1\" \"$HOME/igneum-prove\" 2>&1 | head -2").map_err(|e| format!("cannot write the WSL context script: {e}"))?; + let (ccode, cout) = run_capture(&mut crate::wslhost::command(&crate::platform::tool("wsl"), &distro, Some(&user), &ctx.path, false, &[]), Duration::from_secs(60)); sink.line(&format!("wsl context (-u {user}): exit {ccode:?}: {}", short_out(&cout))); // the payload ships the Linux host next to the app (wsl2\bin): no cargo, no toolchain, run it fixture by fixture; // params.build = true forces the package's prove-shard.sh (cargo build inside the distro) instead @@ -1411,8 +1408,8 @@ fn run_shard_benchmark(shared: &Arc, job: &Job, sink: &Sink, data_root: } let mode = if i == 0 { "shard --shard 0" } else { "block" }; let line = format!("export PATH=\"/usr/local/cuda/bin:$PATH\"; CUDA_DIR=$(ls -d /usr/local/cuda-12.* 2>/dev/null | sort -V | tail -1); export LD_LIBRARY_PATH=\"/usr/lib/wsl/lib:${{CUDA_DIR:+$CUDA_DIR/lib64:}}${{LD_LIBRARY_PATH:-}}\"; echo \"=== GPU run: {f} --mode {mode} (SP1_PROVER=cuda) ===\"; SP1_PROVER=cuda RUST_LOG=info '{host_wsl}' '{fixdir_wsl}/{f}.json' --mode {mode} --out '{results_wsl}/{f}-cuda-{started}.json'; rc=$?; echo \"run exit $rc at $(date -u +%FT%TZ)\"; exit $rc"); - let mut cmd = Command::new(crate::platform::tool("wsl")); - cmd.args(["-d", &distro, "-u", &user, "--", "bash", "-c", &line]); + let run = crate::wslhost::write_script("gpu-run", &line).map_err(|e| format!("cannot write the WSL run script: {e}"))?; + let mut cmd = crate::wslhost::command(&crate::platform::tool("wsl"), &distro, Some(&user), &run.path, false, &[]); cmd.current_dir(&pkg); let r = run_streamed(&mut cmd, sink, ctl, left, shared, job, started, "shard benchmark running")?; if r.code != Some(0) { @@ -1434,7 +1431,9 @@ fn run_shard_benchmark(shared: &Arc, job: &Job, sink: &Sink, data_root: }; if ran.code.is_none() { // the Linux side outlives wsl.exe: end the prover there too - let _ = run_capture(Command::new(crate::platform::tool("wsl")).args(["-d", &distro, "-u", &user, "--", "bash", "-c", "pkill -f igneum-prove-host; pkill -f prove-shard.sh; true"]), Duration::from_secs(30)); + if let Ok(kill) = crate::wslhost::write_script("prover-kill", "pkill -f igneum-prove-host; pkill -f prove-shard.sh; true") { + let _ = run_capture(&mut crate::wslhost::command(&crate::platform::tool("wsl"), &distro, Some(&user), &kill.path, false, &[]), Duration::from_secs(30)); + } } sink.stage("uploading the results"); let mut uploaded = Vec::new(); @@ -1485,7 +1484,9 @@ fn build_stage(shared: &Arc, job: &Job, sink: &Sink, ctl: &Ctl, p: &jb:: cmd.current_dir(dir); let ran = run_streamed(&mut cmd, sink, ctl, cap, shared, job, started, &format!("build: {stage}"))?; if ran.code.is_none() { - let _ = run_capture(Command::new(crate::platform::tool("wsl")).args(["-d", &p.distro, "-u", &p.wsl_user, "--", "bash", "-c", jb::kill_script()]), Duration::from_secs(30)); + if let Ok(kill) = crate::wslhost::write_script("build-kill", jb::kill_script()) { + let _ = run_capture(&mut crate::wslhost::command(&crate::platform::tool("wsl"), &p.distro, Some(&p.wsl_user), &kill.path, false, &[]), Duration::from_secs(30)); + } } sink.line(&format!("STAGE {stage} {} {} {} s exit {}", if ran.timed_out { "timeout" } else { "end" }, jobs::format_time(crate::platform::unix_now()), t0.elapsed().as_secs(), ran.code.map(|c| c.to_string()).unwrap_or_else(|| "none".into()))); Ok(ran) @@ -1562,7 +1563,8 @@ fn run_build(shared: &Arc, job: &Job, sink: &Sink, jobs_dir: &Path, ctl: let ps = format!("[math]::Floor(([IO.DriveInfo]::new('{drive}')).AvailableFreeSpace/1GB)"); let (wc, wo) = run_capture(Command::new(crate::platform::tool("powershell")).args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps]), Duration::from_secs(40)); let win_free = if wc == Some(0) { jb::parse_free_gb(&wo) } else { None }; - let (lc, lo) = run_capture(Command::new(&wsl).args(["-d", &p.distro, "-u", &p.wsl_user, "--", "bash", "-c", jb::free_gb_script()]), Duration::from_secs(120)); + let free = crate::wslhost::write_script("free-gb", jb::free_gb_script()).map_err(|e| format!("cannot write the WSL free-space script: {e}"))?; + let (lc, lo) = run_capture(&mut crate::wslhost::command(&wsl, &p.distro, Some(&p.wsl_user), &free.path, false, &[]), Duration::from_secs(120)); let wsl_free = if lc == Some(0) { jb::parse_free_gb(&lo) } else { None }; sink.line(&format!("RESULT check {} drive {drive}: {} GB free, {} /root: {} GB free, floor {} GB", now(), win_free.map(|g| g.to_string()).unwrap_or_else(|| format!("unknown (powershell exit {wc:?}: {})", short_out(&wo))), p.distro, wsl_free.map(|g| g.to_string()).unwrap_or_else(|| format!("unknown (wsl exit {lc:?}: {})", short_out(&lo))), p.min_free_gb)); let Some(wf) = win_free else { return Err("cannot read the free space of the Windows drive".into()) }; diff --git a/app/igneum-app/src/prover.rs b/app/igneum-app/src/prover.rs index 1106b79dd..76cfa1485 100644 --- a/app/igneum-app/src/prover.rs +++ b/app/igneum-app/src/prover.rs @@ -154,8 +154,11 @@ fn find_tools(bin_dir: &Path) -> Result { // the SP1 host runs inside WSL2 (Ubuntu-24.04): the Linux binaries the payload ships under wsl2\bin\ (seen // from Ubuntu as /mnt//.../wsl2/bin), else one built there by setup-wsl.sh, else a hand install // (the order and the list are src/wslhost.rs, shared with igneum-prove-verify.exe) - let script = format!("{}; command -v nvidia-smi >/dev/null && echo cuda", crate::wslhost::lookup_script(bin_dir)); - let probe = crate::platform::quiet(&mut Command::new(crate::platform::tool("wsl"))).args(["-d", crate::wslhost::DISTRO, "--", "bash", "-lc", &script]).output(); + // from a file, never inline (src/wslhost.rs: an inline script with double quotes and a path with a space + // reached bash mangled on 5 October 2026 and the app said the setup was missing) + let body = format!("{}\ncommand -v nvidia-smi >/dev/null && echo cuda\ntrue", crate::wslhost::lookup_script(bin_dir)); + let file = crate::wslhost::write_script("prove-probe", &body).map_err(|e| format!("cannot write the WSL probe script: {e}"))?; + let probe = crate::platform::quiet(&mut crate::wslhost::command(&crate::platform::tool("wsl"), crate::wslhost::DISTRO, None, &file.path, true, &[])).output(); let answered = probe.is_ok(); let text = probe.map(|o| String::from_utf8_lossy(&o.stdout).to_string()).unwrap_or_default(); let host = text.lines().find(|l| l.contains("igneum-prove-host")).map(|l| PathBuf::from(l.trim())); @@ -218,19 +221,25 @@ fn read_verifier(shared: &Shared) { /// every second and killed when the app quits, the setting goes off or `limit` passes (a proof must never outlive /// the app). Returns (exit ok, output). fn run_tool(shared: &Shared, t: &Tools, exe: &Path, args: &[String], env: &[(&str, &str)], limit: Duration, log: &Path) -> (bool, String) { - let mut cmd = if t.wsl { - let mut c = Command::new(crate::platform::tool("wsl")); - let envs: String = env.iter().map(|(k, v)| format!("{k}={v} ")).collect(); - let line = format!("{envs}{} {}", exe.display(), args.iter().map(|a| format!("'{a}'")).collect::>().join(" ")); - c.args(["-d", crate::wslhost::DISTRO, "--", "bash", "-lc", &line]); - c + // Windows: a script file exports the environment and execs the host; the arguments travel as $1, $2... (the + // single-quoted rule of src/wslhost.rs). The file lives until the run ends. + let (mut cmd, _script) = if t.wsl { + let mut body: String = env.iter().map(|(k, v)| format!("export {k}={}\n", crate::wslhost::sq(v))).collect(); + body.push_str(&format!("exec {} \"$@\"", crate::wslhost::sq(&exe.display().to_string()))); + let file = match crate::wslhost::write_script("prove-run", &body) { + Ok(f) => f, + Err(e) => return (false, format!("cannot write the WSL run script: {e}")), + }; + let argv: Vec<&str> = args.iter().map(|a| a.as_str()).collect(); + let c = crate::wslhost::command(&crate::platform::tool("wsl"), crate::wslhost::DISTRO, None, &file.path, true, &argv); + (c, Some(file)) } else { let mut c = Command::new(exe); c.args(args); for (k, v) in env { c.env(k, v); } - c + (c, None) }; crate::platform::quiet(&mut cmd); let Ok(file) = std::fs::File::create(log) else { return (false, format!("cannot write {}", log.display())) }; @@ -512,9 +521,17 @@ pub fn setup(shared: &Shared) -> Result { if !script.exists() { return Err(format!("setup script missing: {}", script.display())); } - let line = format!("bash {}", wsl_path(&script)); + // cmd's `start` opens the window; the tail after `--` follows the single-quoted rule of src/wslhost.rs (the + // payload path has a space) and goes on the line as written + let line = format!("/c start \"\" {} -d {} -- {}", crate::platform::tool("wsl").display(), crate::wslhost::DISTRO, crate::wslhost::bash_line(&script, true, &[])); let mut c = Command::new(crate::platform::tool("cmd")); - c.args(["/c", "start", "", &crate::platform::tool("wsl").display().to_string(), "-d", crate::wslhost::DISTRO, "--", "bash", "-lc", &line]); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + c.raw_arg(&line); + } + #[cfg(not(windows))] + c.arg(&line); crate::platform::quiet(&mut c); // cmd itself hidden; `start` still opens the setup's own window c.spawn().map_err(|e| e.to_string())?; shared.event("proving", "WSL2 prover setup started in its own window"); diff --git a/app/igneum-app/src/wslhost.rs b/app/igneum-app/src/wslhost.rs index 5bc195068..2740eb752 100644 --- a/app/igneum-app/src/wslhost.rs +++ b/app/igneum-app/src/wslhost.rs @@ -10,7 +10,9 @@ //! 3. `~/igneum-prove/target/release/igneum-prove-host`, the layout before 5 October 2026 //! 4. `/opt/igneum/igneum-prove-host`, a hand install (the devnet jobs put the CUDA host there) -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::atomic::{AtomicU64, Ordering}; /// The WSL distribution the host runs in. pub const DISTRO: &str = "Ubuntu-24.04"; @@ -50,10 +52,174 @@ pub fn lookup_script(bin_dir: &Path) -> String { format!("for f in {}; do [ -x \"$f\" ] && {{ echo \"$f\"; break; }}; done", list.join(" ")) } +// ---- scripts run from a file, never inline on the command line ------------------------------------------------------ +// +// 5 October 2026, PC 2 on 0.3.5: `wsl -d Ubuntu-24.04 -- bash -lc "