use std::path::Path;
use std::path::PathBuf;
use crate::run;
use crate::LibdenoError;
use crate::LibdenoOptions;
const LIBDENO_CHILD_MODE: &str = "LIBDENO_CHILD_MODE";
const LIBDENO_CHILD_TOKEN: &str = "LIBDENO_CHILD_TOKEN";
const LIBDENO_HOST_EXE: &str = "LIBDENO_HOST_EXE";
#[derive(serde::Serialize, serde::Deserialize)]
struct ChildRunRequest {
entry: String,
permissions: Vec<String>,
args: Vec<String>,
cwd: PathBuf,
token: String,
}
fn child_token() -> String {
let mut buf = [0u8; 16];
getrandom::getrandom(&mut buf).expect("failed to read system randomness for child token");
buf.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn run_in_subprocess(
entry: impl AsRef<Path>,
options: &LibdenoOptions,
) -> Result<i32, LibdenoError> {
let token = child_token();
let (payload, mut child) = {
let _lock = crate::CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let cwd = options.cwd.clone().unwrap_or(std::env::current_dir()?);
let request = ChildRunRequest {
entry: entry.as_ref().to_string_lossy().into_owned(),
permissions: options.permissions.clone(),
args: options.args.clone(),
cwd: cwd.clone(),
token: token.clone(),
};
let payload = deno_core::serde_json::to_vec(&request)
.map_err(|e| LibdenoError::Runtime(deno_core::anyhow::anyhow!(e)))?;
let exe = std::env::var_os(LIBDENO_HOST_EXE)
.map(PathBuf::from)
.unwrap_or(std::env::current_exe()?);
let child = std::process::Command::new(exe)
.env(LIBDENO_CHILD_MODE, "1")
.env(LIBDENO_CHILD_TOKEN, &token)
.current_dir(&cwd)
.stdin(std::process::Stdio::piped())
.spawn()
.map_err(LibdenoError::Io)?;
(payload, child)
};
{
use std::io::Write;
let write_result = match child.stdin.as_mut() {
Some(stdin) => stdin.write_all(&payload).map_err(LibdenoError::Io),
None => Err(LibdenoError::Runtime(deno_core::anyhow::anyhow!(
"child has no stdin"
))),
};
if let Err(e) = write_result {
let _ = child.kill();
let _ = child.wait();
return Err(e);
}
}
drop(child.stdin.take());
let status = child.wait().map_err(LibdenoError::Io)?;
Ok(status.code().unwrap_or(1))
}
pub fn maybe_handle_child_mode() -> bool {
if std::env::var(LIBDENO_CHILD_MODE).as_deref() != Ok("1") {
return false;
}
let Some(env_token) = std::env::var_os(LIBDENO_CHILD_TOKEN) else {
eprintln!(
"libdeno: {LIBDENO_CHILD_MODE} is set but {LIBDENO_CHILD_TOKEN} is missing; \
refusing to service an unauthenticated child request"
);
std::process::exit(1);
};
let result: Result<i32, LibdenoError> = (|| {
let request: ChildRunRequest = deno_core::serde_json::from_reader(std::io::stdin())
.map_err(|e| LibdenoError::Runtime(deno_core::anyhow::anyhow!(e)))?;
if request.token != env_token.to_string_lossy() {
return Err(LibdenoError::Runtime(deno_core::anyhow::anyhow!(
"child request token does not match {LIBDENO_CHILD_TOKEN}"
)));
}
let options = LibdenoOptions {
permissions: request.permissions,
args: request.args,
cwd: Some(request.cwd),
};
run(&request.entry, &options)
})();
match result {
Ok(code) => std::process::exit(code),
Err(e) => {
eprintln!("libdeno child run failed: {e}");
std::process::exit(1);
}
}
}