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 thread::spawn(move || {
415 loop {
416 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
417 return;
418 }
419 {
420 let mut guard = child.lock().expect("child mutex poisoned");
421 if let Some(child_state) = guard.as_mut() {
422 let pid = child_state.child.id();
423 match child_state.child.try_wait() {
424 Ok(Some(status)) => {
425 let code = exit_code(status);
426 shared.returncode.store(code as i64, Ordering::Release);
427 shared.emit_exited(pid, code);
431 shared.condvar.notify_all();
432 return;
433 }
434 Ok(None) => {}
435 Err(_) => return,
436 }
437 } else {
438 return;
439 }
440 }
441 thread::sleep(Duration::from_millis(10));
447 }
448 });
449 }
450
451 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
453 let mut guard = self.child.lock().expect("child mutex poisoned");
454 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
455 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
456 use std::io::Write;
457 stdin.write_all(data).map_err(ProcessError::Io)?;
458 stdin.flush().map_err(ProcessError::Io)?;
459 drop(child.stdin.take());
460 Ok(())
461 }
462
463 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
468 let mut guard = self.child.lock().expect("child mutex poisoned");
469 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
470 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
471 use std::io::Write;
472 stdin.write_all(data).map_err(ProcessError::Io)?;
473 stdin.flush().map_err(ProcessError::Io)?;
474 Ok(())
475 }
476
477 pub fn close_stdin(&self) -> Result<(), ProcessError> {
480 let mut guard = self.child.lock().expect("child mutex poisoned");
481 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
482 drop(child.stdin.take());
483 Ok(())
484 }
485
486 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
490 if let Some(code) = self.returncode() {
492 return Ok(Some(code));
493 }
494 let mut guard = self.child.lock().expect("child mutex poisoned");
495 let Some(child_state) = guard.as_mut() else {
496 return Ok(self.returncode());
497 };
498 let pid = child_state.child.id();
499 let child = &mut child_state.child;
500 let status = child.try_wait().map_err(ProcessError::Io)?;
501 if let Some(status) = status {
502 let code = exit_code(status);
503 self.set_returncode(code);
504 self.shared.emit_exited(pid, code);
505 return Ok(Some(code));
506 }
507 Ok(None)
508 }
509
510 #[inline(never)]
512 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
517 public_symbols::rp_native_process_wait_public(self, timeout)
518 }
519
520 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
521 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
522 if self.child.lock().expect("child mutex poisoned").is_none() {
523 return self.returncode().ok_or(ProcessError::NotRunning);
524 }
525 if let Some(code) = self.returncode() {
527 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
528 return Ok(code);
529 }
530 let start = Instant::now();
531 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
532 loop {
533 let rc = self.shared.returncode.load(Ordering::Acquire);
535 if rc != RETURNCODE_NOT_SET {
536 drop(guard);
537 let code = rc as i32;
538 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
539 return Ok(code);
540 }
541 if let Some(limit) = timeout {
542 let elapsed = start.elapsed();
543 if elapsed >= limit {
544 return Err(ProcessError::Timeout);
545 }
546 let remaining = limit - elapsed;
547 let wait_time = remaining.min(Duration::from_millis(50));
549 guard = self
550 .shared
551 .condvar
552 .wait_timeout(guard, wait_time)
553 .expect("queue mutex poisoned")
554 .0;
555 } else {
556 guard = self
558 .shared
559 .condvar
560 .wait_timeout(guard, Duration::from_millis(50))
561 .expect("queue mutex poisoned")
562 .0;
563 }
564 }
565 }
566
567 #[inline(never)]
569 pub fn kill(&self) -> Result<(), ProcessError> {
571 public_symbols::rp_native_process_kill_public(self)
572 }
573
574 fn kill_impl(&self) -> Result<(), ProcessError> {
575 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
576 {
577 let mut guard = self.child.lock().expect("child mutex poisoned");
578 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
579 let pid = child.id();
580 child.kill().map_err(ProcessError::Io)?;
581 let status = child.wait().map_err(ProcessError::Io)?;
582 let code = exit_code(status);
583 self.set_returncode(code);
584 self.shared.emit_exited(pid, code);
587 }
588 #[cfg(windows)]
596 self.cancel_capture_io();
597 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
610 self,
611 kill_drain_deadline(),
612 );
613 Ok(())
614 }
615
616 pub fn terminate(&self) -> Result<(), ProcessError> {
620 self.kill()
621 }
622
623 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
638 #[cfg(unix)]
639 {
640 if !self.config.create_process_group {
641 return Ok(());
642 }
643 let pid = match self.pid() {
644 Some(p) => p as i32,
645 None => return Err(ProcessError::NotRunning),
646 };
647 let result = unsafe { libc::kill(-pid, libc::SIGTERM) };
648 if result != 0 {
649 let err = std::io::Error::last_os_error();
650 if err.raw_os_error() != Some(libc::ESRCH) {
651 return Err(ProcessError::Io(err));
652 }
653 }
654 Ok(())
655 }
656 #[cfg(windows)]
657 {
658 if !self.config.create_process_group {
659 return Ok(());
664 }
665 let pid = match self.pid() {
666 Some(p) => p,
667 None => return Err(ProcessError::NotRunning),
668 };
669 let ok = unsafe {
673 winapi::um::wincon::GenerateConsoleCtrlEvent(
674 winapi::um::wincon::CTRL_BREAK_EVENT,
675 pid,
676 )
677 };
678 if ok == 0 {
679 let err = std::io::Error::last_os_error();
680 if err.raw_os_error() != Some(6) {
686 return Err(ProcessError::Io(err));
687 }
688 }
689 Ok(())
690 }
691 }
692
693 #[inline(never)]
695 pub fn close(&self) -> Result<(), ProcessError> {
697 public_symbols::rp_native_process_close_public(self)
698 }
699
700 fn close_impl(&self) -> Result<(), ProcessError> {
701 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
702 if self.child.lock().expect("child mutex poisoned").is_none() {
703 return Ok(());
704 }
705 if self.poll()?.is_none() {
706 self.kill()?;
707 } else {
708 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
709 }
710 Ok(())
711 }
712
713 pub fn pid(&self) -> Option<u32> {
715 self.child
716 .lock()
717 .expect("child mutex poisoned")
718 .as_ref()
719 .map(|state| state.child.id())
720 }
721
722 pub fn returncode(&self) -> Option<i32> {
724 let v = self.shared.returncode.load(Ordering::Acquire);
725 if v == RETURNCODE_NOT_SET {
726 None
727 } else {
728 Some(v as i32)
729 }
730 }
731
732 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
734 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
735 return false;
736 }
737 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
738 match stream {
739 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
740 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
741 }
742 }
743
744 pub fn has_pending_combined(&self) -> bool {
746 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
747 !guard.combined_queue.is_empty()
748 }
749
750 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
752 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
753 return Vec::new();
754 }
755 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
756 let queue = match stream {
757 StreamKind::Stdout => &mut guard.stdout_queue,
758 StreamKind::Stderr => &mut guard.stderr_queue,
759 };
760 queue.drain(..).collect()
761 }
762
763 pub fn drain_combined(&self) -> Vec<StreamEvent> {
765 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
766 guard.combined_queue.drain(..).collect()
767 }
768
769 pub fn read_stream(
774 &self,
775 stream: StreamKind,
776 timeout: Option<Duration>,
777 ) -> ReadStatus<Vec<u8>> {
778 let deadline = timeout.map(|limit| Instant::now() + limit);
779 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
780
781 loop {
782 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
783 return ReadStatus::Eof;
784 }
785
786 let queue = match stream {
787 StreamKind::Stdout => &mut guard.stdout_queue,
788 StreamKind::Stderr => &mut guard.stderr_queue,
789 };
790 if let Some(line) = queue.pop_front() {
791 return ReadStatus::Line(line);
792 }
793
794 let closed = match stream {
795 StreamKind::Stdout => {
796 if self.config.stderr_mode == StderrMode::Stdout {
797 guard.stdout_closed && guard.stderr_closed
798 } else {
799 guard.stdout_closed
800 }
801 }
802 StreamKind::Stderr => guard.stderr_closed,
803 };
804 if closed {
805 return ReadStatus::Eof;
806 }
807
808 match deadline {
809 Some(deadline) => {
810 let now = Instant::now();
811 if now >= deadline {
812 return ReadStatus::Timeout;
813 }
814 let wait = deadline.saturating_duration_since(now);
815 let result = self
816 .shared
817 .condvar
818 .wait_timeout(guard, wait)
819 .expect("queue mutex poisoned");
820 guard = result.0;
821 if result.1.timed_out() {
822 return ReadStatus::Timeout;
823 }
824 }
825 None => {
826 guard = self
827 .shared
828 .condvar
829 .wait(guard)
830 .expect("queue mutex poisoned");
831 }
832 }
833 }
834 }
835
836 #[inline(never)]
838 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
840 public_symbols::rp_native_process_read_combined_public(self, timeout)
841 }
842
843 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
844 crate::rp_rust_debug_scope!("running_process::NativeProcess::read_combined");
845 let deadline = timeout.map(|limit| Instant::now() + limit);
846 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
847
848 loop {
849 if let Some(event) = guard.combined_queue.pop_front() {
850 return ReadStatus::Line(event);
851 }
852 if guard.stdout_closed && guard.stderr_closed {
853 return ReadStatus::Eof;
854 }
855
856 match deadline {
857 Some(deadline) => {
858 let now = Instant::now();
859 if now >= deadline {
860 return ReadStatus::Timeout;
861 }
862 let wait = deadline.saturating_duration_since(now);
863 let result = self
864 .shared
865 .condvar
866 .wait_timeout(guard, wait)
867 .expect("queue mutex poisoned");
868 guard = result.0;
869 if result.1.timed_out() {
870 return ReadStatus::Timeout;
871 }
872 }
873 None => {
874 guard = self
875 .shared
876 .condvar
877 .wait(guard)
878 .expect("queue mutex poisoned");
879 }
880 }
881 }
882 }
883
884 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
886 self.shared
887 .queues
888 .lock()
889 .expect("queue mutex poisoned")
890 .stdout_history
891 .clone()
892 .into_iter()
893 .collect()
894 }
895
896 fn captured_stdout_raw(&self) -> Vec<u8> {
897 self.shared
898 .queues
899 .lock()
900 .expect("queue mutex poisoned")
901 .stdout_raw
902 .clone()
903 }
904
905 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
907 if self.config.stderr_mode == StderrMode::Stdout {
908 return Vec::new();
909 }
910 self.shared
911 .queues
912 .lock()
913 .expect("queue mutex poisoned")
914 .stderr_history
915 .clone()
916 .into_iter()
917 .collect()
918 }
919
920 fn captured_stderr_raw(&self) -> Vec<u8> {
921 if self.config.stderr_mode == StderrMode::Stdout {
922 return Vec::new();
923 }
924 self.shared
925 .queues
926 .lock()
927 .expect("queue mutex poisoned")
928 .stderr_raw
929 .clone()
930 }
931
932 pub fn captured_combined(&self) -> Vec<StreamEvent> {
934 self.shared
935 .queues
936 .lock()
937 .expect("queue mutex poisoned")
938 .combined_history
939 .clone()
940 .into_iter()
941 .collect()
942 }
943
944 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
946 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
947 return 0;
948 }
949 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
950 match stream {
951 StreamKind::Stdout => guard.stdout_history_bytes,
952 StreamKind::Stderr => guard.stderr_history_bytes,
953 }
954 }
955
956 pub fn captured_combined_bytes(&self) -> usize {
958 self.shared
959 .queues
960 .lock()
961 .expect("queue mutex poisoned")
962 .combined_history_bytes
963 }
964
965 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
967 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
968 return 0;
969 }
970 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
971 match stream {
972 StreamKind::Stdout => {
973 let released = guard.stdout_history_bytes;
974 guard.stdout_history.clear();
975 guard.stdout_raw.clear();
976 guard.stdout_history_bytes = 0;
977 released
978 }
979 StreamKind::Stderr => {
980 let released = guard.stderr_history_bytes;
981 guard.stderr_history.clear();
982 guard.stderr_raw.clear();
983 guard.stderr_history_bytes = 0;
984 released
985 }
986 }
987 }
988
989 pub fn clear_captured_combined(&self) -> usize {
991 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
992 let released = guard.combined_history_bytes;
993 guard.combined_history.clear();
994 guard.combined_history_bytes = 0;
995 released
996 }
997
998 fn build_command(&self) -> Command {
999 let mut command = match &self.config.command {
1000 CommandSpec::Shell(command) => shell_command(command),
1001 CommandSpec::Argv(argv) => {
1002 let mut command = Command::new(&argv[0]);
1003 if argv.len() > 1 {
1004 command.args(&argv[1..]);
1005 }
1006 command
1007 }
1008 };
1009 if let Some(cwd) = &self.config.cwd {
1010 command.current_dir(cwd);
1011 }
1012 if let Some(env) = &self.config.env {
1013 command.env_clear();
1014 command.envs(env.iter().map(|(k, v)| (k, v)));
1015 }
1016 #[cfg(windows)]
1017 {
1018 use std::os::windows::process::CommandExt;
1019
1020 let flags = windows_creation_flags(
1025 self.config.creationflags,
1026 self.config.create_process_group,
1027 self.config.nice,
1028 );
1029 if flags != 0 {
1030 command.creation_flags(flags);
1031 }
1032 }
1033 #[cfg(unix)]
1034 {
1035 let create_process_group = self.config.create_process_group;
1036 let nice = self.config.nice;
1037
1038 if create_process_group || nice.is_some() {
1039 use std::os::unix::process::CommandExt;
1040
1041 unsafe {
1042 command.pre_exec(move || {
1043 if create_process_group && libc::setpgid(0, 0) == -1 {
1044 return Err(std::io::Error::last_os_error());
1045 }
1046 if let Some(nice) = nice {
1047 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
1048 if result == -1 {
1049 return Err(std::io::Error::last_os_error());
1050 }
1051 }
1052 Ok(())
1053 });
1054 }
1055 }
1056 }
1057 command
1058 }
1059
1060 fn spawn_reader<R>(
1061 &self,
1062 pipe: R,
1063 source_stream: StreamKind,
1064 visible_stream: StreamKind,
1065 on_pipe_done: Box<dyn FnOnce() + Send>,
1066 ) where
1067 R: Read + Send + 'static,
1068 {
1069 let shared = Arc::clone(&self.shared);
1070 thread::spawn(move || {
1071 let mut reader = pipe;
1072 let mut chunk = vec![0_u8; 65536];
1073 let mut pending = Vec::new();
1074
1075 loop {
1076 match reader.read(&mut chunk) {
1077 Ok(0) => break,
1078 Ok(n) => {
1079 append_raw(&shared, visible_stream, &chunk[..n]);
1080 let lines = feed_chunk(&mut pending, &chunk[..n]);
1081 emit_lines(&shared, visible_stream, lines);
1082 }
1083 Err(_) => break,
1084 }
1085 }
1086
1087 if !pending.is_empty() {
1088 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1089 }
1090
1091 on_pipe_done();
1096 drop(reader);
1097
1098 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1099 match source_stream {
1100 StreamKind::Stdout => guard.stdout_closed = true,
1101 StreamKind::Stderr => guard.stderr_closed = true,
1102 }
1103 shared.condvar.notify_all();
1104 });
1105 }
1106
1107 #[cfg(windows)]
1108 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1109 let handles = Arc::clone(&self.capture_pipe_handles);
1110 Box::new(move || {
1111 let mut guard = handles.lock().expect("capture pipe handles mutex poisoned");
1112 match stream {
1113 StreamKind::Stdout => guard.stdout = None,
1114 StreamKind::Stderr => guard.stderr = None,
1115 }
1116 })
1117 }
1118
1119 #[cfg(not(windows))]
1120 fn pipe_done_callback(&self, _stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1121 Box::new(|| {})
1122 }
1123
1124 #[cfg(windows)]
1130 fn cancel_capture_io(&self) {
1131 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1132 use winapi::shared::ntdef::HANDLE;
1133 use winapi::um::ioapiset::CancelIoEx;
1134 let guard = self
1135 .capture_pipe_handles
1136 .lock()
1137 .expect("capture pipe handles mutex poisoned");
1138 if let Some(h) = guard.stdout {
1139 unsafe {
1146 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1147 }
1148 }
1149 if let Some(h) = guard.stderr {
1150 unsafe {
1151 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1152 }
1153 }
1154 }
1155
1156 fn set_returncode(&self, code: i32) {
1157 self.shared.returncode.store(code as i64, Ordering::Release);
1158 self.shared.condvar.notify_all();
1159 }
1160
1161 fn wait_for_capture_completion_impl(&self) {
1162 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait_for_capture_completion");
1163 if !self.config.capture {
1164 return;
1165 }
1166
1167 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1168 while !(guard.stdout_closed && guard.stderr_closed) {
1169 guard = self
1170 .shared
1171 .condvar
1172 .wait(guard)
1173 .expect("queue mutex poisoned");
1174 }
1175 }
1176
1177 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1185 crate::rp_rust_debug_scope!(
1186 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1187 );
1188 if !self.config.capture {
1189 return true;
1190 }
1191
1192 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1193 while !(guard.stdout_closed && guard.stderr_closed) {
1194 let now = Instant::now();
1195 if now >= deadline {
1196 guard.stdout_closed = true;
1197 guard.stderr_closed = true;
1198 self.shared.condvar.notify_all();
1199 return false;
1200 }
1201 let (next_guard, result) = self
1202 .shared
1203 .condvar
1204 .wait_timeout(guard, deadline - now)
1205 .expect("queue mutex poisoned");
1206 guard = next_guard;
1207 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1208 guard.stdout_closed = true;
1209 guard.stderr_closed = true;
1210 self.shared.condvar.notify_all();
1211 return false;
1212 }
1213 }
1214 true
1215 }
1216}
1217
1218fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1219 if lines.is_empty() {
1220 return;
1221 }
1222 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1223 for line in lines {
1224 let line_len = line.len();
1225 match stream {
1226 StreamKind::Stdout => {
1227 guard.stdout_history_bytes += line_len;
1228 guard.stdout_history.push_back(line.clone());
1229 guard.stdout_queue.push_back(line.clone());
1230 }
1231 StreamKind::Stderr => {
1232 guard.stderr_history_bytes += line_len;
1233 guard.stderr_history.push_back(line.clone());
1234 guard.stderr_queue.push_back(line.clone());
1235 }
1236 }
1237 let event = StreamEvent { stream, line };
1238 guard.combined_history_bytes += line_len;
1239 guard.combined_history.push_back(event.clone());
1240 guard.combined_queue.push_back(event);
1241 }
1242 shared.condvar.notify_all();
1243}
1244
1245fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) {
1246 if chunk.is_empty() {
1247 return;
1248 }
1249 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1250 match stream {
1251 StreamKind::Stdout => guard.stdout_raw.extend_from_slice(chunk),
1252 StreamKind::Stderr => guard.stderr_raw.extend_from_slice(chunk),
1253 }
1254}
1255
1256pub fn run_command(
1262 mut config: ProcessConfig,
1263 timeout: Option<Duration>,
1264) -> Result<RunOutput, ProcessError> {
1265 config.capture = true;
1266 let process = NativeProcess::new(config);
1267 process.start()?;
1268
1269 let exit_code = match process.wait(timeout) {
1270 Ok(code) => code,
1271 Err(ProcessError::Timeout) => {
1272 match process.kill() {
1273 Ok(()) | Err(ProcessError::NotRunning) => {}
1274 Err(error) => return Err(error),
1275 }
1276 return Err(ProcessError::Timeout);
1277 }
1278 Err(error) => return Err(error),
1279 };
1280
1281 Ok(RunOutput {
1282 stdout: process.captured_stdout_raw(),
1283 stderr: process.captured_stderr_raw(),
1284 exit_code,
1285 })
1286}
1287
1288pub(crate) fn shell_command(command: &str) -> Command {
1289 #[cfg(windows)]
1290 {
1291 use std::os::windows::process::CommandExt;
1292
1293 let mut cmd = Command::new("cmd");
1294 cmd.raw_arg("/D /S /C \"");
1295 cmd.raw_arg(command);
1296 cmd.raw_arg("\"");
1297 cmd
1298 }
1299 #[cfg(not(windows))]
1300 {
1301 let mut cmd = Command::new("sh");
1302 cmd.arg("-lc").arg(command);
1303 cmd
1304 }
1305}
1306
1307#[cfg(test)]
1308mod tests;