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;
95pub mod pty;
97mod public_symbols;
98mod rust_debug;
99pub mod spawn;
100pub mod systemd_killmode;
101pub mod terminal_graphics;
102mod types;
103#[cfg(unix)]
104mod unix;
105#[cfg(windows)]
106mod windows;
107
108pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
109pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
110pub use observer::{
111 CapabilitySupport, CategoryCapability, EventCategory, ObserverCapabilities, ObserverConfig,
112 ObserverEvent, ObserverEventKind, ObserverSubscriber,
113};
114#[cfg(feature = "originator-scan")]
115pub use originator::{find_processes_by_originator, OriginatorProcessInfo};
116pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
117pub use spawn::{
118 spawn, spawn_daemon, spawn_daemon_with_clear_env, DaemonChild, SpawnStdio, SpawnedChild,
119 StdioSource,
120};
121pub use terminal_graphics::{
122 current_terminal_capabilities, current_terminal_capabilities_with_timeout,
123 detect_terminal_capabilities, CapabilityStatus, EvidenceStrength, GraphicsCapability,
124 GraphicsProtocol, TerminalCapabilities, TerminalCapabilityInput, TerminalGraphicsCapabilities,
125 TerminalProbeEvidence,
126};
127pub use types::{
128 CommandSpec, ProcessConfig, ProcessError, ReadStatus, RunOutput, StderrMode, StdinMode,
129 StreamEvent, StreamKind,
130};
131
132pub(crate) use helpers::{exit_code, feed_chunk, kill_drain_deadline, log_spawned_child_pid};
133#[cfg(unix)]
134pub use unix::{unix_set_priority, unix_signal_process, unix_signal_process_group, UnixSignal};
135#[cfg(windows)]
136pub(crate) use windows::{
137 assign_child_to_windows_kill_on_close_job_impl, windows_creation_flags, CapturePipeHandles,
138 WindowsJobHandle,
139};
140
141#[macro_export]
142macro_rules! rp_rust_debug_scope {
144 ($label:expr) => {
145 let _running_process_rust_debug_scope =
146 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
147 };
148}
149
150#[derive(Default)]
151struct QueueState {
152 stdout_queue: VecDeque<Vec<u8>>,
153 stderr_queue: VecDeque<Vec<u8>>,
154 combined_queue: VecDeque<StreamEvent>,
155 stdout_history: VecDeque<Vec<u8>>,
156 stderr_history: VecDeque<Vec<u8>>,
157 combined_history: VecDeque<StreamEvent>,
158 stdout_raw: Vec<u8>,
159 stderr_raw: Vec<u8>,
160 stdout_history_bytes: usize,
161 stderr_history_bytes: usize,
162 combined_history_bytes: usize,
163 stdout_closed: bool,
164 stderr_closed: bool,
165}
166
167const RETURNCODE_NOT_SET: i64 = i64::MIN;
169
170struct SharedState {
171 queues: Mutex<QueueState>,
172 condvar: Condvar,
173 returncode: AtomicI64,
176 observer: Option<ObserverEmitter>,
181 observer_exit_emitted: AtomicBool,
184}
185
186struct ChildState {
187 child: Child,
188 #[cfg(windows)]
189 _job: WindowsJobHandle,
190}
191
192impl SharedState {
193 fn new(capture: bool) -> Self {
194 Self::with_observer(capture, None)
195 }
196
197 fn with_observer(capture: bool, observer: Option<ObserverEmitter>) -> Self {
198 let queues = QueueState {
199 stdout_closed: !capture,
200 stderr_closed: !capture,
201 ..QueueState::default()
202 };
203 Self {
204 queues: Mutex::new(queues),
205 condvar: Condvar::new(),
206 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
207 observer,
208 observer_exit_emitted: AtomicBool::new(false),
209 }
210 }
211
212 fn emit_exited(&self, pid: u32, exit_code: i32) {
215 let Some(emitter) = self.observer.as_ref() else {
216 return;
217 };
218 if self
219 .observer_exit_emitted
220 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
221 .is_ok()
222 {
223 emitter.emit_exited(pid, exit_code);
224 }
225 }
226}
227
228pub struct NativeProcess {
235 config: ProcessConfig,
236 child: Arc<Mutex<Option<ChildState>>>,
237 shared: Arc<SharedState>,
238 #[cfg(windows)]
239 capture_pipe_handles: Arc<Mutex<CapturePipeHandles>>,
240}
241
242impl NativeProcess {
243 pub fn new(config: ProcessConfig) -> Self {
249 Self::new_with_observer(config, None)
250 }
251
252 pub fn with_observer(
265 config: ProcessConfig,
266 observer: crate::observer::ObserverConfig,
267 ) -> (Self, ObserverSubscriber) {
268 let (emitter, subscriber) = ObserverEmitter::new(observer);
269 let process = Self::new_with_observer(config, Some(emitter));
270 (process, subscriber)
271 }
272
273 fn new_with_observer(config: ProcessConfig, observer: Option<ObserverEmitter>) -> Self {
274 let shared = match observer {
275 None => SharedState::new(config.capture),
277 Some(emitter) => SharedState::with_observer(config.capture, Some(emitter)),
278 };
279 Self {
280 shared: Arc::new(shared),
281 child: Arc::new(Mutex::new(None)),
282 config,
283 #[cfg(windows)]
284 capture_pipe_handles: Arc::new(Mutex::new(CapturePipeHandles::default())),
285 }
286 }
287
288 #[inline(never)]
290 pub fn start(&self) -> Result<(), ProcessError> {
295 public_symbols::rp_native_process_start_public(self)
296 }
297
298 fn start_impl(&self) -> Result<(), ProcessError> {
299 crate::rp_rust_debug_scope!("running_process::NativeProcess::start");
300 let mut guard = self.child.lock().expect("child mutex poisoned");
301 if guard.is_some() {
302 return Err(ProcessError::AlreadyStarted);
303 }
304
305 let mut command = self.build_command();
306 match self.config.stdin_mode {
307 StdinMode::Inherit => {}
308 StdinMode::Piped => {
309 command.stdin(Stdio::piped());
310 }
311 StdinMode::Null => {
312 command.stdin(Stdio::null());
313 }
314 }
315 if self.config.capture {
316 command.stdout(Stdio::piped());
317 command.stderr(Stdio::piped());
318 }
319
320 let mut child = command.spawn().map_err(ProcessError::Spawn)?;
321 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
322 if let Some(emitter) = self.shared.observer.as_ref() {
325 emitter.emit_started(child.id());
326 }
327 #[cfg(windows)]
332 let job = {
333 let descendant_sink = self
334 .shared
335 .observer
336 .as_ref()
337 .and_then(|e| e.descendant_sink());
338 let direct_pid = child.id();
339 public_symbols::rp_assign_child_to_windows_kill_on_close_job_with_observer_public(
340 &child,
341 descendant_sink,
342 direct_pid,
343 )
344 .map_err(ProcessError::Spawn)?
345 };
346 #[cfg(target_os = "linux")]
350 {
351 if let Some(emitter) = self.shared.observer.as_ref() {
352 if let Some(sink) = emitter.descendant_sink() {
353 crate::observer::descendants_linux::enable_subreaper();
354 crate::observer::descendants_linux::spawn_pump(child.id(), sink);
355 }
356 }
357 }
358 #[cfg(target_os = "macos")]
362 {
363 if let Some(emitter) = self.shared.observer.as_ref() {
364 if let Some(sink) = emitter.descendant_sink() {
365 crate::observer::descendants_macos::spawn_pump(child.id(), sink);
366 }
367 }
368 }
369 if self.config.capture {
370 let stdout = child.stdout.take().expect("stdout pipe missing");
371 let stderr = child.stderr.take().expect("stderr pipe missing");
372 #[cfg(windows)]
373 {
374 use std::os::windows::io::AsRawHandle;
375 let mut handles = self
376 .capture_pipe_handles
377 .lock()
378 .expect("capture pipe handles mutex poisoned");
379 handles.stdout = Some(stdout.as_raw_handle() as usize);
380 handles.stderr = Some(stderr.as_raw_handle() as usize);
381 }
382 self.spawn_reader(
383 stdout,
384 StreamKind::Stdout,
385 StreamKind::Stdout,
386 self.pipe_done_callback(StreamKind::Stdout),
387 );
388 self.spawn_reader(
389 stderr,
390 StreamKind::Stderr,
391 match self.config.stderr_mode {
392 StderrMode::Stdout => StreamKind::Stdout,
393 StderrMode::Pipe => StreamKind::Stderr,
394 },
395 self.pipe_done_callback(StreamKind::Stderr),
396 );
397 }
398 *guard = Some(ChildState {
399 child,
400 #[cfg(windows)]
401 _job: job,
402 });
403 drop(guard);
404 self.spawn_exit_waiter();
405 Ok(())
406 }
407
408 fn spawn_exit_waiter(&self) {
411 let child = Arc::clone(&self.child);
412 let shared = Arc::clone(&self.shared);
413 thread::spawn(move || {
414 loop {
415 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
416 return;
417 }
418 {
419 let mut guard = child.lock().expect("child mutex poisoned");
420 if let Some(child_state) = guard.as_mut() {
421 let pid = child_state.child.id();
422 match child_state.child.try_wait() {
423 Ok(Some(status)) => {
424 let code = exit_code(status);
425 shared.returncode.store(code as i64, Ordering::Release);
426 shared.emit_exited(pid, code);
430 shared.condvar.notify_all();
431 return;
432 }
433 Ok(None) => {}
434 Err(_) => return,
435 }
436 } else {
437 return;
438 }
439 }
440 thread::sleep(Duration::from_millis(10));
446 }
447 });
448 }
449
450 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
452 let mut guard = self.child.lock().expect("child mutex poisoned");
453 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
454 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
455 use std::io::Write;
456 stdin.write_all(data).map_err(ProcessError::Io)?;
457 stdin.flush().map_err(ProcessError::Io)?;
458 drop(child.stdin.take());
459 Ok(())
460 }
461
462 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
467 let mut guard = self.child.lock().expect("child mutex poisoned");
468 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
469 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
470 use std::io::Write;
471 stdin.write_all(data).map_err(ProcessError::Io)?;
472 stdin.flush().map_err(ProcessError::Io)?;
473 Ok(())
474 }
475
476 pub fn close_stdin(&self) -> Result<(), ProcessError> {
479 let mut guard = self.child.lock().expect("child mutex poisoned");
480 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
481 drop(child.stdin.take());
482 Ok(())
483 }
484
485 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
489 if let Some(code) = self.returncode() {
491 return Ok(Some(code));
492 }
493 let mut guard = self.child.lock().expect("child mutex poisoned");
494 let Some(child_state) = guard.as_mut() else {
495 return Ok(self.returncode());
496 };
497 let pid = child_state.child.id();
498 let child = &mut child_state.child;
499 let status = child.try_wait().map_err(ProcessError::Io)?;
500 if let Some(status) = status {
501 let code = exit_code(status);
502 self.set_returncode(code);
503 self.shared.emit_exited(pid, code);
504 return Ok(Some(code));
505 }
506 Ok(None)
507 }
508
509 #[inline(never)]
511 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
516 public_symbols::rp_native_process_wait_public(self, timeout)
517 }
518
519 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
520 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
521 if self.child.lock().expect("child mutex poisoned").is_none() {
522 return self.returncode().ok_or(ProcessError::NotRunning);
523 }
524 if let Some(code) = self.returncode() {
526 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
527 return Ok(code);
528 }
529 let start = Instant::now();
530 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
531 loop {
532 let rc = self.shared.returncode.load(Ordering::Acquire);
534 if rc != RETURNCODE_NOT_SET {
535 drop(guard);
536 let code = rc as i32;
537 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
538 return Ok(code);
539 }
540 if let Some(limit) = timeout {
541 let elapsed = start.elapsed();
542 if elapsed >= limit {
543 return Err(ProcessError::Timeout);
544 }
545 let remaining = limit - elapsed;
546 let wait_time = remaining.min(Duration::from_millis(50));
548 guard = self
549 .shared
550 .condvar
551 .wait_timeout(guard, wait_time)
552 .expect("queue mutex poisoned")
553 .0;
554 } else {
555 guard = self
557 .shared
558 .condvar
559 .wait_timeout(guard, Duration::from_millis(50))
560 .expect("queue mutex poisoned")
561 .0;
562 }
563 }
564 }
565
566 #[inline(never)]
568 pub fn kill(&self) -> Result<(), ProcessError> {
570 public_symbols::rp_native_process_kill_public(self)
571 }
572
573 fn kill_impl(&self) -> Result<(), ProcessError> {
574 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
575 {
576 let mut guard = self.child.lock().expect("child mutex poisoned");
577 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
578 let pid = child.id();
579 child.kill().map_err(ProcessError::Io)?;
580 let status = child.wait().map_err(ProcessError::Io)?;
581 let code = exit_code(status);
582 self.set_returncode(code);
583 self.shared.emit_exited(pid, code);
586 }
587 #[cfg(windows)]
595 self.cancel_capture_io();
596 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
609 self,
610 kill_drain_deadline(),
611 );
612 Ok(())
613 }
614
615 pub fn terminate(&self) -> Result<(), ProcessError> {
619 self.kill()
620 }
621
622 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
637 #[cfg(unix)]
638 {
639 if !self.config.create_process_group {
640 return Ok(());
641 }
642 let pid = match self.pid() {
643 Some(p) => p as i32,
644 None => return Err(ProcessError::NotRunning),
645 };
646 let result = unsafe { libc::kill(-pid, libc::SIGTERM) };
647 if result != 0 {
648 let err = std::io::Error::last_os_error();
649 if err.raw_os_error() != Some(libc::ESRCH) {
650 return Err(ProcessError::Io(err));
651 }
652 }
653 Ok(())
654 }
655 #[cfg(windows)]
656 {
657 if !self.config.create_process_group {
658 return Ok(());
663 }
664 let pid = match self.pid() {
665 Some(p) => p,
666 None => return Err(ProcessError::NotRunning),
667 };
668 let ok = unsafe {
672 winapi::um::wincon::GenerateConsoleCtrlEvent(
673 winapi::um::wincon::CTRL_BREAK_EVENT,
674 pid,
675 )
676 };
677 if ok == 0 {
678 let err = std::io::Error::last_os_error();
679 if err.raw_os_error() != Some(6) {
685 return Err(ProcessError::Io(err));
686 }
687 }
688 Ok(())
689 }
690 }
691
692 #[inline(never)]
694 pub fn close(&self) -> Result<(), ProcessError> {
696 public_symbols::rp_native_process_close_public(self)
697 }
698
699 fn close_impl(&self) -> Result<(), ProcessError> {
700 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
701 if self.child.lock().expect("child mutex poisoned").is_none() {
702 return Ok(());
703 }
704 if self.poll()?.is_none() {
705 self.kill()?;
706 } else {
707 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
708 }
709 Ok(())
710 }
711
712 pub fn pid(&self) -> Option<u32> {
714 self.child
715 .lock()
716 .expect("child mutex poisoned")
717 .as_ref()
718 .map(|state| state.child.id())
719 }
720
721 pub fn returncode(&self) -> Option<i32> {
723 let v = self.shared.returncode.load(Ordering::Acquire);
724 if v == RETURNCODE_NOT_SET {
725 None
726 } else {
727 Some(v as i32)
728 }
729 }
730
731 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
733 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
734 return false;
735 }
736 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
737 match stream {
738 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
739 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
740 }
741 }
742
743 pub fn has_pending_combined(&self) -> bool {
745 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
746 !guard.combined_queue.is_empty()
747 }
748
749 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
751 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
752 return Vec::new();
753 }
754 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
755 let queue = match stream {
756 StreamKind::Stdout => &mut guard.stdout_queue,
757 StreamKind::Stderr => &mut guard.stderr_queue,
758 };
759 queue.drain(..).collect()
760 }
761
762 pub fn drain_combined(&self) -> Vec<StreamEvent> {
764 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
765 guard.combined_queue.drain(..).collect()
766 }
767
768 pub fn read_stream(
773 &self,
774 stream: StreamKind,
775 timeout: Option<Duration>,
776 ) -> ReadStatus<Vec<u8>> {
777 let deadline = timeout.map(|limit| Instant::now() + limit);
778 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
779
780 loop {
781 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
782 return ReadStatus::Eof;
783 }
784
785 let queue = match stream {
786 StreamKind::Stdout => &mut guard.stdout_queue,
787 StreamKind::Stderr => &mut guard.stderr_queue,
788 };
789 if let Some(line) = queue.pop_front() {
790 return ReadStatus::Line(line);
791 }
792
793 let closed = match stream {
794 StreamKind::Stdout => {
795 if self.config.stderr_mode == StderrMode::Stdout {
796 guard.stdout_closed && guard.stderr_closed
797 } else {
798 guard.stdout_closed
799 }
800 }
801 StreamKind::Stderr => guard.stderr_closed,
802 };
803 if closed {
804 return ReadStatus::Eof;
805 }
806
807 match deadline {
808 Some(deadline) => {
809 let now = Instant::now();
810 if now >= deadline {
811 return ReadStatus::Timeout;
812 }
813 let wait = deadline.saturating_duration_since(now);
814 let result = self
815 .shared
816 .condvar
817 .wait_timeout(guard, wait)
818 .expect("queue mutex poisoned");
819 guard = result.0;
820 if result.1.timed_out() {
821 return ReadStatus::Timeout;
822 }
823 }
824 None => {
825 guard = self
826 .shared
827 .condvar
828 .wait(guard)
829 .expect("queue mutex poisoned");
830 }
831 }
832 }
833 }
834
835 #[inline(never)]
837 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
839 public_symbols::rp_native_process_read_combined_public(self, timeout)
840 }
841
842 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
843 crate::rp_rust_debug_scope!("running_process::NativeProcess::read_combined");
844 let deadline = timeout.map(|limit| Instant::now() + limit);
845 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
846
847 loop {
848 if let Some(event) = guard.combined_queue.pop_front() {
849 return ReadStatus::Line(event);
850 }
851 if guard.stdout_closed && guard.stderr_closed {
852 return ReadStatus::Eof;
853 }
854
855 match deadline {
856 Some(deadline) => {
857 let now = Instant::now();
858 if now >= deadline {
859 return ReadStatus::Timeout;
860 }
861 let wait = deadline.saturating_duration_since(now);
862 let result = self
863 .shared
864 .condvar
865 .wait_timeout(guard, wait)
866 .expect("queue mutex poisoned");
867 guard = result.0;
868 if result.1.timed_out() {
869 return ReadStatus::Timeout;
870 }
871 }
872 None => {
873 guard = self
874 .shared
875 .condvar
876 .wait(guard)
877 .expect("queue mutex poisoned");
878 }
879 }
880 }
881 }
882
883 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
885 self.shared
886 .queues
887 .lock()
888 .expect("queue mutex poisoned")
889 .stdout_history
890 .clone()
891 .into_iter()
892 .collect()
893 }
894
895 fn captured_stdout_raw(&self) -> Vec<u8> {
896 self.shared
897 .queues
898 .lock()
899 .expect("queue mutex poisoned")
900 .stdout_raw
901 .clone()
902 }
903
904 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
906 if self.config.stderr_mode == StderrMode::Stdout {
907 return Vec::new();
908 }
909 self.shared
910 .queues
911 .lock()
912 .expect("queue mutex poisoned")
913 .stderr_history
914 .clone()
915 .into_iter()
916 .collect()
917 }
918
919 fn captured_stderr_raw(&self) -> Vec<u8> {
920 if self.config.stderr_mode == StderrMode::Stdout {
921 return Vec::new();
922 }
923 self.shared
924 .queues
925 .lock()
926 .expect("queue mutex poisoned")
927 .stderr_raw
928 .clone()
929 }
930
931 pub fn captured_combined(&self) -> Vec<StreamEvent> {
933 self.shared
934 .queues
935 .lock()
936 .expect("queue mutex poisoned")
937 .combined_history
938 .clone()
939 .into_iter()
940 .collect()
941 }
942
943 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
945 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
946 return 0;
947 }
948 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
949 match stream {
950 StreamKind::Stdout => guard.stdout_history_bytes,
951 StreamKind::Stderr => guard.stderr_history_bytes,
952 }
953 }
954
955 pub fn captured_combined_bytes(&self) -> usize {
957 self.shared
958 .queues
959 .lock()
960 .expect("queue mutex poisoned")
961 .combined_history_bytes
962 }
963
964 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
966 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
967 return 0;
968 }
969 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
970 match stream {
971 StreamKind::Stdout => {
972 let released = guard.stdout_history_bytes;
973 guard.stdout_history.clear();
974 guard.stdout_raw.clear();
975 guard.stdout_history_bytes = 0;
976 released
977 }
978 StreamKind::Stderr => {
979 let released = guard.stderr_history_bytes;
980 guard.stderr_history.clear();
981 guard.stderr_raw.clear();
982 guard.stderr_history_bytes = 0;
983 released
984 }
985 }
986 }
987
988 pub fn clear_captured_combined(&self) -> usize {
990 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
991 let released = guard.combined_history_bytes;
992 guard.combined_history.clear();
993 guard.combined_history_bytes = 0;
994 released
995 }
996
997 fn build_command(&self) -> Command {
998 let mut command = match &self.config.command {
999 CommandSpec::Shell(command) => shell_command(command),
1000 CommandSpec::Argv(argv) => {
1001 let mut command = Command::new(&argv[0]);
1002 if argv.len() > 1 {
1003 command.args(&argv[1..]);
1004 }
1005 command
1006 }
1007 };
1008 if let Some(cwd) = &self.config.cwd {
1009 command.current_dir(cwd);
1010 }
1011 if let Some(env) = &self.config.env {
1012 command.env_clear();
1013 command.envs(env.iter().map(|(k, v)| (k, v)));
1014 }
1015 #[cfg(windows)]
1016 {
1017 use std::os::windows::process::CommandExt;
1018
1019 let flags = windows_creation_flags(
1024 self.config.creationflags,
1025 self.config.create_process_group,
1026 self.config.nice,
1027 );
1028 if flags != 0 {
1029 command.creation_flags(flags);
1030 }
1031 }
1032 #[cfg(unix)]
1033 {
1034 let create_process_group = self.config.create_process_group;
1035 let nice = self.config.nice;
1036
1037 if create_process_group || nice.is_some() {
1038 use std::os::unix::process::CommandExt;
1039
1040 unsafe {
1041 command.pre_exec(move || {
1042 if create_process_group && libc::setpgid(0, 0) == -1 {
1043 return Err(std::io::Error::last_os_error());
1044 }
1045 if let Some(nice) = nice {
1046 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
1047 if result == -1 {
1048 return Err(std::io::Error::last_os_error());
1049 }
1050 }
1051 Ok(())
1052 });
1053 }
1054 }
1055 }
1056 command
1057 }
1058
1059 fn spawn_reader<R>(
1060 &self,
1061 pipe: R,
1062 source_stream: StreamKind,
1063 visible_stream: StreamKind,
1064 on_pipe_done: Box<dyn FnOnce() + Send>,
1065 ) where
1066 R: Read + Send + 'static,
1067 {
1068 let shared = Arc::clone(&self.shared);
1069 thread::spawn(move || {
1070 let mut reader = pipe;
1071 let mut chunk = vec![0_u8; 65536];
1072 let mut pending = Vec::new();
1073
1074 loop {
1075 match reader.read(&mut chunk) {
1076 Ok(0) => break,
1077 Ok(n) => {
1078 append_raw(&shared, visible_stream, &chunk[..n]);
1079 let lines = feed_chunk(&mut pending, &chunk[..n]);
1080 emit_lines(&shared, visible_stream, lines);
1081 }
1082 Err(_) => break,
1083 }
1084 }
1085
1086 if !pending.is_empty() {
1087 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1088 }
1089
1090 on_pipe_done();
1095 drop(reader);
1096
1097 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1098 match source_stream {
1099 StreamKind::Stdout => guard.stdout_closed = true,
1100 StreamKind::Stderr => guard.stderr_closed = true,
1101 }
1102 shared.condvar.notify_all();
1103 });
1104 }
1105
1106 #[cfg(windows)]
1107 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1108 let handles = Arc::clone(&self.capture_pipe_handles);
1109 Box::new(move || {
1110 let mut guard = handles.lock().expect("capture pipe handles mutex poisoned");
1111 match stream {
1112 StreamKind::Stdout => guard.stdout = None,
1113 StreamKind::Stderr => guard.stderr = None,
1114 }
1115 })
1116 }
1117
1118 #[cfg(not(windows))]
1119 fn pipe_done_callback(&self, _stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1120 Box::new(|| {})
1121 }
1122
1123 #[cfg(windows)]
1129 fn cancel_capture_io(&self) {
1130 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1131 use winapi::shared::ntdef::HANDLE;
1132 use winapi::um::ioapiset::CancelIoEx;
1133 let guard = self
1134 .capture_pipe_handles
1135 .lock()
1136 .expect("capture pipe handles mutex poisoned");
1137 if let Some(h) = guard.stdout {
1138 unsafe {
1145 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1146 }
1147 }
1148 if let Some(h) = guard.stderr {
1149 unsafe {
1150 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1151 }
1152 }
1153 }
1154
1155 fn set_returncode(&self, code: i32) {
1156 self.shared.returncode.store(code as i64, Ordering::Release);
1157 self.shared.condvar.notify_all();
1158 }
1159
1160 fn wait_for_capture_completion_impl(&self) {
1161 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait_for_capture_completion");
1162 if !self.config.capture {
1163 return;
1164 }
1165
1166 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1167 while !(guard.stdout_closed && guard.stderr_closed) {
1168 guard = self
1169 .shared
1170 .condvar
1171 .wait(guard)
1172 .expect("queue mutex poisoned");
1173 }
1174 }
1175
1176 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1184 crate::rp_rust_debug_scope!(
1185 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1186 );
1187 if !self.config.capture {
1188 return true;
1189 }
1190
1191 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1192 while !(guard.stdout_closed && guard.stderr_closed) {
1193 let now = Instant::now();
1194 if now >= deadline {
1195 guard.stdout_closed = true;
1196 guard.stderr_closed = true;
1197 self.shared.condvar.notify_all();
1198 return false;
1199 }
1200 let (next_guard, result) = self
1201 .shared
1202 .condvar
1203 .wait_timeout(guard, deadline - now)
1204 .expect("queue mutex poisoned");
1205 guard = next_guard;
1206 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1207 guard.stdout_closed = true;
1208 guard.stderr_closed = true;
1209 self.shared.condvar.notify_all();
1210 return false;
1211 }
1212 }
1213 true
1214 }
1215}
1216
1217fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1218 if lines.is_empty() {
1219 return;
1220 }
1221 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1222 for line in lines {
1223 let line_len = line.len();
1224 match stream {
1225 StreamKind::Stdout => {
1226 guard.stdout_history_bytes += line_len;
1227 guard.stdout_history.push_back(line.clone());
1228 guard.stdout_queue.push_back(line.clone());
1229 }
1230 StreamKind::Stderr => {
1231 guard.stderr_history_bytes += line_len;
1232 guard.stderr_history.push_back(line.clone());
1233 guard.stderr_queue.push_back(line.clone());
1234 }
1235 }
1236 let event = StreamEvent { stream, line };
1237 guard.combined_history_bytes += line_len;
1238 guard.combined_history.push_back(event.clone());
1239 guard.combined_queue.push_back(event);
1240 }
1241 shared.condvar.notify_all();
1242}
1243
1244fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) {
1245 if chunk.is_empty() {
1246 return;
1247 }
1248 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1249 match stream {
1250 StreamKind::Stdout => guard.stdout_raw.extend_from_slice(chunk),
1251 StreamKind::Stderr => guard.stderr_raw.extend_from_slice(chunk),
1252 }
1253}
1254
1255pub fn run_command(
1261 mut config: ProcessConfig,
1262 timeout: Option<Duration>,
1263) -> Result<RunOutput, ProcessError> {
1264 config.capture = true;
1265 let process = NativeProcess::new(config);
1266 process.start()?;
1267
1268 let exit_code = match process.wait(timeout) {
1269 Ok(code) => code,
1270 Err(ProcessError::Timeout) => {
1271 match process.kill() {
1272 Ok(()) | Err(ProcessError::NotRunning) => {}
1273 Err(error) => return Err(error),
1274 }
1275 return Err(ProcessError::Timeout);
1276 }
1277 Err(error) => return Err(error),
1278 };
1279
1280 Ok(RunOutput {
1281 stdout: process.captured_stdout_raw(),
1282 stderr: process.captured_stderr_raw(),
1283 exit_code,
1284 })
1285}
1286
1287pub(crate) fn shell_command(command: &str) -> Command {
1288 #[cfg(windows)]
1289 {
1290 use std::os::windows::process::CommandExt;
1291
1292 let mut cmd = Command::new("cmd");
1293 cmd.raw_arg("/D /S /C \"");
1294 cmd.raw_arg(command);
1295 cmd.raw_arg("\"");
1296 cmd
1297 }
1298 #[cfg(not(windows))]
1299 {
1300 let mut cmd = Command::new("sh");
1301 cmd.arg("-lc").arg(command);
1302 cmd
1303 }
1304}
1305
1306#[cfg(test)]
1307mod tests;