1use std::collections::VecDeque;
10use std::io::Read;
11use std::process::{Child, Command, Stdio};
12use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
13use std::sync::{Arc, Condvar, Mutex};
14use std::thread;
15use std::time::{Duration, Instant};
16
17use crate::observer::ObserverEmitter;
18
19pub mod console_detect;
20pub mod containment;
21pub mod environment;
22mod helpers;
23pub mod observer;
28#[cfg(feature = "originator-scan")]
29pub mod originator;
30#[cfg(feature = "client")]
35pub mod proto {
37 #[allow(missing_docs)]
39 pub mod daemon {
40 include!(concat!(env!("OUT_DIR"), "/running_process.daemon.v1.rs"));
41 }
42}
43
44#[cfg(feature = "client")]
45pub mod client;
46
47#[cfg(feature = "client")]
52pub mod broker;
53
54#[cfg(feature = "client")]
60pub mod maintenance;
61
62#[cfg(feature = "client")]
63pub mod cleanup;
64
65#[cfg(feature = "client")]
69pub mod boot_autostart;
70
71#[cfg(feature = "client")]
76pub mod runpm_config;
77
78#[cfg(feature = "test-support")]
83pub mod test_support;
84
85#[cfg(feature = "telemetry")]
88#[path = "daemon/telemetry.rs"]
89pub mod telemetry;
90
91#[cfg(feature = "daemon")]
94pub mod daemon;
96pub mod process_tree;
97#[cfg(feature = "pty")]
98pub mod pty;
100mod public_symbols;
101mod rust_debug;
102pub mod spawn;
103pub mod systemd_killmode;
104pub mod terminal_graphics;
105mod types;
106#[cfg(unix)]
107mod unix;
108#[cfg(windows)]
109mod windows;
110
111pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
112pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
113pub use observer::{
114 CapabilitySupport, CategoryCapability, EventCategory, ObserverCapabilities, ObserverConfig,
115 ObserverEvent, ObserverEventKind, ObserverSubscriber,
116};
117#[cfg(feature = "originator-scan")]
118pub use originator::{find_processes_by_originator, OriginatorProcessInfo};
119pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
120pub use spawn::{
121 spawn, spawn_daemon, spawn_daemon_with_clear_env, spawn_daemon_with_env_policy,
122 spawn_with_env_policy, DaemonChild, EnvironmentPolicy, SpawnStdio, SpawnedChild, StdioSource,
123};
124pub use terminal_graphics::{
125 current_terminal_capabilities, current_terminal_capabilities_with_timeout,
126 detect_terminal_capabilities, CapabilityStatus, EvidenceStrength, GraphicsCapability,
127 GraphicsProtocol, TerminalCapabilities, TerminalCapabilityInput, TerminalGraphicsCapabilities,
128 TerminalProbeEvidence,
129};
130pub use types::{
131 CommandSpec, ProcessConfig, ProcessError, ReadStatus, RunOutput, StderrMode, StdinMode,
132 StreamEvent, StreamKind,
133};
134
135pub(crate) use helpers::{exit_code, feed_chunk, kill_drain_deadline, log_spawned_child_pid};
136#[cfg(unix)]
137pub use unix::{unix_set_priority, unix_signal_process, unix_signal_process_group, UnixSignal};
138#[cfg(windows)]
139pub(crate) use windows::{
140 assign_child_to_windows_kill_on_close_job_impl, windows_creation_flags, CapturePipeHandles,
141 WindowsJobHandle,
142};
143
144#[macro_export]
145macro_rules! rp_rust_debug_scope {
147 ($label:expr) => {
148 let _running_process_rust_debug_scope =
149 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
150 };
151}
152
153#[derive(Default)]
154struct QueueState {
155 stdout_queue: VecDeque<Vec<u8>>,
156 stderr_queue: VecDeque<Vec<u8>>,
157 combined_queue: VecDeque<StreamEvent>,
158 stdout_history: VecDeque<Vec<u8>>,
159 stderr_history: VecDeque<Vec<u8>>,
160 combined_history: VecDeque<StreamEvent>,
161 stdout_raw: Vec<u8>,
162 stderr_raw: Vec<u8>,
163 stdout_history_bytes: usize,
164 stderr_history_bytes: usize,
165 combined_history_bytes: usize,
166 stdout_closed: bool,
167 stderr_closed: bool,
168}
169
170const RETURNCODE_NOT_SET: i64 = i64::MIN;
172
173struct SharedState {
174 queues: Mutex<QueueState>,
175 condvar: Condvar,
176 returncode: AtomicI64,
179 observer: Option<ObserverEmitter>,
184 observer_exit_emitted: AtomicBool,
187}
188
189struct ChildState {
190 child: Child,
191 #[cfg(windows)]
192 _job: WindowsJobHandle,
193}
194
195impl SharedState {
196 fn new(capture: bool) -> Self {
197 Self::with_observer(capture, None)
198 }
199
200 fn with_observer(capture: bool, observer: Option<ObserverEmitter>) -> Self {
201 let queues = QueueState {
202 stdout_closed: !capture,
203 stderr_closed: !capture,
204 ..QueueState::default()
205 };
206 Self {
207 queues: Mutex::new(queues),
208 condvar: Condvar::new(),
209 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
210 observer,
211 observer_exit_emitted: AtomicBool::new(false),
212 }
213 }
214
215 fn emit_exited(&self, pid: u32, exit_code: i32) {
218 let Some(emitter) = self.observer.as_ref() else {
219 return;
220 };
221 if self
222 .observer_exit_emitted
223 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
224 .is_ok()
225 {
226 emitter.emit_exited(pid, exit_code);
227 }
228 }
229}
230
231pub struct NativeProcess {
238 config: ProcessConfig,
239 child: Arc<Mutex<Option<ChildState>>>,
240 shared: Arc<SharedState>,
241 #[cfg(windows)]
242 capture_pipe_handles: Arc<Mutex<CapturePipeHandles>>,
243}
244
245impl NativeProcess {
246 pub fn new(config: ProcessConfig) -> Self {
252 Self::new_with_observer(config, None)
253 }
254
255 pub fn with_observer(
268 config: ProcessConfig,
269 observer: crate::observer::ObserverConfig,
270 ) -> (Self, ObserverSubscriber) {
271 let (emitter, subscriber) = ObserverEmitter::new(observer);
272 let process = Self::new_with_observer(config, Some(emitter));
273 (process, subscriber)
274 }
275
276 fn new_with_observer(config: ProcessConfig, observer: Option<ObserverEmitter>) -> Self {
277 let shared = match observer {
278 None => SharedState::new(config.capture),
280 Some(emitter) => SharedState::with_observer(config.capture, Some(emitter)),
281 };
282 Self {
283 shared: Arc::new(shared),
284 child: Arc::new(Mutex::new(None)),
285 config,
286 #[cfg(windows)]
287 capture_pipe_handles: Arc::new(Mutex::new(CapturePipeHandles::default())),
288 }
289 }
290
291 #[inline(never)]
293 pub fn start(&self) -> Result<(), ProcessError> {
298 public_symbols::rp_native_process_start_public(self)
299 }
300
301 fn start_impl(&self) -> Result<(), ProcessError> {
302 crate::rp_rust_debug_scope!("running_process::NativeProcess::start");
303 let mut guard = self.child.lock().expect("child mutex poisoned");
304 if guard.is_some() {
305 return Err(ProcessError::AlreadyStarted);
306 }
307
308 let mut command = self.build_command();
309 match self.config.stdin_mode {
310 StdinMode::Inherit => {}
311 StdinMode::Piped => {
312 command.stdin(Stdio::piped());
313 }
314 StdinMode::Null => {
315 command.stdin(Stdio::null());
316 }
317 }
318 if self.config.capture {
319 command.stdout(Stdio::piped());
320 command.stderr(Stdio::piped());
321 }
322
323 let mut child = command.spawn().map_err(ProcessError::Spawn)?;
324 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
325 if let Some(emitter) = self.shared.observer.as_ref() {
328 emitter.emit_started(child.id());
329 }
330 #[cfg(windows)]
335 let job = {
336 let descendant_sink = self
337 .shared
338 .observer
339 .as_ref()
340 .and_then(|e| e.descendant_sink());
341 let direct_pid = child.id();
342 public_symbols::rp_assign_child_to_windows_kill_on_close_job_with_observer_public(
343 &child,
344 descendant_sink,
345 direct_pid,
346 )
347 .map_err(ProcessError::Spawn)?
348 };
349 #[cfg(target_os = "linux")]
353 {
354 if let Some(emitter) = self.shared.observer.as_ref() {
355 if let Some(sink) = emitter.descendant_sink() {
356 crate::observer::descendants_linux::enable_subreaper();
357 crate::observer::descendants_linux::spawn_pump(child.id(), sink);
358 }
359 }
360 }
361 #[cfg(target_os = "macos")]
365 {
366 if let Some(emitter) = self.shared.observer.as_ref() {
367 if let Some(sink) = emitter.descendant_sink() {
368 crate::observer::descendants_macos::spawn_pump(child.id(), sink);
369 }
370 }
371 }
372 if self.config.capture {
373 let stdout = child.stdout.take().expect("stdout pipe missing");
374 let stderr = child.stderr.take().expect("stderr pipe missing");
375 #[cfg(windows)]
376 {
377 use std::os::windows::io::AsRawHandle;
378 let mut handles = self
379 .capture_pipe_handles
380 .lock()
381 .expect("capture pipe handles mutex poisoned");
382 handles.stdout = Some(stdout.as_raw_handle() as usize);
383 handles.stderr = Some(stderr.as_raw_handle() as usize);
384 }
385 self.spawn_reader(
386 stdout,
387 StreamKind::Stdout,
388 StreamKind::Stdout,
389 self.pipe_done_callback(StreamKind::Stdout),
390 );
391 self.spawn_reader(
392 stderr,
393 StreamKind::Stderr,
394 match self.config.stderr_mode {
395 StderrMode::Stdout => StreamKind::Stdout,
396 StderrMode::Pipe => StreamKind::Stderr,
397 },
398 self.pipe_done_callback(StreamKind::Stderr),
399 );
400 }
401 *guard = Some(ChildState {
402 child,
403 #[cfg(windows)]
404 _job: job,
405 });
406 drop(guard);
407 self.spawn_exit_waiter();
408 Ok(())
409 }
410
411 fn spawn_exit_waiter(&self) {
414 let child = Arc::clone(&self.child);
415 let shared = Arc::clone(&self.shared);
416 let capture = self.config.capture;
417 #[cfg(windows)]
418 let capture_pipe_handles = Arc::clone(&self.capture_pipe_handles);
419 thread::spawn(move || {
420 loop {
421 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
422 return;
423 }
424 let exited = {
425 let mut guard = child.lock().expect("child mutex poisoned");
426 if let Some(child_state) = guard.as_mut() {
427 let pid = child_state.child.id();
428 match child_state.child.try_wait() {
429 Ok(Some(status)) => {
430 let code = exit_code(status);
431 shared.returncode.store(code as i64, Ordering::Release);
432 shared.emit_exited(pid, code);
436 shared.condvar.notify_all();
437 true
438 }
439 Ok(None) => false,
440 Err(_) => return,
441 }
442 } else {
443 return;
444 }
445 };
446 if exited {
447 if capture {
461 let drained = finalize_capture_completion(&shared, kill_drain_deadline());
462 #[cfg(windows)]
463 if !drained {
464 cancel_capture_pipe_io(&capture_pipe_handles);
465 }
466 #[cfg(not(windows))]
467 {
468 let _ = drained;
469 }
470 }
471 return;
472 }
473 thread::sleep(Duration::from_millis(10));
479 }
480 });
481 }
482
483 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
485 let mut guard = self.child.lock().expect("child mutex poisoned");
486 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
487 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
488 use std::io::Write;
489 stdin.write_all(data).map_err(ProcessError::Io)?;
490 stdin.flush().map_err(ProcessError::Io)?;
491 drop(child.stdin.take());
492 Ok(())
493 }
494
495 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
500 let mut guard = self.child.lock().expect("child mutex poisoned");
501 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
502 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
503 use std::io::Write;
504 stdin.write_all(data).map_err(ProcessError::Io)?;
505 stdin.flush().map_err(ProcessError::Io)?;
506 Ok(())
507 }
508
509 pub fn close_stdin(&self) -> Result<(), ProcessError> {
512 let mut guard = self.child.lock().expect("child mutex poisoned");
513 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
514 drop(child.stdin.take());
515 Ok(())
516 }
517
518 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
522 if let Some(code) = self.returncode() {
524 return Ok(Some(code));
525 }
526 let mut guard = self.child.lock().expect("child mutex poisoned");
527 let Some(child_state) = guard.as_mut() else {
528 return Ok(self.returncode());
529 };
530 let pid = child_state.child.id();
531 let child = &mut child_state.child;
532 let status = child.try_wait().map_err(ProcessError::Io)?;
533 if let Some(status) = status {
534 let code = exit_code(status);
535 self.set_returncode(code);
536 self.shared.emit_exited(pid, code);
537 return Ok(Some(code));
538 }
539 Ok(None)
540 }
541
542 #[inline(never)]
544 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
549 public_symbols::rp_native_process_wait_public(self, timeout)
550 }
551
552 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
553 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
554 if self.child.lock().expect("child mutex poisoned").is_none() {
555 return self.returncode().ok_or(ProcessError::NotRunning);
556 }
557 if let Some(code) = self.returncode() {
559 self.finish_capture_drain();
560 return Ok(code);
561 }
562 let start = Instant::now();
563 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
564 loop {
565 let rc = self.shared.returncode.load(Ordering::Acquire);
567 if rc != RETURNCODE_NOT_SET {
568 drop(guard);
569 let code = rc as i32;
570 self.finish_capture_drain();
571 return Ok(code);
572 }
573 if let Some(limit) = timeout {
574 let elapsed = start.elapsed();
575 if elapsed >= limit {
576 return Err(ProcessError::Timeout);
577 }
578 let remaining = limit - elapsed;
579 let wait_time = remaining.min(Duration::from_millis(50));
581 guard = self
582 .shared
583 .condvar
584 .wait_timeout(guard, wait_time)
585 .expect("queue mutex poisoned")
586 .0;
587 } else {
588 guard = self
590 .shared
591 .condvar
592 .wait_timeout(guard, Duration::from_millis(50))
593 .expect("queue mutex poisoned")
594 .0;
595 }
596 }
597 }
598
599 #[inline(never)]
601 pub fn kill(&self) -> Result<(), ProcessError> {
603 public_symbols::rp_native_process_kill_public(self)
604 }
605
606 fn kill_impl(&self) -> Result<(), ProcessError> {
607 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
608 {
609 let mut guard = self.child.lock().expect("child mutex poisoned");
610 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
611 let pid = child.id();
612 child.kill().map_err(ProcessError::Io)?;
613 let status = child.wait().map_err(ProcessError::Io)?;
614 let code = exit_code(status);
615 self.set_returncode(code);
616 self.shared.emit_exited(pid, code);
619 }
620 #[cfg(windows)]
628 self.cancel_capture_io();
629 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
642 self,
643 kill_drain_deadline(),
644 );
645 Ok(())
646 }
647
648 pub fn terminate(&self) -> Result<(), ProcessError> {
652 self.kill()
653 }
654
655 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
670 #[cfg(unix)]
671 {
672 if !self.config.create_process_group {
673 return Ok(());
674 }
675 let pid = match self.pid() {
676 Some(p) => p as i32,
677 None => return Err(ProcessError::NotRunning),
678 };
679 let result = unsafe { libc::kill(-pid, libc::SIGTERM) };
680 if result != 0 {
681 let err = std::io::Error::last_os_error();
682 if err.raw_os_error() != Some(libc::ESRCH) {
683 return Err(ProcessError::Io(err));
684 }
685 }
686 Ok(())
687 }
688 #[cfg(windows)]
689 {
690 if !self.config.create_process_group {
691 return Ok(());
696 }
697 let pid = match self.pid() {
698 Some(p) => p,
699 None => return Err(ProcessError::NotRunning),
700 };
701 let ok = unsafe {
705 winapi::um::wincon::GenerateConsoleCtrlEvent(
706 winapi::um::wincon::CTRL_BREAK_EVENT,
707 pid,
708 )
709 };
710 if ok == 0 {
711 let err = std::io::Error::last_os_error();
712 if err.raw_os_error() != Some(6) {
718 return Err(ProcessError::Io(err));
719 }
720 }
721 Ok(())
722 }
723 }
724
725 #[inline(never)]
727 pub fn close(&self) -> Result<(), ProcessError> {
729 public_symbols::rp_native_process_close_public(self)
730 }
731
732 fn close_impl(&self) -> Result<(), ProcessError> {
733 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
734 if self.child.lock().expect("child mutex poisoned").is_none() {
735 return Ok(());
736 }
737 if self.poll()?.is_none() {
738 self.kill()?;
739 } else {
740 self.finish_capture_drain();
741 }
742 Ok(())
743 }
744
745 pub fn pid(&self) -> Option<u32> {
747 self.child
748 .lock()
749 .expect("child mutex poisoned")
750 .as_ref()
751 .map(|state| state.child.id())
752 }
753
754 pub fn returncode(&self) -> Option<i32> {
756 let v = self.shared.returncode.load(Ordering::Acquire);
757 if v == RETURNCODE_NOT_SET {
758 None
759 } else {
760 Some(v as i32)
761 }
762 }
763
764 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
766 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
767 return false;
768 }
769 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
770 match stream {
771 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
772 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
773 }
774 }
775
776 pub fn has_pending_combined(&self) -> bool {
778 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
779 !guard.combined_queue.is_empty()
780 }
781
782 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
784 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
785 return Vec::new();
786 }
787 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
788 let queue = match stream {
789 StreamKind::Stdout => &mut guard.stdout_queue,
790 StreamKind::Stderr => &mut guard.stderr_queue,
791 };
792 queue.drain(..).collect()
793 }
794
795 pub fn drain_combined(&self) -> Vec<StreamEvent> {
797 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
798 guard.combined_queue.drain(..).collect()
799 }
800
801 pub fn read_stream(
806 &self,
807 stream: StreamKind,
808 timeout: Option<Duration>,
809 ) -> ReadStatus<Vec<u8>> {
810 let deadline = timeout.map(|limit| Instant::now() + limit);
811 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
812
813 loop {
814 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
815 return ReadStatus::Eof;
816 }
817
818 let queue = match stream {
819 StreamKind::Stdout => &mut guard.stdout_queue,
820 StreamKind::Stderr => &mut guard.stderr_queue,
821 };
822 if let Some(line) = queue.pop_front() {
823 return ReadStatus::Line(line);
824 }
825
826 let closed = match stream {
827 StreamKind::Stdout => {
828 if self.config.stderr_mode == StderrMode::Stdout {
829 guard.stdout_closed && guard.stderr_closed
830 } else {
831 guard.stdout_closed
832 }
833 }
834 StreamKind::Stderr => guard.stderr_closed,
835 };
836 if closed {
837 return ReadStatus::Eof;
838 }
839
840 match deadline {
841 Some(deadline) => {
842 let now = Instant::now();
843 if now >= deadline {
844 return ReadStatus::Timeout;
845 }
846 let wait = deadline.saturating_duration_since(now);
847 let result = self
848 .shared
849 .condvar
850 .wait_timeout(guard, wait)
851 .expect("queue mutex poisoned");
852 guard = result.0;
853 if result.1.timed_out() {
854 return ReadStatus::Timeout;
855 }
856 }
857 None => {
858 guard = self
859 .shared
860 .condvar
861 .wait(guard)
862 .expect("queue mutex poisoned");
863 }
864 }
865 }
866 }
867
868 #[inline(never)]
870 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
872 public_symbols::rp_native_process_read_combined_public(self, timeout)
873 }
874
875 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
876 crate::rp_rust_debug_scope!("running_process::NativeProcess::read_combined");
877 let deadline = timeout.map(|limit| Instant::now() + limit);
878 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
879
880 loop {
881 if let Some(event) = guard.combined_queue.pop_front() {
882 return ReadStatus::Line(event);
883 }
884 if guard.stdout_closed && guard.stderr_closed {
885 return ReadStatus::Eof;
886 }
887
888 match deadline {
889 Some(deadline) => {
890 let now = Instant::now();
891 if now >= deadline {
892 return ReadStatus::Timeout;
893 }
894 let wait = deadline.saturating_duration_since(now);
895 let result = self
896 .shared
897 .condvar
898 .wait_timeout(guard, wait)
899 .expect("queue mutex poisoned");
900 guard = result.0;
901 if result.1.timed_out() {
902 return ReadStatus::Timeout;
903 }
904 }
905 None => {
906 guard = self
907 .shared
908 .condvar
909 .wait(guard)
910 .expect("queue mutex poisoned");
911 }
912 }
913 }
914 }
915
916 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
918 self.shared
919 .queues
920 .lock()
921 .expect("queue mutex poisoned")
922 .stdout_history
923 .clone()
924 .into_iter()
925 .collect()
926 }
927
928 fn captured_stdout_raw(&self) -> Vec<u8> {
929 self.shared
930 .queues
931 .lock()
932 .expect("queue mutex poisoned")
933 .stdout_raw
934 .clone()
935 }
936
937 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
939 if self.config.stderr_mode == StderrMode::Stdout {
940 return Vec::new();
941 }
942 self.shared
943 .queues
944 .lock()
945 .expect("queue mutex poisoned")
946 .stderr_history
947 .clone()
948 .into_iter()
949 .collect()
950 }
951
952 fn captured_stderr_raw(&self) -> Vec<u8> {
953 if self.config.stderr_mode == StderrMode::Stdout {
954 return Vec::new();
955 }
956 self.shared
957 .queues
958 .lock()
959 .expect("queue mutex poisoned")
960 .stderr_raw
961 .clone()
962 }
963
964 pub fn captured_combined(&self) -> Vec<StreamEvent> {
966 self.shared
967 .queues
968 .lock()
969 .expect("queue mutex poisoned")
970 .combined_history
971 .clone()
972 .into_iter()
973 .collect()
974 }
975
976 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
978 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
979 return 0;
980 }
981 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
982 match stream {
983 StreamKind::Stdout => guard.stdout_history_bytes,
984 StreamKind::Stderr => guard.stderr_history_bytes,
985 }
986 }
987
988 pub fn captured_combined_bytes(&self) -> usize {
990 self.shared
991 .queues
992 .lock()
993 .expect("queue mutex poisoned")
994 .combined_history_bytes
995 }
996
997 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
999 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1000 return 0;
1001 }
1002 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1003 match stream {
1004 StreamKind::Stdout => {
1005 let released = guard.stdout_history_bytes;
1006 guard.stdout_history.clear();
1007 guard.stdout_raw.clear();
1008 guard.stdout_history_bytes = 0;
1009 released
1010 }
1011 StreamKind::Stderr => {
1012 let released = guard.stderr_history_bytes;
1013 guard.stderr_history.clear();
1014 guard.stderr_raw.clear();
1015 guard.stderr_history_bytes = 0;
1016 released
1017 }
1018 }
1019 }
1020
1021 pub fn clear_captured_combined(&self) -> usize {
1023 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1024 let released = guard.combined_history_bytes;
1025 guard.combined_history.clear();
1026 guard.combined_history_bytes = 0;
1027 released
1028 }
1029
1030 fn build_command(&self) -> Command {
1031 let mut command = match &self.config.command {
1032 CommandSpec::Shell(command) => shell_command(command),
1033 CommandSpec::Argv(argv) => {
1034 let mut command = Command::new(&argv[0]);
1035 if argv.len() > 1 {
1036 command.args(&argv[1..]);
1037 }
1038 command
1039 }
1040 };
1041 if let Some(cwd) = &self.config.cwd {
1042 command.current_dir(cwd);
1043 }
1044 if let Some(env) = &self.config.env {
1045 command.env_clear();
1046 command.envs(env.iter().map(|(k, v)| (k, v)));
1047 }
1048 #[cfg(windows)]
1049 {
1050 use std::os::windows::process::CommandExt;
1051
1052 let flags = windows_creation_flags(
1060 self.config.creationflags,
1061 self.config.create_process_group,
1062 self.config.nice,
1063 crate::windows::parent_has_console(),
1064 );
1065 if flags != 0 {
1066 command.creation_flags(flags);
1067 }
1068 }
1069 #[cfg(unix)]
1070 {
1071 let create_process_group = self.config.create_process_group;
1072 let nice = self.config.nice;
1073
1074 if create_process_group || nice.is_some() {
1075 use std::os::unix::process::CommandExt;
1076
1077 unsafe {
1078 command.pre_exec(move || {
1079 if create_process_group && libc::setpgid(0, 0) == -1 {
1080 return Err(std::io::Error::last_os_error());
1081 }
1082 if let Some(nice) = nice {
1083 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
1084 if result == -1 {
1085 return Err(std::io::Error::last_os_error());
1086 }
1087 }
1088 Ok(())
1089 });
1090 }
1091 }
1092 }
1093 command
1094 }
1095
1096 fn spawn_reader<R>(
1097 &self,
1098 pipe: R,
1099 source_stream: StreamKind,
1100 visible_stream: StreamKind,
1101 on_pipe_done: Box<dyn FnOnce() + Send>,
1102 ) where
1103 R: Read + Send + 'static,
1104 {
1105 let shared = Arc::clone(&self.shared);
1106 thread::spawn(move || {
1107 let mut reader = pipe;
1108 let mut chunk = vec![0_u8; 65536];
1109 let mut pending = Vec::new();
1110
1111 loop {
1112 match reader.read(&mut chunk) {
1113 Ok(0) => break,
1114 Ok(n) => {
1115 append_raw(&shared, visible_stream, &chunk[..n]);
1116 let lines = feed_chunk(&mut pending, &chunk[..n]);
1117 emit_lines(&shared, visible_stream, lines);
1118 }
1119 Err(_) => break,
1120 }
1121 }
1122
1123 if !pending.is_empty() {
1124 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1125 }
1126
1127 on_pipe_done();
1132 drop(reader);
1133
1134 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1135 match source_stream {
1136 StreamKind::Stdout => guard.stdout_closed = true,
1137 StreamKind::Stderr => guard.stderr_closed = true,
1138 }
1139 shared.condvar.notify_all();
1140 });
1141 }
1142
1143 #[cfg(windows)]
1144 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1145 let handles = Arc::clone(&self.capture_pipe_handles);
1146 Box::new(move || {
1147 let mut guard = handles.lock().expect("capture pipe handles mutex poisoned");
1148 match stream {
1149 StreamKind::Stdout => guard.stdout = None,
1150 StreamKind::Stderr => guard.stderr = None,
1151 }
1152 })
1153 }
1154
1155 #[cfg(not(windows))]
1156 fn pipe_done_callback(&self, _stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1157 Box::new(|| {})
1158 }
1159
1160 #[cfg(windows)]
1166 fn cancel_capture_io(&self) {
1167 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1168 cancel_capture_pipe_io(&self.capture_pipe_handles);
1169 }
1170
1171 fn set_returncode(&self, code: i32) {
1172 self.shared.returncode.store(code as i64, Ordering::Release);
1173 self.shared.condvar.notify_all();
1174 }
1175
1176 fn finish_capture_drain(&self) {
1186 let drained = self.wait_for_capture_completion_with_deadline_impl(kill_drain_deadline());
1187 #[cfg(windows)]
1188 if !drained {
1189 self.cancel_capture_io();
1190 }
1191 #[cfg(not(windows))]
1192 {
1193 let _ = drained;
1194 }
1195 }
1196
1197 fn wait_for_capture_completion_impl(&self) {
1198 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait_for_capture_completion");
1199 if !self.config.capture {
1200 return;
1201 }
1202
1203 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1204 while !(guard.stdout_closed && guard.stderr_closed) {
1205 guard = self
1206 .shared
1207 .condvar
1208 .wait(guard)
1209 .expect("queue mutex poisoned");
1210 }
1211 }
1212
1213 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1221 crate::rp_rust_debug_scope!(
1222 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1223 );
1224 if !self.config.capture {
1225 return true;
1226 }
1227 finalize_capture_completion(&self.shared, deadline)
1228 }
1229}
1230
1231#[cfg(windows)]
1237fn cancel_capture_pipe_io(handles: &Mutex<CapturePipeHandles>) {
1238 use winapi::shared::ntdef::HANDLE;
1239 use winapi::um::ioapiset::CancelIoEx;
1240 let guard = handles.lock().expect("capture pipe handles mutex poisoned");
1241 if let Some(h) = guard.stdout {
1242 unsafe {
1248 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1249 }
1250 }
1251 if let Some(h) = guard.stderr {
1252 unsafe {
1253 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1254 }
1255 }
1256}
1257
1258fn finalize_capture_completion(shared: &SharedState, deadline: Instant) -> bool {
1265 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1266 while !(guard.stdout_closed && guard.stderr_closed) {
1267 let now = Instant::now();
1268 if now >= deadline {
1269 guard.stdout_closed = true;
1270 guard.stderr_closed = true;
1271 shared.condvar.notify_all();
1272 return false;
1273 }
1274 let (next_guard, result) = shared
1275 .condvar
1276 .wait_timeout(guard, deadline - now)
1277 .expect("queue mutex poisoned");
1278 guard = next_guard;
1279 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1280 guard.stdout_closed = true;
1281 guard.stderr_closed = true;
1282 shared.condvar.notify_all();
1283 return false;
1284 }
1285 }
1286 true
1287}
1288
1289fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1290 if lines.is_empty() {
1291 return;
1292 }
1293 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1294 for line in lines {
1295 let line_len = line.len();
1296 match stream {
1297 StreamKind::Stdout => {
1298 guard.stdout_history_bytes += line_len;
1299 guard.stdout_history.push_back(line.clone());
1300 guard.stdout_queue.push_back(line.clone());
1301 }
1302 StreamKind::Stderr => {
1303 guard.stderr_history_bytes += line_len;
1304 guard.stderr_history.push_back(line.clone());
1305 guard.stderr_queue.push_back(line.clone());
1306 }
1307 }
1308 let event = StreamEvent { stream, line };
1309 guard.combined_history_bytes += line_len;
1310 guard.combined_history.push_back(event.clone());
1311 guard.combined_queue.push_back(event);
1312 }
1313 shared.condvar.notify_all();
1314}
1315
1316fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) {
1317 if chunk.is_empty() {
1318 return;
1319 }
1320 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1321 match stream {
1322 StreamKind::Stdout => guard.stdout_raw.extend_from_slice(chunk),
1323 StreamKind::Stderr => guard.stderr_raw.extend_from_slice(chunk),
1324 }
1325}
1326
1327pub fn run_command(
1333 mut config: ProcessConfig,
1334 timeout: Option<Duration>,
1335) -> Result<RunOutput, ProcessError> {
1336 config.capture = true;
1337 let process = NativeProcess::new(config);
1338 process.start()?;
1339
1340 let exit_code = match process.wait(timeout) {
1341 Ok(code) => code,
1342 Err(ProcessError::Timeout) => {
1343 match process.kill() {
1344 Ok(()) | Err(ProcessError::NotRunning) => {}
1345 Err(error) => return Err(error),
1346 }
1347 return Err(ProcessError::Timeout);
1348 }
1349 Err(error) => return Err(error),
1350 };
1351
1352 Ok(RunOutput {
1353 stdout: process.captured_stdout_raw(),
1354 stderr: process.captured_stderr_raw(),
1355 exit_code,
1356 })
1357}
1358
1359pub(crate) fn shell_command(command: &str) -> Command {
1360 #[cfg(windows)]
1361 {
1362 use std::os::windows::process::CommandExt;
1363
1364 let mut cmd = Command::new("cmd");
1365 cmd.raw_arg("/D /S /C \"");
1366 cmd.raw_arg(command);
1367 cmd.raw_arg("\"");
1368 cmd
1369 }
1370 #[cfg(not(windows))]
1371 {
1372 let mut cmd = Command::new("sh");
1373 cmd.arg("-lc").arg(command);
1374 cmd
1375 }
1376}
1377
1378#[cfg(test)]
1379mod tests;