1use std::collections::VecDeque;
2use std::fs::OpenOptions;
3use std::io::Read;
4use std::io::Write;
5use std::path::PathBuf;
6use std::process::{Child, Command, Stdio};
7use std::sync::atomic::{AtomicI64, Ordering};
8use std::sync::{Arc, Condvar, Mutex};
9use std::thread;
10use std::time::{Duration, Instant};
11
12use thiserror::Error;
13
14pub mod console_detect;
15pub mod containment;
16#[cfg(feature = "originator-scan")]
17pub mod originator;
18pub mod pty;
19mod public_symbols;
20mod rust_debug;
21pub mod spawn;
22
23pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
24pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
25#[cfg(feature = "originator-scan")]
26pub use originator::{find_processes_by_originator, OriginatorProcessInfo};
27pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
28pub use spawn::{
29 spawn, spawn_daemon, spawn_daemon_with_clear_env, DaemonChild, SpawnStdio, SpawnedChild,
30 StdioSource,
31};
32
33#[macro_export]
34macro_rules! rp_rust_debug_scope {
35 ($label:expr) => {
36 let _running_process_rust_debug_scope =
37 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
38 };
39}
40
41const CHILD_PID_LOG_PATH_ENV: &str = "RUNNING_PROCESS_CHILD_PID_LOG_PATH";
42
43const DEFAULT_KILL_DRAIN_TIMEOUT: Duration = Duration::from_secs(2);
51const KILL_DRAIN_TIMEOUT_ENV: &str = "RUNNING_PROCESS_KILL_DRAIN_TIMEOUT_MS";
52
53fn kill_drain_deadline() -> Instant {
54 let timeout = std::env::var(KILL_DRAIN_TIMEOUT_ENV)
55 .ok()
56 .and_then(|raw| raw.trim().parse::<u64>().ok())
57 .map(Duration::from_millis)
58 .unwrap_or(DEFAULT_KILL_DRAIN_TIMEOUT);
59 Instant::now() + timeout
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum StreamKind {
64 Stdout,
65 Stderr,
66}
67
68impl StreamKind {
69 pub fn as_str(self) -> &'static str {
70 match self {
71 Self::Stdout => "stdout",
72 Self::Stderr => "stderr",
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct StreamEvent {
79 pub stream: StreamKind,
80 pub line: Vec<u8>,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum ReadStatus<T> {
85 Line(T),
86 Timeout,
87 Eof,
88}
89
90#[derive(Debug, Error)]
91pub enum ProcessError {
92 #[error("process already started")]
93 AlreadyStarted,
94 #[error("process is not running")]
95 NotRunning,
96 #[error("process stdin is not available")]
97 StdinUnavailable,
98 #[error("failed to spawn process: {0}")]
99 Spawn(std::io::Error),
100 #[error("failed to read process output: {0}")]
101 Io(std::io::Error),
102 #[error("process timed out")]
103 Timeout,
104}
105
106#[derive(Debug, Clone)]
107pub enum CommandSpec {
108 Shell(String),
109 Argv(Vec<String>),
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum StdinMode {
114 Inherit,
115 Piped,
116 Null,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum StderrMode {
121 Stdout,
122 Pipe,
123}
124
125#[cfg(unix)]
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum UnixSignal {
128 Interrupt,
129 Terminate,
130 Kill,
131}
132
133#[derive(Debug, Clone)]
134pub struct ProcessConfig {
135 pub command: CommandSpec,
136 pub cwd: Option<PathBuf>,
137 pub env: Option<Vec<(String, String)>>,
138 pub capture: bool,
139 pub stderr_mode: StderrMode,
140 pub creationflags: Option<u32>,
141 pub create_process_group: bool,
142 pub stdin_mode: StdinMode,
143 pub nice: Option<i32>,
144}
145
146#[derive(Default)]
147struct QueueState {
148 stdout_queue: VecDeque<Vec<u8>>,
149 stderr_queue: VecDeque<Vec<u8>>,
150 combined_queue: VecDeque<StreamEvent>,
151 stdout_history: VecDeque<Vec<u8>>,
152 stderr_history: VecDeque<Vec<u8>>,
153 combined_history: VecDeque<StreamEvent>,
154 stdout_history_bytes: usize,
155 stderr_history_bytes: usize,
156 combined_history_bytes: usize,
157 stdout_closed: bool,
158 stderr_closed: bool,
159}
160
161const RETURNCODE_NOT_SET: i64 = i64::MIN;
163
164struct SharedState {
165 queues: Mutex<QueueState>,
166 condvar: Condvar,
167 returncode: AtomicI64,
170}
171
172#[cfg(windows)]
173struct WindowsJobHandle(usize);
174
175#[cfg(windows)]
176impl Drop for WindowsJobHandle {
177 fn drop(&mut self) {
178 unsafe {
179 winapi::um::handleapi::CloseHandle(self.0 as winapi::shared::ntdef::HANDLE);
180 }
181 }
182}
183
184struct ChildState {
185 child: Child,
186 #[cfg(windows)]
187 _job: WindowsJobHandle,
188}
189
190#[cfg(windows)]
199#[derive(Default)]
200struct CapturePipeHandles {
201 stdout: Option<usize>,
202 stderr: Option<usize>,
203}
204
205impl SharedState {
206 fn new(capture: bool) -> Self {
207 let queues = QueueState {
208 stdout_closed: !capture,
209 stderr_closed: !capture,
210 ..QueueState::default()
211 };
212 Self {
213 queues: Mutex::new(queues),
214 condvar: Condvar::new(),
215 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
216 }
217 }
218}
219
220pub struct NativeProcess {
221 config: ProcessConfig,
222 child: Arc<Mutex<Option<ChildState>>>,
223 shared: Arc<SharedState>,
224 #[cfg(windows)]
225 capture_pipe_handles: Arc<Mutex<CapturePipeHandles>>,
226}
227
228impl NativeProcess {
229 pub fn new(config: ProcessConfig) -> Self {
230 Self {
231 shared: Arc::new(SharedState::new(config.capture)),
232 child: Arc::new(Mutex::new(None)),
233 config,
234 #[cfg(windows)]
235 capture_pipe_handles: Arc::new(Mutex::new(CapturePipeHandles::default())),
236 }
237 }
238
239 #[inline(never)]
241 pub fn start(&self) -> Result<(), ProcessError> {
242 public_symbols::rp_native_process_start_public(self)
243 }
244
245 fn start_impl(&self) -> Result<(), ProcessError> {
246 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::start");
247 let mut guard = self.child.lock().expect("child mutex poisoned");
248 if guard.is_some() {
249 return Err(ProcessError::AlreadyStarted);
250 }
251
252 let mut command = self.build_command();
253 match self.config.stdin_mode {
254 StdinMode::Inherit => {}
255 StdinMode::Piped => {
256 command.stdin(Stdio::piped());
257 }
258 StdinMode::Null => {
259 command.stdin(Stdio::null());
260 }
261 }
262 if self.config.capture {
263 command.stdout(Stdio::piped());
264 command.stderr(Stdio::piped());
265 }
266
267 let mut child = command.spawn().map_err(ProcessError::Spawn)?;
268 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
269 #[cfg(windows)]
270 let job = public_symbols::rp_assign_child_to_windows_kill_on_close_job_public(&child)
271 .map_err(ProcessError::Spawn)?;
272 if self.config.capture {
273 let stdout = child.stdout.take().expect("stdout pipe missing");
274 let stderr = child.stderr.take().expect("stderr pipe missing");
275 #[cfg(windows)]
276 {
277 use std::os::windows::io::AsRawHandle;
278 let mut handles = self
279 .capture_pipe_handles
280 .lock()
281 .expect("capture pipe handles mutex poisoned");
282 handles.stdout = Some(stdout.as_raw_handle() as usize);
283 handles.stderr = Some(stderr.as_raw_handle() as usize);
284 }
285 self.spawn_reader(
286 stdout,
287 StreamKind::Stdout,
288 StreamKind::Stdout,
289 self.pipe_done_callback(StreamKind::Stdout),
290 );
291 self.spawn_reader(
292 stderr,
293 StreamKind::Stderr,
294 match self.config.stderr_mode {
295 StderrMode::Stdout => StreamKind::Stdout,
296 StderrMode::Pipe => StreamKind::Stderr,
297 },
298 self.pipe_done_callback(StreamKind::Stderr),
299 );
300 }
301 *guard = Some(ChildState {
302 child,
303 #[cfg(windows)]
304 _job: job,
305 });
306 drop(guard);
307 self.spawn_exit_waiter();
308 Ok(())
309 }
310
311 fn spawn_exit_waiter(&self) {
314 let child = Arc::clone(&self.child);
315 let shared = Arc::clone(&self.shared);
316 thread::spawn(move || loop {
317 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
318 return;
319 }
320 {
321 let mut guard = child.lock().expect("child mutex poisoned");
322 if let Some(child_state) = guard.as_mut() {
323 match child_state.child.try_wait() {
324 Ok(Some(status)) => {
325 let code = exit_code(status);
326 shared.returncode.store(code as i64, Ordering::Release);
327 shared.condvar.notify_all();
328 return;
329 }
330 Ok(None) => {}
331 Err(_) => return,
332 }
333 } else {
334 return;
335 }
336 }
337 thread::sleep(Duration::from_millis(10));
338 });
339 }
340
341 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
342 let mut guard = self.child.lock().expect("child mutex poisoned");
343 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
344 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
345 use std::io::Write;
346 stdin.write_all(data).map_err(ProcessError::Io)?;
347 stdin.flush().map_err(ProcessError::Io)?;
348 drop(child.stdin.take());
349 Ok(())
350 }
351
352 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
357 let mut guard = self.child.lock().expect("child mutex poisoned");
358 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
359 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
360 use std::io::Write;
361 stdin.write_all(data).map_err(ProcessError::Io)?;
362 stdin.flush().map_err(ProcessError::Io)?;
363 Ok(())
364 }
365
366 pub fn close_stdin(&self) -> Result<(), ProcessError> {
369 let mut guard = self.child.lock().expect("child mutex poisoned");
370 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
371 drop(child.stdin.take());
372 Ok(())
373 }
374
375 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
376 if let Some(code) = self.returncode() {
378 return Ok(Some(code));
379 }
380 let mut guard = self.child.lock().expect("child mutex poisoned");
381 let Some(child_state) = guard.as_mut() else {
382 return Ok(self.returncode());
383 };
384 let child = &mut child_state.child;
385 let status = child.try_wait().map_err(ProcessError::Io)?;
386 if let Some(status) = status {
387 let code = exit_code(status);
388 self.set_returncode(code);
389 return Ok(Some(code));
390 }
391 Ok(None)
392 }
393
394 #[inline(never)]
396 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
397 public_symbols::rp_native_process_wait_public(self, timeout)
398 }
399
400 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
401 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::wait");
402 if self.child.lock().expect("child mutex poisoned").is_none() {
403 return self.returncode().ok_or(ProcessError::NotRunning);
404 }
405 if let Some(code) = self.returncode() {
407 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
408 return Ok(code);
409 }
410 let start = Instant::now();
411 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
412 loop {
413 let rc = self.shared.returncode.load(Ordering::Acquire);
415 if rc != RETURNCODE_NOT_SET {
416 drop(guard);
417 let code = rc as i32;
418 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
419 return Ok(code);
420 }
421 if let Some(limit) = timeout {
422 let elapsed = start.elapsed();
423 if elapsed >= limit {
424 return Err(ProcessError::Timeout);
425 }
426 let remaining = limit - elapsed;
427 let wait_time = remaining.min(Duration::from_millis(50));
429 guard = self
430 .shared
431 .condvar
432 .wait_timeout(guard, wait_time)
433 .expect("queue mutex poisoned")
434 .0;
435 } else {
436 guard = self
438 .shared
439 .condvar
440 .wait_timeout(guard, Duration::from_millis(50))
441 .expect("queue mutex poisoned")
442 .0;
443 }
444 }
445 }
446
447 #[inline(never)]
449 pub fn kill(&self) -> Result<(), ProcessError> {
450 public_symbols::rp_native_process_kill_public(self)
451 }
452
453 fn kill_impl(&self) -> Result<(), ProcessError> {
454 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::kill");
455 {
456 let mut guard = self.child.lock().expect("child mutex poisoned");
457 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
458 child.kill().map_err(ProcessError::Io)?;
459 let status = child.wait().map_err(ProcessError::Io)?;
460 self.set_returncode(exit_code(status));
461 }
462 #[cfg(windows)]
470 self.cancel_capture_io();
471 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
484 self,
485 kill_drain_deadline(),
486 );
487 Ok(())
488 }
489
490 pub fn terminate(&self) -> Result<(), ProcessError> {
491 self.kill()
492 }
493
494 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
509 #[cfg(unix)]
510 {
511 if !self.config.create_process_group {
512 return Ok(());
513 }
514 let pid = match self.pid() {
515 Some(p) => p as i32,
516 None => return Err(ProcessError::NotRunning),
517 };
518 let result = unsafe { libc::kill(-pid, libc::SIGTERM) };
519 if result != 0 {
520 let err = std::io::Error::last_os_error();
521 if err.raw_os_error() != Some(libc::ESRCH) {
522 return Err(ProcessError::Io(err));
523 }
524 }
525 Ok(())
526 }
527 #[cfg(windows)]
528 {
529 if !self.config.create_process_group {
530 return Ok(());
535 }
536 let pid = match self.pid() {
537 Some(p) => p,
538 None => return Err(ProcessError::NotRunning),
539 };
540 let ok = unsafe {
544 winapi::um::wincon::GenerateConsoleCtrlEvent(
545 winapi::um::wincon::CTRL_BREAK_EVENT,
546 pid,
547 )
548 };
549 if ok == 0 {
550 let err = std::io::Error::last_os_error();
551 if err.raw_os_error() != Some(6) {
557 return Err(ProcessError::Io(err));
558 }
559 }
560 Ok(())
561 }
562 }
563
564 #[inline(never)]
566 pub fn close(&self) -> Result<(), ProcessError> {
567 public_symbols::rp_native_process_close_public(self)
568 }
569
570 fn close_impl(&self) -> Result<(), ProcessError> {
571 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::close");
572 if self.child.lock().expect("child mutex poisoned").is_none() {
573 return Ok(());
574 }
575 if self.poll()?.is_none() {
576 self.kill()?;
577 } else {
578 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
579 }
580 Ok(())
581 }
582
583 pub fn pid(&self) -> Option<u32> {
584 self.child
585 .lock()
586 .expect("child mutex poisoned")
587 .as_ref()
588 .map(|state| state.child.id())
589 }
590
591 pub fn returncode(&self) -> Option<i32> {
592 let v = self.shared.returncode.load(Ordering::Acquire);
593 if v == RETURNCODE_NOT_SET {
594 None
595 } else {
596 Some(v as i32)
597 }
598 }
599
600 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
601 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
602 return false;
603 }
604 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
605 match stream {
606 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
607 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
608 }
609 }
610
611 pub fn has_pending_combined(&self) -> bool {
612 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
613 !guard.combined_queue.is_empty()
614 }
615
616 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
617 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
618 return Vec::new();
619 }
620 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
621 let queue = match stream {
622 StreamKind::Stdout => &mut guard.stdout_queue,
623 StreamKind::Stderr => &mut guard.stderr_queue,
624 };
625 queue.drain(..).collect()
626 }
627
628 pub fn drain_combined(&self) -> Vec<StreamEvent> {
629 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
630 guard.combined_queue.drain(..).collect()
631 }
632
633 pub fn read_stream(
634 &self,
635 stream: StreamKind,
636 timeout: Option<Duration>,
637 ) -> ReadStatus<Vec<u8>> {
638 let deadline = timeout.map(|limit| Instant::now() + limit);
639 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
640
641 loop {
642 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
643 return ReadStatus::Eof;
644 }
645
646 let queue = match stream {
647 StreamKind::Stdout => &mut guard.stdout_queue,
648 StreamKind::Stderr => &mut guard.stderr_queue,
649 };
650 if let Some(line) = queue.pop_front() {
651 return ReadStatus::Line(line);
652 }
653
654 let closed = match stream {
655 StreamKind::Stdout => {
656 if self.config.stderr_mode == StderrMode::Stdout {
657 guard.stdout_closed && guard.stderr_closed
658 } else {
659 guard.stdout_closed
660 }
661 }
662 StreamKind::Stderr => guard.stderr_closed,
663 };
664 if closed {
665 return ReadStatus::Eof;
666 }
667
668 match deadline {
669 Some(deadline) => {
670 let now = Instant::now();
671 if now >= deadline {
672 return ReadStatus::Timeout;
673 }
674 let wait = deadline.saturating_duration_since(now);
675 let result = self
676 .shared
677 .condvar
678 .wait_timeout(guard, wait)
679 .expect("queue mutex poisoned");
680 guard = result.0;
681 if result.1.timed_out() {
682 return ReadStatus::Timeout;
683 }
684 }
685 None => {
686 guard = self
687 .shared
688 .condvar
689 .wait(guard)
690 .expect("queue mutex poisoned");
691 }
692 }
693 }
694 }
695
696 #[inline(never)]
698 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
699 public_symbols::rp_native_process_read_combined_public(self, timeout)
700 }
701
702 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
703 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::read_combined");
704 let deadline = timeout.map(|limit| Instant::now() + limit);
705 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
706
707 loop {
708 if let Some(event) = guard.combined_queue.pop_front() {
709 return ReadStatus::Line(event);
710 }
711 if guard.stdout_closed && guard.stderr_closed {
712 return ReadStatus::Eof;
713 }
714
715 match deadline {
716 Some(deadline) => {
717 let now = Instant::now();
718 if now >= deadline {
719 return ReadStatus::Timeout;
720 }
721 let wait = deadline.saturating_duration_since(now);
722 let result = self
723 .shared
724 .condvar
725 .wait_timeout(guard, wait)
726 .expect("queue mutex poisoned");
727 guard = result.0;
728 if result.1.timed_out() {
729 return ReadStatus::Timeout;
730 }
731 }
732 None => {
733 guard = self
734 .shared
735 .condvar
736 .wait(guard)
737 .expect("queue mutex poisoned");
738 }
739 }
740 }
741 }
742
743 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
744 self.shared
745 .queues
746 .lock()
747 .expect("queue mutex poisoned")
748 .stdout_history
749 .clone()
750 .into_iter()
751 .collect()
752 }
753
754 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
755 if self.config.stderr_mode == StderrMode::Stdout {
756 return Vec::new();
757 }
758 self.shared
759 .queues
760 .lock()
761 .expect("queue mutex poisoned")
762 .stderr_history
763 .clone()
764 .into_iter()
765 .collect()
766 }
767
768 pub fn captured_combined(&self) -> Vec<StreamEvent> {
769 self.shared
770 .queues
771 .lock()
772 .expect("queue mutex poisoned")
773 .combined_history
774 .clone()
775 .into_iter()
776 .collect()
777 }
778
779 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
780 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
781 return 0;
782 }
783 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
784 match stream {
785 StreamKind::Stdout => guard.stdout_history_bytes,
786 StreamKind::Stderr => guard.stderr_history_bytes,
787 }
788 }
789
790 pub fn captured_combined_bytes(&self) -> usize {
791 self.shared
792 .queues
793 .lock()
794 .expect("queue mutex poisoned")
795 .combined_history_bytes
796 }
797
798 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
799 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
800 return 0;
801 }
802 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
803 match stream {
804 StreamKind::Stdout => {
805 let released = guard.stdout_history_bytes;
806 guard.stdout_history.clear();
807 guard.stdout_history_bytes = 0;
808 released
809 }
810 StreamKind::Stderr => {
811 let released = guard.stderr_history_bytes;
812 guard.stderr_history.clear();
813 guard.stderr_history_bytes = 0;
814 released
815 }
816 }
817 }
818
819 pub fn clear_captured_combined(&self) -> usize {
820 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
821 let released = guard.combined_history_bytes;
822 guard.combined_history.clear();
823 guard.combined_history_bytes = 0;
824 released
825 }
826
827 fn build_command(&self) -> Command {
828 let mut command = match &self.config.command {
829 CommandSpec::Shell(command) => shell_command(command),
830 CommandSpec::Argv(argv) => {
831 let mut command = Command::new(&argv[0]);
832 if argv.len() > 1 {
833 command.args(&argv[1..]);
834 }
835 command
836 }
837 };
838 if let Some(cwd) = &self.config.cwd {
839 command.current_dir(cwd);
840 }
841 if let Some(env) = &self.config.env {
842 command.env_clear();
843 command.envs(env.iter().map(|(k, v)| (k, v)));
844 }
845 #[cfg(windows)]
846 {
847 use std::os::windows::process::CommandExt;
848
849 const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
854 let extra = if self.config.create_process_group {
855 CREATE_NEW_PROCESS_GROUP
856 } else {
857 0
858 };
859 let flags = self.config.creationflags.unwrap_or(0)
860 | extra
861 | windows_priority_flags(self.config.nice);
862 if flags != 0 {
863 command.creation_flags(flags);
864 }
865 }
866 #[cfg(unix)]
867 {
868 let create_process_group = self.config.create_process_group;
869 let nice = self.config.nice;
870
871 if create_process_group || nice.is_some() {
872 use std::os::unix::process::CommandExt;
873
874 unsafe {
875 command.pre_exec(move || {
876 if create_process_group && libc::setpgid(0, 0) == -1 {
877 return Err(std::io::Error::last_os_error());
878 }
879 if let Some(nice) = nice {
880 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
881 if result == -1 {
882 return Err(std::io::Error::last_os_error());
883 }
884 }
885 Ok(())
886 });
887 }
888 }
889 }
890 command
891 }
892
893 fn spawn_reader<R>(
894 &self,
895 pipe: R,
896 source_stream: StreamKind,
897 visible_stream: StreamKind,
898 on_pipe_done: Box<dyn FnOnce() + Send>,
899 ) where
900 R: Read + Send + 'static,
901 {
902 let shared = Arc::clone(&self.shared);
903 thread::spawn(move || {
904 let mut reader = pipe;
905 let mut chunk = vec![0_u8; 65536];
906 let mut pending = Vec::new();
907
908 loop {
909 match reader.read(&mut chunk) {
910 Ok(0) => break,
911 Ok(n) => {
912 let lines = feed_chunk(&mut pending, &chunk[..n]);
913 emit_lines(&shared, visible_stream, lines);
914 }
915 Err(_) => break,
916 }
917 }
918
919 if !pending.is_empty() {
920 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
921 }
922
923 on_pipe_done();
928 drop(reader);
929
930 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
931 match source_stream {
932 StreamKind::Stdout => guard.stdout_closed = true,
933 StreamKind::Stderr => guard.stderr_closed = true,
934 }
935 shared.condvar.notify_all();
936 });
937 }
938
939 #[cfg(windows)]
940 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
941 let handles = Arc::clone(&self.capture_pipe_handles);
942 Box::new(move || {
943 let mut guard = handles
944 .lock()
945 .expect("capture pipe handles mutex poisoned");
946 match stream {
947 StreamKind::Stdout => guard.stdout = None,
948 StreamKind::Stderr => guard.stderr = None,
949 }
950 })
951 }
952
953 #[cfg(not(windows))]
954 fn pipe_done_callback(&self, _stream: StreamKind) -> Box<dyn FnOnce() + Send> {
955 Box::new(|| {})
956 }
957
958 #[cfg(windows)]
964 fn cancel_capture_io(&self) {
965 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::cancel_capture_io");
966 use winapi::shared::ntdef::HANDLE;
967 use winapi::um::ioapiset::CancelIoEx;
968 let guard = self
969 .capture_pipe_handles
970 .lock()
971 .expect("capture pipe handles mutex poisoned");
972 if let Some(h) = guard.stdout {
973 unsafe {
980 CancelIoEx(h as HANDLE, std::ptr::null_mut());
981 }
982 }
983 if let Some(h) = guard.stderr {
984 unsafe {
985 CancelIoEx(h as HANDLE, std::ptr::null_mut());
986 }
987 }
988 }
989
990 fn set_returncode(&self, code: i32) {
991 self.shared.returncode.store(code as i64, Ordering::Release);
992 self.shared.condvar.notify_all();
993 }
994
995 fn wait_for_capture_completion_impl(&self) {
996 crate::rp_rust_debug_scope!(
997 "running_process_core::NativeProcess::wait_for_capture_completion"
998 );
999 if !self.config.capture {
1000 return;
1001 }
1002
1003 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1004 while !(guard.stdout_closed && guard.stderr_closed) {
1005 guard = self
1006 .shared
1007 .condvar
1008 .wait(guard)
1009 .expect("queue mutex poisoned");
1010 }
1011 }
1012
1013 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1021 crate::rp_rust_debug_scope!(
1022 "running_process_core::NativeProcess::wait_for_capture_completion_with_deadline"
1023 );
1024 if !self.config.capture {
1025 return true;
1026 }
1027
1028 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1029 while !(guard.stdout_closed && guard.stderr_closed) {
1030 let now = Instant::now();
1031 if now >= deadline {
1032 guard.stdout_closed = true;
1033 guard.stderr_closed = true;
1034 self.shared.condvar.notify_all();
1035 return false;
1036 }
1037 let (next_guard, result) = self
1038 .shared
1039 .condvar
1040 .wait_timeout(guard, deadline - now)
1041 .expect("queue mutex poisoned");
1042 guard = next_guard;
1043 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1044 guard.stdout_closed = true;
1045 guard.stderr_closed = true;
1046 self.shared.condvar.notify_all();
1047 return false;
1048 }
1049 }
1050 true
1051 }
1052}
1053
1054#[cfg(unix)]
1055pub fn unix_set_priority(pid: u32, nice: i32) -> Result<(), std::io::Error> {
1056 let result = unsafe { libc::setpriority(libc::PRIO_PROCESS, pid, nice) };
1057 if result == -1 {
1058 return Err(std::io::Error::last_os_error());
1059 }
1060 Ok(())
1061}
1062
1063#[cfg(unix)]
1064pub fn unix_signal_process(pid: u32, signal: UnixSignal) -> Result<(), std::io::Error> {
1065 let result = unsafe { libc::kill(pid as i32, unix_signal_raw(signal)) };
1066 if result == -1 {
1067 return Err(std::io::Error::last_os_error());
1068 }
1069 Ok(())
1070}
1071
1072#[cfg(unix)]
1073pub fn unix_signal_process_group(pid: i32, signal: UnixSignal) -> Result<(), std::io::Error> {
1074 let result = unsafe { libc::killpg(pid, unix_signal_raw(signal)) };
1075 if result == -1 {
1076 return Err(std::io::Error::last_os_error());
1077 }
1078 Ok(())
1079}
1080
1081fn log_spawned_child_pid(pid: u32) -> Result<(), std::io::Error> {
1082 let Some(path) = std::env::var_os(CHILD_PID_LOG_PATH_ENV) else {
1083 return Ok(());
1084 };
1085
1086 let mut file = OpenOptions::new().create(true).append(true).open(path)?;
1087 file.write_all(format!("{pid}\n").as_bytes())?;
1088 file.flush()?;
1089 Ok(())
1090}
1091
1092#[cfg(windows)]
1093fn assign_child_to_windows_kill_on_close_job_impl(
1094 child: &Child,
1095) -> Result<WindowsJobHandle, std::io::Error> {
1096 crate::rp_rust_debug_scope!("running_process_core::assign_child_to_windows_kill_on_close_job");
1097 use std::mem::zeroed;
1098 use std::os::windows::io::AsRawHandle;
1099
1100 use winapi::shared::minwindef::FALSE;
1101 use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
1102 use winapi::um::jobapi2::{
1103 AssignProcessToJobObject, CreateJobObjectW, SetInformationJobObject,
1104 };
1105 use winapi::um::winnt::{
1106 JobObjectExtendedLimitInformation, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
1107 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
1108 };
1109
1110 let handle = child.as_raw_handle();
1111 let job = unsafe { CreateJobObjectW(std::ptr::null_mut(), std::ptr::null()) };
1112 if job.is_null() || job == INVALID_HANDLE_VALUE {
1113 return Err(std::io::Error::last_os_error());
1114 }
1115
1116 let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() };
1117 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
1118 let ok = unsafe {
1119 SetInformationJobObject(
1120 job,
1121 JobObjectExtendedLimitInformation,
1122 (&mut info as *mut JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(),
1123 std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
1124 )
1125 };
1126 if ok == FALSE {
1127 let err = std::io::Error::last_os_error();
1128 unsafe { CloseHandle(job) };
1129 return Err(err);
1130 }
1131
1132 let ok = unsafe { AssignProcessToJobObject(job, handle.cast()) };
1133 if ok == FALSE {
1134 let err = std::io::Error::last_os_error();
1135 unsafe { CloseHandle(job) };
1136 return Err(err);
1137 }
1138
1139 Ok(WindowsJobHandle(job as usize))
1140}
1141
1142fn feed_chunk(pending: &mut Vec<u8>, chunk: &[u8]) -> Vec<Vec<u8>> {
1143 let mut lines = Vec::new();
1144 let mut start = 0;
1145 let mut index = 0;
1146
1147 while index < chunk.len() {
1148 if chunk[index] == b'\n' {
1149 let end = if index > start && chunk[index - 1] == b'\r' {
1150 index - 1
1151 } else {
1152 index
1153 };
1154 pending.extend_from_slice(&chunk[start..end]);
1155 if !pending.is_empty() {
1156 lines.push(std::mem::take(pending));
1157 }
1158 start = index + 1;
1159 }
1160 index += 1;
1161 }
1162
1163 pending.extend_from_slice(&chunk[start..]);
1164 lines
1165}
1166
1167fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1168 if lines.is_empty() {
1169 return;
1170 }
1171 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1172 for line in lines {
1173 let line_len = line.len();
1174 match stream {
1175 StreamKind::Stdout => {
1176 guard.stdout_history_bytes += line_len;
1177 guard.stdout_history.push_back(line.clone());
1178 guard.stdout_queue.push_back(line.clone());
1179 }
1180 StreamKind::Stderr => {
1181 guard.stderr_history_bytes += line_len;
1182 guard.stderr_history.push_back(line.clone());
1183 guard.stderr_queue.push_back(line.clone());
1184 }
1185 }
1186 let event = StreamEvent { stream, line };
1187 guard.combined_history_bytes += line_len;
1188 guard.combined_history.push_back(event.clone());
1189 guard.combined_queue.push_back(event);
1190 }
1191 shared.condvar.notify_all();
1192}
1193
1194fn shell_command(command: &str) -> Command {
1195 #[cfg(windows)]
1196 {
1197 use std::os::windows::process::CommandExt;
1198
1199 let mut cmd = Command::new("cmd");
1200 cmd.raw_arg("/D /S /C \"");
1201 cmd.raw_arg(command);
1202 cmd.raw_arg("\"");
1203 cmd
1204 }
1205 #[cfg(not(windows))]
1206 {
1207 let mut cmd = Command::new("sh");
1208 cmd.arg("-lc").arg(command);
1209 cmd
1210 }
1211}
1212
1213fn exit_code(status: std::process::ExitStatus) -> i32 {
1214 #[cfg(unix)]
1215 {
1216 use std::os::unix::process::ExitStatusExt;
1217 status
1218 .code()
1219 .unwrap_or_else(|| -status.signal().unwrap_or(1))
1220 }
1221 #[cfg(not(unix))]
1222 {
1223 status.code().unwrap_or(1)
1224 }
1225}
1226
1227#[cfg(unix)]
1228fn unix_signal_raw(signal: UnixSignal) -> i32 {
1229 match signal {
1230 UnixSignal::Interrupt => libc::SIGINT,
1231 UnixSignal::Terminate => libc::SIGTERM,
1232 UnixSignal::Kill => libc::SIGKILL,
1233 }
1234}
1235
1236#[cfg(windows)]
1237fn windows_priority_flags(nice: Option<i32>) -> u32 {
1238 const IDLE_PRIORITY_CLASS: u32 = 0x0000_0040;
1239 const BELOW_NORMAL_PRIORITY_CLASS: u32 = 0x0000_4000;
1240 const ABOVE_NORMAL_PRIORITY_CLASS: u32 = 0x0000_8000;
1241 const HIGH_PRIORITY_CLASS: u32 = 0x0000_0080;
1242
1243 match nice {
1244 Some(value) if value >= 15 => IDLE_PRIORITY_CLASS,
1245 Some(value) if value >= 1 => BELOW_NORMAL_PRIORITY_CLASS,
1246 Some(value) if value <= -15 => HIGH_PRIORITY_CLASS,
1247 Some(value) if value <= -1 => ABOVE_NORMAL_PRIORITY_CLASS,
1248 _ => 0,
1249 }
1250}
1251#[cfg(test)]
1252mod tests;