1use deno_core::op2;
4use deno_core::serde_json;
5use deno_core::AsyncMutFuture;
6use deno_core::AsyncRefCell;
7use deno_core::OpState;
8use deno_core::RcRef;
9use deno_core::Resource;
10use deno_core::ResourceId;
11use deno_core::ToJsBuffer;
12use deno_io::fs::FileResource;
13use deno_io::ChildStderrResource;
14use deno_io::ChildStdinResource;
15use deno_io::ChildStdoutResource;
16use deno_io::IntoRawIoHandle;
17use deno_permissions::PermissionsContainer;
18use deno_permissions::RunQueryDescriptor;
19use serde::Deserialize;
20use serde::Serialize;
21use std::borrow::Cow;
22use std::cell::RefCell;
23use std::collections::HashMap;
24use std::ffi::OsString;
25use std::io::Write;
26use std::path::Path;
27use std::path::PathBuf;
28use std::process::ExitStatus;
29use std::rc::Rc;
30use tokio::process::Command;
31
32#[cfg(windows)]
33use std::os::windows::process::CommandExt;
34
35use crate::ops::signal::SignalError;
36#[cfg(unix)]
37use std::os::unix::prelude::ExitStatusExt;
38#[cfg(unix)]
39use std::os::unix::process::CommandExt;
40
41pub const UNSTABLE_FEATURE_NAME: &str = "process";
42
43#[derive(Copy, Clone, Eq, PartialEq, Deserialize)]
44#[serde(rename_all = "snake_case")]
45pub enum Stdio {
46 Inherit,
47 Piped,
48 Null,
49 IpcForInternalUse,
50}
51
52impl Stdio {
53 pub fn as_stdio(&self) -> std::process::Stdio {
54 match &self {
55 Stdio::Inherit => std::process::Stdio::inherit(),
56 Stdio::Piped => std::process::Stdio::piped(),
57 Stdio::Null => std::process::Stdio::null(),
58 _ => unreachable!(),
59 }
60 }
61}
62
63#[derive(Copy, Clone, Eq, PartialEq)]
64pub enum StdioOrRid {
65 Stdio(Stdio),
66 Rid(ResourceId),
67}
68
69impl<'de> Deserialize<'de> for StdioOrRid {
70 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
71 where
72 D: serde::Deserializer<'de>,
73 {
74 use serde_json::Value;
75 let value = Value::deserialize(deserializer)?;
76 match value {
77 Value::String(val) => match val.as_str() {
78 "inherit" => Ok(StdioOrRid::Stdio(Stdio::Inherit)),
79 "piped" => Ok(StdioOrRid::Stdio(Stdio::Piped)),
80 "null" => Ok(StdioOrRid::Stdio(Stdio::Null)),
81 "ipc_for_internal_use" => {
82 Ok(StdioOrRid::Stdio(Stdio::IpcForInternalUse))
83 }
84 val => Err(serde::de::Error::unknown_variant(
85 val,
86 &["inherit", "piped", "null"],
87 )),
88 },
89 Value::Number(val) => match val.as_u64() {
90 Some(val) if val <= ResourceId::MAX as u64 => {
91 Ok(StdioOrRid::Rid(val as ResourceId))
92 }
93 _ => Err(serde::de::Error::custom("Expected a positive integer")),
94 },
95 _ => Err(serde::de::Error::custom(
96 r#"Expected a resource id, "inherit", "piped", or "null""#,
97 )),
98 }
99 }
100}
101
102impl StdioOrRid {
103 pub fn as_stdio(
104 &self,
105 state: &mut OpState,
106 ) -> Result<std::process::Stdio, ProcessError> {
107 match &self {
108 StdioOrRid::Stdio(val) => Ok(val.as_stdio()),
109 StdioOrRid::Rid(rid) => {
110 FileResource::with_file(state, *rid, |file| Ok(file.as_stdio()?))
111 .map_err(ProcessError::Resource)
112 }
113 }
114 }
115
116 pub fn is_ipc(&self) -> bool {
117 matches!(self, StdioOrRid::Stdio(Stdio::IpcForInternalUse))
118 }
119}
120
121#[allow(clippy::disallowed_types)]
122pub type NpmProcessStateProviderRc =
123 deno_fs::sync::MaybeArc<dyn NpmProcessStateProvider>;
124
125pub trait NpmProcessStateProvider:
126 std::fmt::Debug + deno_fs::sync::MaybeSend + deno_fs::sync::MaybeSync
127{
128 fn get_npm_process_state(&self) -> String {
134 String::new()
136 }
137}
138
139#[derive(Debug)]
140pub struct EmptyNpmProcessStateProvider;
141impl NpmProcessStateProvider for EmptyNpmProcessStateProvider {}
142deno_core::extension!(
143 deno_process,
144 ops = [
145 op_spawn_child,
146 op_spawn_wait,
147 op_spawn_sync,
148 op_spawn_kill,
149 deprecated::op_run,
150 deprecated::op_run_status,
151 deprecated::op_kill,
152 ],
153 options = { get_npm_process_state: Option<NpmProcessStateProviderRc> },
154 state = |state, options| {
155 state.put::<NpmProcessStateProviderRc>(options.get_npm_process_state.unwrap_or(deno_fs::sync::MaybeArc::new(EmptyNpmProcessStateProvider)));
156 },
157);
158
159struct ChildResource(RefCell<tokio::process::Child>, u32);
162
163impl Resource for ChildResource {
164 fn name(&self) -> Cow<str> {
165 "child".into()
166 }
167}
168
169#[derive(Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct SpawnArgs {
172 cmd: String,
173 args: Vec<String>,
174 cwd: Option<String>,
175 clear_env: bool,
176 env: Vec<(String, String)>,
177 #[cfg(unix)]
178 gid: Option<u32>,
179 #[cfg(unix)]
180 uid: Option<u32>,
181 #[cfg(windows)]
182 windows_raw_arguments: bool,
183 ipc: Option<i32>,
184
185 #[serde(flatten)]
186 stdio: ChildStdio,
187
188 extra_stdio: Vec<Stdio>,
189 detached: bool,
190 needs_npm_process_state: bool,
191}
192
193#[derive(Debug, thiserror::Error)]
194pub enum ProcessError {
195 #[error("Failed to spawn '{command}': {error}")]
196 SpawnFailed {
197 command: String,
198 #[source]
199 error: Box<ProcessError>,
200 },
201 #[error("{0}")]
202 Io(#[from] std::io::Error),
203 #[cfg(unix)]
204 #[error(transparent)]
205 Nix(nix::Error),
206 #[error("failed resolving cwd: {0}")]
207 FailedResolvingCwd(#[source] std::io::Error),
208 #[error(transparent)]
209 Permission(#[from] deno_permissions::PermissionCheckError),
210 #[error(transparent)]
211 RunPermission(#[from] CheckRunPermissionError),
212 #[error(transparent)]
213 Resource(deno_core::error::AnyError),
214 #[error(transparent)]
215 BorrowMut(std::cell::BorrowMutError),
216 #[error(transparent)]
217 Which(which::Error),
218 #[error("Child process has already terminated.")]
219 ChildProcessAlreadyTerminated,
220 #[error("Invalid pid")]
221 InvalidPid,
222 #[error(transparent)]
223 Signal(#[from] SignalError),
224 #[error("Missing cmd")]
225 MissingCmd, }
227
228#[derive(Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct ChildStdio {
231 stdin: StdioOrRid,
232 stdout: StdioOrRid,
233 stderr: StdioOrRid,
234}
235
236#[derive(Serialize)]
237#[serde(rename_all = "camelCase")]
238pub struct ChildStatus {
239 success: bool,
240 code: i32,
241 signal: Option<String>,
242}
243
244impl TryFrom<ExitStatus> for ChildStatus {
245 type Error = SignalError;
246
247 fn try_from(status: ExitStatus) -> Result<Self, Self::Error> {
248 let code = status.code();
249 #[cfg(unix)]
250 let signal = status.signal();
251 #[cfg(not(unix))]
252 let signal: Option<i32> = None;
253
254 let status = if let Some(signal) = signal {
255 ChildStatus {
256 success: false,
257 code: 128 + signal,
258 #[cfg(unix)]
259 signal: Some(
260 crate::ops::signal::signal_int_to_str(signal)?.to_string(),
261 ),
262 #[cfg(not(unix))]
263 signal: None,
264 }
265 } else {
266 let code = code.expect("Should have either an exit code or a signal.");
267
268 ChildStatus {
269 success: code == 0,
270 code,
271 signal: None,
272 }
273 };
274
275 Ok(status)
276 }
277}
278
279#[derive(Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct SpawnOutput {
282 status: ChildStatus,
283 stdout: Option<ToJsBuffer>,
284 stderr: Option<ToJsBuffer>,
285}
286
287type CreateCommand = (
288 std::process::Command,
289 Option<ResourceId>,
290 Vec<Option<ResourceId>>,
291 Vec<deno_io::RawBiPipeHandle>,
292);
293
294pub fn npm_process_state_tempfile(
295 contents: &[u8],
296) -> Result<deno_io::RawIoHandle, std::io::Error> {
297 let mut temp_file = tempfile::tempfile()?;
298 temp_file.write_all(contents)?;
299 let handle = temp_file.into_raw_io_handle();
300 #[cfg(windows)]
301 {
302 use windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT;
303 unsafe {
306 windows_sys::Win32::Foundation::SetHandleInformation(
307 handle as _,
308 HANDLE_FLAG_INHERIT,
309 HANDLE_FLAG_INHERIT,
310 );
311 }
312 Ok(handle)
313 }
314 #[cfg(unix)]
315 {
316 let inheritable = unsafe {
318 libc::dup(handle)
321 };
322 unsafe {
324 libc::close(handle);
326 }
327 Ok(inheritable)
328 }
329}
330
331pub const NPM_RESOLUTION_STATE_FD_ENV_VAR_NAME: &str =
332 "DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE_FD";
333
334fn create_command(
335 state: &mut OpState,
336 mut args: SpawnArgs,
337 api_name: &str,
338) -> Result<CreateCommand, ProcessError> {
339 let maybe_npm_process_state = if args.needs_npm_process_state {
340 let provider = state.borrow::<NpmProcessStateProviderRc>();
341 let process_state = provider.get_npm_process_state();
342 let fd = npm_process_state_tempfile(process_state.as_bytes())?;
343 args.env.push((
344 NPM_RESOLUTION_STATE_FD_ENV_VAR_NAME.to_string(),
345 (fd as usize).to_string(),
346 ));
347 Some(fd)
348 } else {
349 None
350 };
351
352 let (cmd, run_env) = compute_run_cmd_and_check_permissions(
353 &args.cmd,
354 args.cwd.as_deref(),
355 &args.env,
356 args.clear_env,
357 state,
358 api_name,
359 )?;
360 let mut command = std::process::Command::new(cmd);
361
362 #[cfg(windows)]
363 {
364 if args.detached {
365 log::warn!("detached processes are not currently supported on Windows");
370 }
371 if args.windows_raw_arguments {
372 for arg in args.args.iter() {
373 command.raw_arg(arg);
374 }
375 } else {
376 command.args(args.args);
377 }
378 }
379
380 #[cfg(not(windows))]
381 command.args(args.args);
382
383 command.current_dir(run_env.cwd);
384 command.env_clear();
385 command.envs(run_env.envs);
386
387 #[cfg(unix)]
388 if let Some(gid) = args.gid {
389 command.gid(gid);
390 }
391 #[cfg(unix)]
392 if let Some(uid) = args.uid {
393 command.uid(uid);
394 }
395
396 if args.stdio.stdin.is_ipc() {
397 args.ipc = Some(0);
398 } else {
399 command.stdin(args.stdio.stdin.as_stdio(state)?);
400 }
401
402 command.stdout(match args.stdio.stdout {
403 StdioOrRid::Stdio(Stdio::Inherit) => StdioOrRid::Rid(1).as_stdio(state)?,
404 value => value.as_stdio(state)?,
405 });
406 command.stderr(match args.stdio.stderr {
407 StdioOrRid::Stdio(Stdio::Inherit) => StdioOrRid::Rid(2).as_stdio(state)?,
408 value => value.as_stdio(state)?,
409 });
410
411 #[cfg(unix)]
412 #[allow(clippy::undocumented_unsafe_blocks)]
414 unsafe {
415 let mut extra_pipe_rids = Vec::new();
416 let mut fds_to_dup = Vec::new();
417 let mut fds_to_close = Vec::new();
418 let mut ipc_rid = None;
419 if let Some(fd) = maybe_npm_process_state {
420 fds_to_close.push(fd);
421 }
422 if let Some(ipc) = args.ipc {
423 if ipc >= 0 {
424 let (ipc_fd1, ipc_fd2) = deno_io::bi_pipe_pair_raw()?;
425 fds_to_dup.push((ipc_fd2, ipc));
426 fds_to_close.push(ipc_fd2);
427 let pipe_rid =
429 state
430 .resource_table
431 .add(deno_node::IpcJsonStreamResource::new(
432 ipc_fd1 as _,
433 deno_node::IpcRefTracker::new(state.external_ops_tracker.clone()),
434 )?);
435 command.env("NODE_CHANNEL_FD", format!("{}", ipc));
437 ipc_rid = Some(pipe_rid);
438 }
439 }
440
441 for (i, stdio) in args.extra_stdio.into_iter().enumerate() {
442 let fd = (i + 3) as i32;
445 if matches!(stdio, Stdio::Piped) {
449 let (fd1, fd2) = deno_io::bi_pipe_pair_raw()?;
450 fds_to_dup.push((fd2, fd));
451 fds_to_close.push(fd2);
452 let rid = state.resource_table.add(
453 match deno_io::BiPipeResource::from_raw_handle(fd1) {
454 Ok(v) => v,
455 Err(e) => {
456 log::warn!("Failed to open bidirectional pipe for fd {fd}: {e}");
457 extra_pipe_rids.push(None);
458 continue;
459 }
460 },
461 );
462 extra_pipe_rids.push(Some(rid));
463 } else {
464 extra_pipe_rids.push(None);
465 }
466 }
467
468 let detached = args.detached;
469 command.pre_exec(move || {
470 if detached {
471 libc::setsid();
472 }
473 for &(src, dst) in &fds_to_dup {
474 if src >= 0 && dst >= 0 {
475 let _fd = libc::dup2(src, dst);
476 libc::close(src);
477 }
478 }
479 libc::setgroups(0, std::ptr::null());
480 Ok(())
481 });
482
483 Ok((command, ipc_rid, extra_pipe_rids, fds_to_close))
484 }
485
486 #[cfg(windows)]
487 {
488 let mut ipc_rid = None;
489 let mut handles_to_close = Vec::with_capacity(1);
490 if let Some(handle) = maybe_npm_process_state {
491 handles_to_close.push(handle);
492 }
493 if let Some(ipc) = args.ipc {
494 if ipc >= 0 {
495 let (hd1, hd2) = deno_io::bi_pipe_pair_raw()?;
496
497 let pipe_rid = Some(state.resource_table.add(
499 deno_node::IpcJsonStreamResource::new(
500 hd1 as i64,
501 deno_node::IpcRefTracker::new(state.external_ops_tracker.clone()),
502 )?,
503 ));
504
505 command.env("NODE_CHANNEL_FD", format!("{}", hd2 as i64));
507
508 handles_to_close.push(hd2);
509
510 ipc_rid = pipe_rid;
511 }
512 }
513
514 if args.extra_stdio.iter().any(|s| matches!(s, Stdio::Piped)) {
515 log::warn!(
516 "Additional stdio pipes beyond stdin/stdout/stderr are not currently supported on windows"
517 );
518 }
519
520 Ok((command, ipc_rid, vec![], handles_to_close))
521 }
522}
523
524#[derive(Serialize)]
525#[serde(rename_all = "camelCase")]
526struct Child {
527 rid: ResourceId,
528 pid: u32,
529 stdin_rid: Option<ResourceId>,
530 stdout_rid: Option<ResourceId>,
531 stderr_rid: Option<ResourceId>,
532 ipc_pipe_rid: Option<ResourceId>,
533 extra_pipe_rids: Vec<Option<ResourceId>>,
534}
535
536fn spawn_child(
537 state: &mut OpState,
538 command: std::process::Command,
539 ipc_pipe_rid: Option<ResourceId>,
540 extra_pipe_rids: Vec<Option<ResourceId>>,
541 detached: bool,
542) -> Result<Child, ProcessError> {
543 let mut command = tokio::process::Command::from(command);
544 if !detached {
548 command.kill_on_drop(true);
549 }
550
551 let mut child = match command.spawn() {
552 Ok(child) => child,
553 Err(err) => {
554 let command = command.as_std();
555 let command_name = command.get_program().to_string_lossy();
556
557 if let Some(cwd) = command.get_current_dir() {
558 #[allow(clippy::disallowed_methods)]
561 if !cwd.exists() {
562 return Err(
563 std::io::Error::new(
564 std::io::ErrorKind::NotFound,
565 format!(
566 "Failed to spawn '{}': No such cwd '{}'",
567 command_name,
568 cwd.to_string_lossy()
569 ),
570 )
571 .into(),
572 );
573 }
574
575 #[allow(clippy::disallowed_methods)]
576 if !cwd.is_dir() {
577 return Err(
578 std::io::Error::new(
579 std::io::ErrorKind::NotFound,
580 format!(
581 "Failed to spawn '{}': cwd is not a directory '{}'",
582 command_name,
583 cwd.to_string_lossy()
584 ),
585 )
586 .into(),
587 );
588 }
589 }
590
591 return Err(ProcessError::SpawnFailed {
592 command: command.get_program().to_string_lossy().to_string(),
593 error: Box::new(err.into()),
594 });
595 }
596 };
597
598 let pid = child.id().expect("Process ID should be set.");
599
600 let stdin_rid = child
601 .stdin
602 .take()
603 .map(|stdin| state.resource_table.add(ChildStdinResource::from(stdin)));
604
605 let stdout_rid = child
606 .stdout
607 .take()
608 .map(|stdout| state.resource_table.add(ChildStdoutResource::from(stdout)));
609
610 let stderr_rid = child
611 .stderr
612 .take()
613 .map(|stderr| state.resource_table.add(ChildStderrResource::from(stderr)));
614
615 let child_rid = state
616 .resource_table
617 .add(ChildResource(RefCell::new(child), pid));
618
619 Ok(Child {
620 rid: child_rid,
621 pid,
622 stdin_rid,
623 stdout_rid,
624 stderr_rid,
625 ipc_pipe_rid,
626 extra_pipe_rids,
627 })
628}
629
630fn compute_run_cmd_and_check_permissions(
631 arg_cmd: &str,
632 arg_cwd: Option<&str>,
633 arg_envs: &[(String, String)],
634 arg_clear_env: bool,
635 state: &mut OpState,
636 api_name: &str,
637) -> Result<(PathBuf, RunEnv), ProcessError> {
638 let run_env =
639 compute_run_env(arg_cwd, arg_envs, arg_clear_env).map_err(|e| {
640 ProcessError::SpawnFailed {
641 command: arg_cmd.to_string(),
642 error: Box::new(e),
643 }
644 })?;
645 let cmd =
646 resolve_cmd(arg_cmd, &run_env).map_err(|e| ProcessError::SpawnFailed {
647 command: arg_cmd.to_string(),
648 error: Box::new(e),
649 })?;
650 check_run_permission(
651 state,
652 &RunQueryDescriptor::Path {
653 requested: arg_cmd.to_string(),
654 resolved: cmd.clone(),
655 },
656 &run_env,
657 api_name,
658 )?;
659 Ok((cmd, run_env))
660}
661
662struct RunEnv {
663 envs: HashMap<OsString, OsString>,
664 cwd: PathBuf,
665}
666
667fn compute_run_env(
673 arg_cwd: Option<&str>,
674 arg_envs: &[(String, String)],
675 arg_clear_env: bool,
676) -> Result<RunEnv, ProcessError> {
677 #[allow(clippy::disallowed_methods)]
678 let cwd =
679 std::env::current_dir().map_err(ProcessError::FailedResolvingCwd)?;
680 let cwd = arg_cwd
681 .map(|cwd_arg| resolve_path(cwd_arg, &cwd))
682 .unwrap_or(cwd);
683 let envs = if arg_clear_env {
684 arg_envs
685 .iter()
686 .map(|(k, v)| (OsString::from(k), OsString::from(v)))
687 .collect()
688 } else {
689 let mut envs = std::env::vars_os()
690 .map(|(k, v)| {
691 (
692 if cfg!(windows) {
693 k.to_ascii_uppercase()
694 } else {
695 k
696 },
697 v,
698 )
699 })
700 .collect::<HashMap<_, _>>();
701 for (key, value) in arg_envs {
702 envs.insert(
703 OsString::from(if cfg!(windows) {
704 key.to_ascii_uppercase()
705 } else {
706 key.clone()
707 }),
708 OsString::from(value.clone()),
709 );
710 }
711 envs
712 };
713 Ok(RunEnv { envs, cwd })
714}
715
716fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, ProcessError> {
717 let is_path = cmd.contains('/');
718 #[cfg(windows)]
719 let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute();
720 if is_path {
721 Ok(resolve_path(cmd, &env.cwd))
722 } else {
723 let path = env.envs.get(&OsString::from("PATH"));
724 match which::which_in(cmd, path, &env.cwd) {
725 Ok(cmd) => Ok(cmd),
726 Err(which::Error::CannotFindBinaryPath) => {
727 Err(std::io::Error::from(std::io::ErrorKind::NotFound).into())
728 }
729 Err(err) => Err(ProcessError::Which(err)),
730 }
731 }
732}
733
734fn resolve_path(path: &str, cwd: &Path) -> PathBuf {
735 deno_path_util::normalize_path(cwd.join(path))
736}
737
738#[derive(Debug, thiserror::Error)]
739pub enum CheckRunPermissionError {
740 #[error(transparent)]
741 Permission(#[from] deno_permissions::PermissionCheckError),
742 #[error("{0}")]
743 Other(deno_core::error::AnyError),
744}
745
746fn check_run_permission(
747 state: &mut OpState,
748 cmd: &RunQueryDescriptor,
749 run_env: &RunEnv,
750 api_name: &str,
751) -> Result<(), CheckRunPermissionError> {
752 let permissions = state.borrow_mut::<PermissionsContainer>();
753 if !permissions.query_run_all(api_name) {
754 let env_var_names = get_requires_allow_all_env_vars(run_env);
756 if !env_var_names.is_empty() {
757 return Err(CheckRunPermissionError::Other(
760 deno_core::error::custom_error(
761 "NotCapable",
762 format!(
763 "Requires --allow-run permissions to spawn subprocess with {0} environment variable{1}. Alternatively, spawn with {2} environment variable{1} unset.",
764 env_var_names.join(", "),
765 if env_var_names.len() != 1 { "s" } else { "" },
766 if env_var_names.len() != 1 { "these" } else { "the" }
767 ),
768 ),
769 ));
770 }
771 permissions.check_run(cmd, api_name)?;
772 }
773 Ok(())
774}
775
776fn get_requires_allow_all_env_vars(env: &RunEnv) -> Vec<&str> {
777 fn requires_allow_all(key: &str) -> bool {
778 let key = key.trim();
779 key.starts_with("LD_") || key.starts_with("DYLD_")
782 }
783
784 fn is_empty(value: &OsString) -> bool {
785 value.is_empty()
786 || value.to_str().map(|v| v.trim().is_empty()).unwrap_or(false)
787 }
788
789 let mut found_envs = env
790 .envs
791 .iter()
792 .filter_map(|(k, v)| {
793 let key = k.to_str()?;
794 if requires_allow_all(key) && !is_empty(v) {
795 Some(key)
796 } else {
797 None
798 }
799 })
800 .collect::<Vec<_>>();
801 found_envs.sort();
802 found_envs
803}
804
805#[op2(stack_trace)]
806#[serde]
807fn op_spawn_child(
808 state: &mut OpState,
809 #[serde] args: SpawnArgs,
810 #[string] api_name: String,
811) -> Result<Child, ProcessError> {
812 let detached = args.detached;
813 let (command, pipe_rid, extra_pipe_rids, handles_to_close) =
814 create_command(state, args, &api_name)?;
815 let child = spawn_child(state, command, pipe_rid, extra_pipe_rids, detached);
816 for handle in handles_to_close {
817 deno_io::close_raw_handle(handle);
818 }
819 child
820}
821
822#[op2(async)]
823#[allow(clippy::await_holding_refcell_ref)]
824#[serde]
825async fn op_spawn_wait(
826 state: Rc<RefCell<OpState>>,
827 #[smi] rid: ResourceId,
828) -> Result<ChildStatus, ProcessError> {
829 let resource = state
830 .borrow_mut()
831 .resource_table
832 .get::<ChildResource>(rid)
833 .map_err(ProcessError::Resource)?;
834 let result = resource
835 .0
836 .try_borrow_mut()
837 .map_err(ProcessError::BorrowMut)?
838 .wait()
839 .await?
840 .try_into()?;
841 if let Ok(resource) = state.borrow_mut().resource_table.take_any(rid) {
842 resource.close();
843 }
844 Ok(result)
845}
846
847#[op2(stack_trace)]
848#[serde]
849fn op_spawn_sync(
850 state: &mut OpState,
851 #[serde] args: SpawnArgs,
852) -> Result<SpawnOutput, ProcessError> {
853 let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped));
854 let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped));
855 let (mut command, _, _, _) =
856 create_command(state, args, "Deno.Command().outputSync()")?;
857 let output = command.output().map_err(|e| ProcessError::SpawnFailed {
858 command: command.get_program().to_string_lossy().to_string(),
859 error: Box::new(e.into()),
860 })?;
861
862 Ok(SpawnOutput {
863 status: output.status.try_into()?,
864 stdout: if stdout {
865 Some(output.stdout.into())
866 } else {
867 None
868 },
869 stderr: if stderr {
870 Some(output.stderr.into())
871 } else {
872 None
873 },
874 })
875}
876
877#[op2(fast)]
878fn op_spawn_kill(
879 state: &mut OpState,
880 #[smi] rid: ResourceId,
881 #[string] signal: String,
882) -> Result<(), ProcessError> {
883 if let Ok(child_resource) = state.resource_table.get::<ChildResource>(rid) {
884 deprecated::kill(child_resource.1 as i32, &signal)?;
885 return Ok(());
886 }
887 Err(ProcessError::ChildProcessAlreadyTerminated)
888}
889
890mod deprecated {
891 use super::*;
892
893 #[derive(Deserialize)]
894 #[serde(rename_all = "camelCase")]
895 pub struct RunArgs {
896 cmd: Vec<String>,
897 cwd: Option<String>,
898 env: Vec<(String, String)>,
899 stdin: StdioOrRid,
900 stdout: StdioOrRid,
901 stderr: StdioOrRid,
902 }
903
904 struct ChildResource {
905 child: AsyncRefCell<tokio::process::Child>,
906 }
907
908 impl Resource for ChildResource {
909 fn name(&self) -> Cow<str> {
910 "child".into()
911 }
912 }
913
914 impl ChildResource {
915 fn borrow_mut(self: Rc<Self>) -> AsyncMutFuture<tokio::process::Child> {
916 RcRef::map(self, |r| &r.child).borrow_mut()
917 }
918 }
919
920 #[derive(Serialize)]
921 #[serde(rename_all = "camelCase")]
922 pub struct RunInfo {
924 rid: ResourceId,
925 pid: Option<u32>,
926 stdin_rid: Option<ResourceId>,
927 stdout_rid: Option<ResourceId>,
928 stderr_rid: Option<ResourceId>,
929 }
930
931 #[op2(stack_trace)]
932 #[serde]
933 pub fn op_run(
934 state: &mut OpState,
935 #[serde] run_args: RunArgs,
936 ) -> Result<RunInfo, ProcessError> {
937 let args = run_args.cmd;
938 let cmd = args.first().ok_or(ProcessError::MissingCmd)?;
939 let (cmd, run_env) = compute_run_cmd_and_check_permissions(
940 cmd,
941 run_args.cwd.as_deref(),
942 &run_args.env,
943 false,
944 state,
945 "Deno.run()",
946 )?;
947
948 let mut c = Command::new(cmd);
949 for arg in args.iter().skip(1) {
950 c.arg(arg);
951 }
952 c.current_dir(run_env.cwd);
953
954 c.env_clear();
955 for (key, value) in run_env.envs {
956 c.env(key, value);
957 }
958
959 #[cfg(unix)]
960 #[allow(clippy::undocumented_unsafe_blocks)]
962 unsafe {
963 c.pre_exec(|| {
964 libc::setgroups(0, std::ptr::null());
965 Ok(())
966 });
967 }
968
969 c.stdin(run_args.stdin.as_stdio(state)?);
971 c.stdout(
972 match run_args.stdout {
973 StdioOrRid::Stdio(Stdio::Inherit) => StdioOrRid::Rid(1),
974 value => value,
975 }
976 .as_stdio(state)?,
977 );
978 c.stderr(
979 match run_args.stderr {
980 StdioOrRid::Stdio(Stdio::Inherit) => StdioOrRid::Rid(2),
981 value => value,
982 }
983 .as_stdio(state)?,
984 );
985
986 c.kill_on_drop(true);
988
989 let mut child = c.spawn()?;
991 let pid = child.id();
992
993 let stdin_rid = match child.stdin.take() {
994 Some(child_stdin) => {
995 let rid = state
996 .resource_table
997 .add(ChildStdinResource::from(child_stdin));
998 Some(rid)
999 }
1000 None => None,
1001 };
1002
1003 let stdout_rid = match child.stdout.take() {
1004 Some(child_stdout) => {
1005 let rid = state
1006 .resource_table
1007 .add(ChildStdoutResource::from(child_stdout));
1008 Some(rid)
1009 }
1010 None => None,
1011 };
1012
1013 let stderr_rid = match child.stderr.take() {
1014 Some(child_stderr) => {
1015 let rid = state
1016 .resource_table
1017 .add(ChildStderrResource::from(child_stderr));
1018 Some(rid)
1019 }
1020 None => None,
1021 };
1022
1023 let child_resource = ChildResource {
1024 child: AsyncRefCell::new(child),
1025 };
1026 let child_rid = state.resource_table.add(child_resource);
1027
1028 Ok(RunInfo {
1029 rid: child_rid,
1030 pid,
1031 stdin_rid,
1032 stdout_rid,
1033 stderr_rid,
1034 })
1035 }
1036
1037 #[derive(Serialize)]
1038 #[serde(rename_all = "camelCase")]
1039 pub struct ProcessStatus {
1040 got_signal: bool,
1041 exit_code: i32,
1042 exit_signal: i32,
1043 }
1044
1045 #[op2(async)]
1046 #[serde]
1047 pub async fn op_run_status(
1048 state: Rc<RefCell<OpState>>,
1049 #[smi] rid: ResourceId,
1050 ) -> Result<ProcessStatus, ProcessError> {
1051 let resource = state
1052 .borrow_mut()
1053 .resource_table
1054 .get::<ChildResource>(rid)
1055 .map_err(ProcessError::Resource)?;
1056 let mut child = resource.borrow_mut().await;
1057 let run_status = child.wait().await?;
1058 let code = run_status.code();
1059
1060 #[cfg(unix)]
1061 let signal = run_status.signal();
1062 #[cfg(not(unix))]
1063 let signal = Default::default();
1064
1065 code
1066 .or(signal)
1067 .expect("Should have either an exit code or a signal.");
1068 let got_signal = signal.is_some();
1069
1070 Ok(ProcessStatus {
1071 got_signal,
1072 exit_code: code.unwrap_or(-1),
1073 exit_signal: signal.unwrap_or(-1),
1074 })
1075 }
1076
1077 #[cfg(unix)]
1078 pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> {
1079 let signo = super::super::signal::signal_str_to_int(signal)?;
1080 use nix::sys::signal::kill as unix_kill;
1081 use nix::sys::signal::Signal;
1082 use nix::unistd::Pid;
1083 let sig = Signal::try_from(signo).map_err(ProcessError::Nix)?;
1084 unix_kill(Pid::from_raw(pid), Some(sig)).map_err(ProcessError::Nix)
1085 }
1086
1087 #[cfg(not(unix))]
1088 pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> {
1089 use std::io::Error;
1090 use std::io::ErrorKind::NotFound;
1091 use winapi::shared::minwindef::DWORD;
1092 use winapi::shared::minwindef::FALSE;
1093 use winapi::shared::minwindef::TRUE;
1094 use winapi::shared::winerror::ERROR_INVALID_PARAMETER;
1095 use winapi::um::errhandlingapi::GetLastError;
1096 use winapi::um::handleapi::CloseHandle;
1097 use winapi::um::processthreadsapi::OpenProcess;
1098 use winapi::um::processthreadsapi::TerminateProcess;
1099 use winapi::um::winnt::PROCESS_TERMINATE;
1100
1101 if !matches!(signal, "SIGKILL" | "SIGTERM") {
1102 Err(SignalError::InvalidSignalStr(signal.to_string()).into())
1103 } else if pid <= 0 {
1104 Err(ProcessError::InvalidPid)
1105 } else {
1106 let handle =
1107 unsafe { OpenProcess(PROCESS_TERMINATE, FALSE, pid as DWORD) };
1109
1110 if handle.is_null() {
1111 let err = match unsafe { GetLastError() } {
1113 ERROR_INVALID_PARAMETER => Error::from(NotFound), errno => Error::from_raw_os_error(errno as i32),
1115 };
1116 Err(err.into())
1117 } else {
1118 unsafe {
1120 let is_terminated = TerminateProcess(handle, 1);
1121 CloseHandle(handle);
1122 match is_terminated {
1123 FALSE => Err(Error::last_os_error().into()),
1124 TRUE => Ok(()),
1125 _ => unreachable!(),
1126 }
1127 }
1128 }
1129 }
1130 }
1131
1132 #[op2(fast, stack_trace)]
1133 pub fn op_kill(
1134 state: &mut OpState,
1135 #[smi] pid: i32,
1136 #[string] signal: String,
1137 #[string] api_name: String,
1138 ) -> Result<(), ProcessError> {
1139 state
1140 .borrow_mut::<PermissionsContainer>()
1141 .check_run_all(&api_name)?;
1142 kill(pid, &signal)
1143 }
1144}