deno.land / x / deno@v1.28.2 / runtime / ops / spawn.rs
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use super::io::ChildStderrResource;use super::io::ChildStdinResource;use super::io::ChildStdoutResource;use super::process::Stdio;use super::process::StdioOrRid;use crate::permissions::Permissions;use deno_core::error::AnyError;use deno_core::op;use deno_core::Extension;use deno_core::OpState;use deno_core::Resource;use deno_core::ResourceId;use deno_core::ZeroCopyBuf;use serde::Deserialize;use serde::Serialize;use std::borrow::Cow;use std::cell::RefCell;#[cfg(windows)]use std::os::windows::process::CommandExt;use std::process::ExitStatus;use std::rc::Rc;
#[cfg(unix)]use std::os::unix::prelude::ExitStatusExt;#[cfg(unix)]use std::os::unix::process::CommandExt;
pub fn init() -> Extension { Extension::builder() .ops(vec![ op_spawn_child::decl(), op_node_unstable_spawn_child::decl(), op_spawn_wait::decl(), op_spawn_sync::decl(), op_node_unstable_spawn_sync::decl(), ]) .build()}
struct ChildResource(tokio::process::Child);
impl Resource for ChildResource { fn name(&self) -> Cow<str> { "child".into() }}
#[derive(Deserialize)]#[serde(rename_all = "camelCase")]pub struct SpawnArgs { cmd: String, args: Vec<String>, cwd: Option<String>, clear_env: bool, env: Vec<(String, String)>, #[cfg(unix)] gid: Option<u32>, #[cfg(unix)] uid: Option<u32>, #[cfg(windows)] windows_raw_arguments: bool,
#[serde(flatten)] stdio: ChildStdio,}
#[derive(Deserialize)]#[serde(rename_all = "camelCase")]pub struct ChildStdio { stdin: Stdio, stdout: Stdio, stderr: Stdio,}
#[derive(Serialize)]#[serde(rename_all = "camelCase")]pub struct ChildStatus { success: bool, code: i32, signal: Option<String>,}
impl TryFrom<ExitStatus> for ChildStatus { type Error = AnyError;
fn try_from(status: ExitStatus) -> Result<Self, Self::Error> { let code = status.code(); #[cfg(unix)] let signal = status.signal(); #[cfg(not(unix))] let signal: Option<i32> = None;
let status = if let Some(signal) = signal { ChildStatus { success: false, code: 128 + signal, #[cfg(unix)] signal: Some( crate::ops::signal::signal_int_to_str(signal)?.to_string(), ), #[cfg(not(unix))] signal: None, } } else { let code = code.expect("Should have either an exit code or a signal.");
ChildStatus { success: code == 0, code, signal: None, } };
Ok(status) }}
#[derive(Serialize)]#[serde(rename_all = "camelCase")]pub struct SpawnOutput { status: ChildStatus, stdout: Option<ZeroCopyBuf>, stderr: Option<ZeroCopyBuf>,}
fn node_unstable_create_command( state: &mut OpState, args: SpawnArgs, api_name: &str,) -> Result<std::process::Command, AnyError> { state .borrow_mut::<Permissions>() .run .check(&args.cmd, Some(api_name))?;
let mut command = std::process::Command::new(args.cmd);
#[cfg(windows)] if args.windows_raw_arguments { for arg in args.args.iter() { command.raw_arg(arg); } } else { command.args(args.args); }
#[cfg(not(windows))] command.args(args.args);
if let Some(cwd) = args.cwd { command.current_dir(cwd); }
if args.clear_env { command.env_clear(); } command.envs(args.env);
#[cfg(unix)] if let Some(gid) = args.gid { command.gid(gid); } #[cfg(unix)] if let Some(uid) = args.uid { command.uid(uid); } #[cfg(unix)] // TODO(bartlomieju): #[allow(clippy::undocumented_unsafe_blocks)] unsafe { command.pre_exec(|| { libc::setgroups(0, std::ptr::null()); Ok(()) }); }
command.stdin(args.stdio.stdin.as_stdio()); command.stdout(match args.stdio.stdout { Stdio::Inherit => StdioOrRid::Rid(1).as_stdio(state)?, value => value.as_stdio(), }); command.stderr(match args.stdio.stderr { Stdio::Inherit => StdioOrRid::Rid(2).as_stdio(state)?, value => value.as_stdio(), });
Ok(command)}
fn create_command( state: &mut OpState, args: SpawnArgs, api_name: &str,) -> Result<std::process::Command, AnyError> { super::check_unstable(state, "Deno.spawn"); state .borrow_mut::<Permissions>() .run .check(&args.cmd, Some(api_name))?;
let mut command = std::process::Command::new(args.cmd);
#[cfg(windows)] if args.windows_raw_arguments { for arg in args.args.iter() { command.raw_arg(arg); } } else { command.args(args.args); }
#[cfg(not(windows))] command.args(args.args);
if let Some(cwd) = args.cwd { command.current_dir(cwd); }
if args.clear_env { command.env_clear(); } command.envs(args.env);
#[cfg(unix)] if let Some(gid) = args.gid { super::check_unstable(state, "Deno.spawn.gid"); command.gid(gid); } #[cfg(unix)] if let Some(uid) = args.uid { super::check_unstable(state, "Deno.spawn.uid"); command.uid(uid); } #[cfg(unix)] // TODO(bartlomieju): #[allow(clippy::undocumented_unsafe_blocks)] unsafe { command.pre_exec(|| { libc::setgroups(0, std::ptr::null()); Ok(()) }); }
command.stdin(args.stdio.stdin.as_stdio()); command.stdout(match args.stdio.stdout { Stdio::Inherit => StdioOrRid::Rid(1).as_stdio(state)?, value => value.as_stdio(), }); command.stderr(match args.stdio.stderr { Stdio::Inherit => StdioOrRid::Rid(2).as_stdio(state)?, value => value.as_stdio(), });
Ok(command)}
#[derive(Serialize)]#[serde(rename_all = "camelCase")]struct Child { rid: ResourceId, pid: u32, stdin_rid: Option<ResourceId>, stdout_rid: Option<ResourceId>, stderr_rid: Option<ResourceId>,}
fn spawn_child( state: &mut OpState, command: std::process::Command,) -> Result<Child, AnyError> { let mut command = tokio::process::Command::from(command); // TODO(@crowlkats): allow detaching processes. // currently deno will orphan a process when exiting with an error or Deno.exit() // We want to kill child when it's closed command.kill_on_drop(true);
let mut child = command.spawn()?; let pid = child.id().expect("Process ID should be set.");
let stdin_rid = child .stdin .take() .map(|stdin| state.resource_table.add(ChildStdinResource::from(stdin)));
let stdout_rid = child .stdout .take() .map(|stdout| state.resource_table.add(ChildStdoutResource::from(stdout)));
let stderr_rid = child .stderr .take() .map(|stderr| state.resource_table.add(ChildStderrResource::from(stderr)));
let child_rid = state.resource_table.add(ChildResource(child));
Ok(Child { rid: child_rid, pid, stdin_rid, stdout_rid, stderr_rid, })}
#[op]fn op_spawn_child( state: &mut OpState, args: SpawnArgs, api_name: String,) -> Result<Child, AnyError> { let command = create_command(state, args, &api_name)?; spawn_child(state, command)}
#[op]fn op_node_unstable_spawn_child( state: &mut OpState, args: SpawnArgs, api_name: String,) -> Result<Child, AnyError> { let command = node_unstable_create_command(state, args, &api_name)?; spawn_child(state, command)}
#[op]async fn op_spawn_wait( state: Rc<RefCell<OpState>>, rid: ResourceId,) -> Result<ChildStatus, AnyError> { let resource = state .borrow_mut() .resource_table .take::<ChildResource>(rid)?; Rc::try_unwrap(resource) .ok() .unwrap() .0 .wait() .await? .try_into()}
#[op]fn op_spawn_sync( state: &mut OpState, args: SpawnArgs,) -> Result<SpawnOutput, AnyError> { let stdout = matches!(args.stdio.stdout, Stdio::Piped); let stderr = matches!(args.stdio.stderr, Stdio::Piped); let output = create_command(state, args, "Deno.spawnSync()")?.output()?;
Ok(SpawnOutput { status: output.status.try_into()?, stdout: if stdout { Some(output.stdout.into()) } else { None }, stderr: if stderr { Some(output.stderr.into()) } else { None }, })}
#[op]fn op_node_unstable_spawn_sync( state: &mut OpState, args: SpawnArgs,) -> Result<SpawnOutput, AnyError> { let stdout = matches!(args.stdio.stdout, Stdio::Piped); let stderr = matches!(args.stdio.stderr, Stdio::Piped); let output = node_unstable_create_command(state, args, "Deno.spawnSync()")?.output()?;
Ok(SpawnOutput { status: output.status.try_into()?, stdout: if stdout { Some(output.stdout.into()) } else { None }, stderr: if stderr { Some(output.stderr.into()) } else { None }, })}
Version Info