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: Option<PathBuf>,
token: String,
}
fn child_token() -> String {
#[cfg(unix)]
{
use std::io::Read;
if let Ok(mut f) = std::fs::File::open("/dev/urandom") {
let mut buf = [0u8; 16];
if f.read_exact(&mut buf).is_ok() {
return buf.iter().map(|b| format!("{b:02x}")).collect();
}
}
}
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
format!("{nanos:016x}{:016x}", std::process::id())
}
pub fn run_in_subprocess(
entry: impl AsRef<Path>,
options: &LibdenoOptions,
) -> Result<i32, LibdenoError> {
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 token = child_token();
let request = ChildRunRequest {
entry: entry.as_ref().to_string_lossy().into_owned(),
permissions: options.permissions.clone(),
args: options.args.clone(),
cwd: Some(cwd),
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 mut child = std::process::Command::new(exe)
.env(LIBDENO_CHILD_MODE, "1")
.env(LIBDENO_CHILD_TOKEN, &token)
.stdin(std::process::Stdio::piped())
.spawn()
.map_err(LibdenoError::Io)?;
{
use std::io::Write;
child
.stdin
.as_mut()
.ok_or_else(|| LibdenoError::Runtime(deno_core::anyhow::anyhow!("child has no stdin")))?
.write_all(&payload)
.map_err(LibdenoError::Io)?;
}
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_os(LIBDENO_CHILD_MODE).is_none() {
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: 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);
}
}
}