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