1use std::collections::VecDeque;
10use std::io::Read;
11use std::process::{Child, ChildStdin, 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, ProcessWatchEmitter};
18
19pub(crate) use running_process_platform_internal::platform;
20
21#[cfg(feature = "async-process")]
22mod async_process;
23#[cfg(feature = "async-process")]
24mod blocking_island;
25#[cfg(feature = "async-process")]
26pub use blocking_island::dispatch_blocking as blocking_island_dispatch;
27pub mod console_detect;
28pub mod containment;
29mod descendant_monitor;
30pub mod env_vars;
31pub mod environment;
32mod helpers;
33#[cfg(feature = "async-process")]
34mod process_runtime;
35pub mod window_icon;
36pub mod observer;
41#[cfg(feature = "originator-scan")]
42pub mod originator;
43pub mod output_log;
44#[cfg(feature = "client")]
48pub mod proto {
50 pub use running_process_protocol::daemon;
52}
53
54#[cfg(feature = "client")]
55pub mod client;
56
57#[cfg(feature = "client")]
62pub mod broker;
63
64#[cfg(feature = "client")]
68pub mod content_hash;
69
70#[cfg(feature = "probe")]
73pub mod probe;
74
75#[cfg(feature = "client")]
81pub mod maintenance;
82
83#[cfg(feature = "client")]
84pub mod cleanup;
85
86#[cfg(feature = "client")]
90pub mod boot_autostart;
91
92#[cfg(feature = "client")]
97pub mod runpm_config;
98
99#[cfg(feature = "test-support")]
104pub mod test_support;
105
106#[cfg(all(feature = "telemetry", not(feature = "daemon")))]
115#[path = "daemon/telemetry.rs"]
116pub mod telemetry;
117
118#[cfg(all(feature = "telemetry", feature = "daemon"))]
119pub use daemon::telemetry;
120
121#[cfg(all(feature = "telemetry", feature = "daemon"))]
131const _: fn(crate::telemetry::TeeHandle) -> daemon::telemetry::TeeHandle = |handle| handle;
132
133#[cfg(feature = "daemon")]
136pub mod daemon;
138pub mod process_tree;
143#[cfg(feature = "pty")]
144pub mod pty;
146mod public_symbols;
147mod rust_debug;
148pub mod spawn;
149pub mod systemd_killmode;
150#[cfg(feature = "terminal-graphics")]
151pub mod terminal_graphics;
152mod types;
153#[cfg(unix)]
154mod unix;
155#[cfg(windows)]
156mod windows;
157
158#[cfg(feature = "async-process")]
159pub use async_process::{AsyncCapturedOutput, AsyncProcess, AsyncProcessBuilder, AsyncStdio};
160pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
161pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
162#[cfg(feature = "client")]
164pub use content_hash::blake3_file;
165pub use observer::{
166 CapabilitySupport, CaptureSource, CategoryCapability, DumpResult, EventCategory,
167 ObservationGrade, ObservationPolicy, ObserverCapabilities, ObserverConfig, ObserverEvent,
168 ObserverEventKind, ObserverSubscriber, ProcessEvent, ProcessEventKind, ProcessIdentity,
169 ProcessObservation, ProcessObservationCapabilities, ProcessObservationError, ProcessWatch,
170 ProcessWatchConfigurationError, ProcessWatchCursor, ProcessWatchGap, ProcessWatchLoss,
171 ProcessWatchMatch, ProcessWatchRead, ProcessWatchSubscriber, StackCapture, StackDump,
172};
173#[cfg(feature = "originator-scan")]
174pub use originator::{
175 find_declared_daemon_pids, find_processes_by_originator, OriginatorProcessInfo,
176};
177pub use output_log::{
178 CursorRead, OutputCursor, OutputLog, OutputRecord, SharedOutputCursor, SharedOutputLog,
179};
180#[doc(hidden)]
183pub use running_process_platform_internal::platform::executable as platform_executable;
184#[cfg(target_os = "linux")]
185pub use running_process_platform_internal::platform::process::current_executable_build_id;
186pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
187pub use spawn::{
188 spawn, spawn_daemon, spawn_daemon_breaking_away_from_job,
189 spawn_daemon_breaking_away_with_env_policy, spawn_daemon_with_clear_env,
190 spawn_daemon_with_env_policy, spawn_daemon_with_stdio, spawn_daemon_with_stdio_and_env_policy,
191 spawn_with_env_policy, DaemonChild, DaemonStdio, DaemonStdioSource, EnvironmentPolicy,
192 SpawnStdio, SpawnedChild, StdioSource, DAEMON_MARKER_ENV_VAR,
193};
194#[cfg(feature = "client-async")]
195pub use spawn::{spawn_tokio, TokioSpawnOptions};
196#[cfg(feature = "terminal-graphics")]
197pub use terminal_graphics::{
198 current_terminal_capabilities, current_terminal_capabilities_with_timeout,
199 detect_terminal_capabilities, CapabilityStatus, EvidenceStrength, GraphicsCapability,
200 GraphicsProtocol, TerminalCapabilities, TerminalCapabilityInput, TerminalGraphicsCapabilities,
201 TerminalProbeEvidence,
202};
203pub use types::{
204 CommandSpec, ProcessConfig, ProcessError, ReadStatus, RunOutput, StderrMode, StdinMode,
205 StreamEvent, StreamKind,
206};
207pub use window_icon::{
208 host_icon_support, icon_support, set_host_icon, set_icon, IconError, IconScope, IconSource,
209 IconSupport, StockIcon,
210};
211
212#[cfg(unix)]
213pub(crate) use helpers::{child_try_wait_error_is_retryable, poll_mutex_until};
214pub(crate) use helpers::{exit_code, feed_chunk, kill_drain_deadline, log_spawned_child_pid};
215#[cfg(unix)]
216pub use unix::{unix_set_priority, unix_signal_process, unix_signal_process_group, UnixSignal};
217#[cfg(windows)]
218pub(crate) use windows::{assign_child_to_windows_kill_on_close_job_impl, WindowsJobHandle};
219
220#[macro_export]
221macro_rules! rp_rust_debug_scope {
223 ($label:expr) => {
224 let _running_process_rust_debug_scope =
225 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
226 };
227}
228
229#[derive(Default)]
230struct QueueState {
231 stdout_queue: VecDeque<Vec<u8>>,
232 stderr_queue: VecDeque<Vec<u8>>,
233 combined_queue: VecDeque<StreamEvent>,
234 stdout_history: VecDeque<Vec<u8>>,
235 stderr_history: VecDeque<Vec<u8>>,
236 combined_history: VecDeque<StreamEvent>,
237 stdout_raw: Vec<u8>,
238 stderr_raw: Vec<u8>,
239 stdout_history_bytes: usize,
240 stderr_history_bytes: usize,
241 combined_history_bytes: usize,
242 stdout_closed: bool,
243 stderr_closed: bool,
244}
245
246const RETURNCODE_NOT_SET: i64 = i64::MIN;
248
249struct SharedState {
250 queues: Mutex<QueueState>,
251 condvar: Condvar,
252 capture_limit: Option<usize>,
253 capture_overflowed: AtomicBool,
254 active_capture_readers: std::sync::atomic::AtomicUsize,
255 returncode: AtomicI64,
258 observer: Option<ObserverEmitter>,
263 observer_exit_emitted: AtomicBool,
266}
267
268struct ChildState {
269 child: ChildHandle,
270 #[cfg(windows)]
271 _job: WindowsJobHandle,
272}
273
274enum ChildHandle {
275 Standard(Child),
276 ExactTrace(running_process_platform_internal::platform::process::TracedChild),
277}
278
279impl ChildHandle {
280 fn id(&self) -> u32 {
281 match self {
282 Self::Standard(child) => child.id(),
283 Self::ExactTrace(child) => child.id(),
284 }
285 }
286
287 fn try_wait_code(&mut self) -> std::io::Result<Option<i32>> {
288 match self {
289 Self::Standard(child) => child.try_wait().map(|status| status.map(exit_code)),
290 Self::ExactTrace(child) => child.try_wait_code(),
291 }
292 }
293
294 fn kill(&mut self) -> std::io::Result<()> {
295 match self {
296 Self::Standard(child) => child.kill(),
297 Self::ExactTrace(child) => child.kill(),
298 }
299 }
300
301 fn take_stdin(&mut self) -> Option<ChildStdin> {
302 match self {
303 Self::Standard(child) => child.stdin.take(),
304 Self::ExactTrace(child) => child.take_stdin(),
305 }
306 }
307
308 fn take_stdout(&mut self) -> Option<std::process::ChildStdout> {
309 match self {
310 Self::Standard(child) => child.stdout.take(),
311 Self::ExactTrace(child) => child.take_stdout(),
312 }
313 }
314
315 fn take_stderr(&mut self) -> Option<std::process::ChildStderr> {
316 match self {
317 Self::Standard(child) => child.stderr.take(),
318 Self::ExactTrace(child) => child.take_stderr(),
319 }
320 }
321
322 #[cfg(windows)]
323 fn wait_code(&mut self) -> std::io::Result<i32> {
324 match self {
325 Self::Standard(child) => child.wait().map(exit_code),
326 Self::ExactTrace(child) => child.wait_code(),
327 }
328 }
329}
330
331#[cfg(test)]
332#[derive(Debug, Eq, PartialEq)]
333enum CapturePollAction {
334 Wait,
335 Read,
336 Cancel,
337}
338
339#[cfg(test)]
340fn capture_poll_action(capture_revents: i16, wake_revents: i16) -> CapturePollAction {
341 if wake_revents != 0 {
342 CapturePollAction::Cancel
343 } else if capture_revents != 0 {
344 CapturePollAction::Read
345 } else {
346 CapturePollAction::Wait
347 }
348}
349
350fn cleanup_child_after_start_error(child: ChildHandle) {
351 match child {
352 ChildHandle::Standard(mut child) => {
353 let _ = child.kill();
354 thread::spawn(move || {
357 let _ = child.wait();
358 });
359 }
360 ChildHandle::ExactTrace(mut child) => {
361 let _ = child.kill();
363 }
364 }
365}
366
367impl SharedState {
368 #[cfg(test)]
369 fn new(capture: bool) -> Self {
370 Self::with_observer_and_limit(capture, None, None)
371 }
372
373 fn with_observer_and_limit(
374 capture: bool,
375 observer: Option<ObserverEmitter>,
376 capture_limit: Option<usize>,
377 ) -> Self {
378 let queues = QueueState {
379 stdout_closed: !capture,
380 stderr_closed: !capture,
381 ..QueueState::default()
382 };
383 Self {
384 queues: Mutex::new(queues),
385 condvar: Condvar::new(),
386 capture_limit,
387 capture_overflowed: AtomicBool::new(false),
388 active_capture_readers: std::sync::atomic::AtomicUsize::new(0),
389 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
390 observer,
391 observer_exit_emitted: AtomicBool::new(false),
392 }
393 }
394
395 fn emit_exited(&self, pid: u32, exit_code: i32) {
398 let Some(emitter) = self.observer.as_ref() else {
399 return;
400 };
401 if self
402 .observer_exit_emitted
403 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
404 .is_ok()
405 {
406 emitter.emit_exited(pid, exit_code);
407 }
408 }
409}
410
411pub struct NativeProcess {
418 config: ProcessConfig,
419 command_override: Mutex<Option<Command>>,
420 child: Arc<Mutex<Option<ChildState>>>,
421 stdin: Mutex<Option<ChildStdin>>,
422 shared: Arc<SharedState>,
423 process_watch: Option<Arc<ProcessWatchEmitter>>,
424 #[cfg(test)]
425 stdin_write_active: AtomicBool,
426 capture_cancellation:
427 Arc<running_process_platform_internal::platform::process::CaptureCancellation>,
428}
429
430impl NativeProcess {
431 pub fn new(config: ProcessConfig) -> Self {
437 Self::new_with_options(config, None, None, None, None)
438 }
439
440 pub fn with_observer(
453 config: ProcessConfig,
454 observer: crate::observer::ObserverConfig,
455 ) -> (Self, ObserverSubscriber) {
456 let (emitter, subscriber) = ObserverEmitter::new(observer);
457 let process = Self::new_with_options(config, Some(emitter), None, None, None);
458 (process, subscriber)
459 }
460
461 pub fn with_observer_and_command(
476 command: Command,
477 config: ProcessConfig,
478 observer: crate::observer::ObserverConfig,
479 ) -> (Self, ObserverSubscriber) {
480 let (emitter, subscriber) = ObserverEmitter::new(observer);
481 let process = Self::new_with_options(config, Some(emitter), None, Some(command), None);
482 (process, subscriber)
483 }
484
485 pub fn with_process_watches(
488 config: ProcessConfig,
489 watches: Vec<ProcessWatch>,
490 policy: ObservationPolicy,
491 ) -> Result<(Self, ProcessWatchSubscriber), ProcessObservationError> {
492 let (emitter, subscriber) = ProcessWatchEmitter::new(watches, policy)?;
493 let process = Self::new_with_options(config, None, None, None, Some(emitter));
494 Ok((process, subscriber))
495 }
496
497 pub fn process_observation_capabilities() -> ProcessObservationCapabilities {
499 ProcessObservationCapabilities::current()
500 }
501
502 fn new_with_capture_limit(config: ProcessConfig, capture_limit: usize) -> Self {
503 Self::new_with_options(config, None, Some(capture_limit), None, None)
504 }
505
506 fn new_with_command_capture_limit(
507 command: Command,
508 config: ProcessConfig,
509 capture_limit: usize,
510 ) -> Self {
511 Self::new_with_options(config, None, Some(capture_limit), Some(command), None)
512 }
513
514 fn new_with_options(
515 config: ProcessConfig,
516 observer: Option<ObserverEmitter>,
517 capture_limit: Option<usize>,
518 command_override: Option<Command>,
519 process_watch: Option<Arc<ProcessWatchEmitter>>,
520 ) -> Self {
521 let shared = SharedState::with_observer_and_limit(config.capture, observer, capture_limit);
522 Self {
523 shared: Arc::new(shared),
524 process_watch,
525 command_override: Mutex::new(command_override),
526 child: Arc::new(Mutex::new(None)),
527 stdin: Mutex::new(None),
528 #[cfg(test)]
529 stdin_write_active: AtomicBool::new(false),
530 config,
531 capture_cancellation: Arc::new(Default::default()),
532 }
533 }
534
535 #[inline(never)]
537 pub fn start(&self) -> Result<(), ProcessError> {
542 public_symbols::rp_native_process_start_public(self)
543 }
544
545 fn start_impl(&self) -> Result<(), ProcessError> {
546 crate::rp_rust_debug_scope!("running_process::NativeProcess::start");
547 let mut guard = self.child.lock().expect("child mutex poisoned");
548 if guard.is_some() {
549 return Err(ProcessError::AlreadyStarted);
550 }
551
552 let mut command = self.build_command();
553 let exact_trace = self
554 .process_watch
555 .as_ref()
556 .is_some_and(|watch| watch.uses_exact_trace());
557 match self.config.stdin_mode {
558 StdinMode::Inherit => {}
559 StdinMode::Piped => {
560 command.stdin(Stdio::piped());
561 }
562 StdinMode::Null => {
563 command.stdin(Stdio::null());
564 }
565 }
566 if self.config.capture {
567 command.stdout(Stdio::piped());
568 command.stderr(Stdio::piped());
569 }
570
571 let mut child = if exact_trace {
572 let event_watch = Arc::clone(self.process_watch.as_ref().expect("exact watch checked"));
573 let completion_watch = Arc::clone(&event_watch);
574 match running_process_platform_internal::platform::process::start_exact_trace(
575 command,
576 Box::new(move |event| event_watch.emit_exact(event)),
577 Box::new(move || completion_watch.close()),
578 ) {
579 Ok(child) => ChildHandle::ExactTrace(child),
580 Err(error) => {
581 if let Some(watch) = self.process_watch.as_ref() {
582 watch.close();
583 }
584 return Err(ProcessError::Spawn(error));
585 }
586 }
587 } else {
588 ChildHandle::Standard(command.spawn().map_err(ProcessError::Spawn)?)
589 };
590 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
591 if let Some(emitter) = self.shared.observer.as_ref() {
594 emitter.emit_started(child.id());
595 }
596 #[cfg(windows)]
601 let job = {
602 let descendant_sink = self
603 .shared
604 .observer
605 .as_ref()
606 .and_then(|e| e.descendant_sink());
607 let job_result = match &child {
608 ChildHandle::Standard(standard_child) => {
609 let direct_pid = standard_child.id();
610 public_symbols::rp_assign_child_to_windows_kill_on_close_job_with_observer_public(
611 standard_child,
612 descendant_sink,
613 self.process_watch.clone(),
614 direct_pid,
615 self.config.address_space_limit_bytes,
616 )
617 }
618 ChildHandle::ExactTrace(_) => unreachable!("Windows exact tracing is unavailable"),
619 };
620 match job_result {
621 Ok(job) => job,
622 Err(error) => {
623 if let Some(watch) = self.process_watch.as_ref() {
624 watch.close();
625 }
626 cleanup_child_after_start_error(child);
627 return Err(ProcessError::Spawn(error));
628 }
629 }
630 };
631 if !exact_trace {
632 descendant_monitor::start(
633 child.id(),
634 self.shared.observer.as_ref(),
635 self.process_watch.as_ref(),
636 );
637 }
638 if self.config.capture {
639 let stdout = child.take_stdout().expect("stdout pipe missing");
640 let stderr = child.take_stderr().expect("stderr pipe missing");
641 let stdout =
642 match running_process_platform_internal::platform::process::prepare_capture_reader(
643 stdout,
644 &self.capture_cancellation,
645 running_process_platform_internal::platform::process::CaptureStream::Stdout,
646 ) {
647 Ok(stdout) => stdout,
648 Err(error) => {
649 cleanup_child_after_start_error(child);
650 return Err(ProcessError::Spawn(error));
651 }
652 };
653 let stderr =
654 match running_process_platform_internal::platform::process::prepare_capture_reader(
655 stderr,
656 &self.capture_cancellation,
657 running_process_platform_internal::platform::process::CaptureStream::Stderr,
658 ) {
659 Ok(stderr) => stderr,
660 Err(error) => {
661 running_process_platform_internal::platform::process::capture_reader_done(
662 &self.capture_cancellation,
663 running_process_platform_internal::platform::process::CaptureStream::Stdout,
664 );
665 cleanup_child_after_start_error(child);
666 return Err(ProcessError::Spawn(error));
667 }
668 };
669 self.spawn_reader(
670 stdout,
671 StreamKind::Stdout,
672 StreamKind::Stdout,
673 self.pipe_done_callback(StreamKind::Stdout),
674 );
675 self.spawn_reader(
676 stderr,
677 StreamKind::Stderr,
678 match self.config.stderr_mode {
679 StderrMode::Stdout => StreamKind::Stdout,
680 StderrMode::Pipe => StreamKind::Stderr,
681 },
682 self.pipe_done_callback(StreamKind::Stderr),
683 );
684 }
685 *self.stdin.lock().expect("stdin mutex poisoned") = child.take_stdin();
686 *guard = Some(ChildState {
687 child,
688 #[cfg(windows)]
689 _job: job,
690 });
691 drop(guard);
692 self.spawn_exit_waiter();
693 Ok(())
694 }
695
696 fn spawn_exit_waiter(&self) {
699 let child = Arc::clone(&self.child);
700 let shared = Arc::clone(&self.shared);
701 let capture = self.config.capture;
702 let capture_cancellation = Arc::clone(&self.capture_cancellation);
703 thread::spawn(move || {
704 loop {
705 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
706 return;
707 }
708 let exited = {
709 let mut guard = child.lock().expect("child mutex poisoned");
710 if let Some(child_state) = guard.as_mut() {
711 let pid = child_state.child.id();
712 match child_state.child.try_wait_code() {
713 Ok(Some(code)) => {
714 shared.returncode.store(code as i64, Ordering::Release);
715 shared.emit_exited(pid, code);
719 shared.condvar.notify_all();
720 true
721 }
722 Ok(None) => false,
723 Err(_error) => {
724 #[cfg(unix)]
725 if child_try_wait_error_is_retryable(&_error) {
726 false
727 } else {
728 return;
729 }
730 #[cfg(windows)]
731 return;
732 }
733 }
734 } else {
735 return;
736 }
737 };
738 if exited {
739 if capture {
754 let drained = finalize_capture_completion(&shared, kill_drain_deadline());
755 if !drained {
756 running_process_platform_internal::platform::process::cancel_capture_reader(
757 &capture_cancellation,
758 );
759 }
760 }
761 return;
767 }
768 thread::sleep(Duration::from_millis(10));
774 }
775 });
776 }
777
778 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
780 if self.child.lock().expect("child mutex poisoned").is_none() {
781 return Err(ProcessError::NotRunning);
782 }
783 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
784 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
785 use std::io::Write;
786 #[cfg(test)]
787 self.stdin_write_active.store(true, Ordering::Release);
788 let write_result = stdin.write_all(data);
789 #[cfg(test)]
790 self.stdin_write_active.store(false, Ordering::Release);
791 write_result.map_err(ProcessError::Io)?;
792 stdin.flush().map_err(ProcessError::Io)?;
793 drop(guard.take());
794 Ok(())
795 }
796
797 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
802 if self.child.lock().expect("child mutex poisoned").is_none() {
803 return Err(ProcessError::NotRunning);
804 }
805 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
806 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
807 use std::io::Write;
808 #[cfg(test)]
809 self.stdin_write_active.store(true, Ordering::Release);
810 let write_result = stdin.write_all(data);
811 #[cfg(test)]
812 self.stdin_write_active.store(false, Ordering::Release);
813 write_result.map_err(ProcessError::Io)?;
814 stdin.flush().map_err(ProcessError::Io)?;
815 Ok(())
816 }
817
818 pub fn close_stdin(&self) -> Result<(), ProcessError> {
821 if self.child.lock().expect("child mutex poisoned").is_none() {
822 return Err(ProcessError::NotRunning);
823 }
824 drop(self.stdin.lock().expect("stdin mutex poisoned").take());
825 Ok(())
826 }
827
828 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
832 if let Some(code) = self.returncode() {
834 return Ok(Some(code));
835 }
836 let mut guard = self.child.lock().expect("child mutex poisoned");
837 let Some(child_state) = guard.as_mut() else {
838 return Ok(self.returncode());
839 };
840 let pid = child_state.child.id();
841 let child = &mut child_state.child;
842 let status = child.try_wait_code().map_err(ProcessError::Io)?;
843 if let Some(code) = status {
844 self.set_returncode(code);
845 self.shared.emit_exited(pid, code);
846 return Ok(Some(code));
847 }
848 Ok(None)
849 }
850
851 #[inline(never)]
853 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
858 public_symbols::rp_native_process_wait_public(self, timeout)
859 }
860
861 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
862 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
863 if self.child.lock().expect("child mutex poisoned").is_none() {
864 return self.returncode().ok_or(ProcessError::NotRunning);
865 }
866 if let Some(code) = self.returncode() {
868 self.finish_capture_drain();
869 return Ok(code);
870 }
871 let start = Instant::now();
872 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
873 loop {
874 let rc = self.shared.returncode.load(Ordering::Acquire);
876 if rc != RETURNCODE_NOT_SET {
877 drop(guard);
878 let code = rc as i32;
879 self.finish_capture_drain();
880 return Ok(code);
881 }
882 if let Some(limit) = timeout {
883 let elapsed = start.elapsed();
884 if elapsed >= limit {
885 return Err(ProcessError::Timeout);
886 }
887 let remaining = limit - elapsed;
888 let wait_time = remaining.min(Duration::from_millis(50));
890 guard = self
891 .shared
892 .condvar
893 .wait_timeout(guard, wait_time)
894 .expect("queue mutex poisoned")
895 .0;
896 } else {
897 guard = self
899 .shared
900 .condvar
901 .wait_timeout(guard, Duration::from_millis(50))
902 .expect("queue mutex poisoned")
903 .0;
904 }
905 }
906 }
907
908 #[inline(never)]
910 pub fn kill(&self) -> Result<(), ProcessError> {
912 public_symbols::rp_native_process_kill_public(self)
913 }
914
915 fn kill_impl(&self) -> Result<(), ProcessError> {
916 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
917 #[cfg(windows)]
918 {
919 let mut guard = self.child.lock().expect("child mutex poisoned");
920 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
921 let pid = child.id();
922 child.kill().map_err(ProcessError::Io)?;
923 let code = child.wait_code().map_err(ProcessError::Io)?;
924 self.set_returncode(code);
925 self.shared.emit_exited(pid, code);
928 }
929 #[cfg(unix)]
930 {
931 let deadline = kill_drain_deadline();
932 let (pid, already_reaped) = {
933 let mut state = self.child.lock().expect("child mutex poisoned");
934 let child = &mut state.as_mut().ok_or(ProcessError::NotRunning)?.child;
935 let pid = child.id();
936 if let Some(code) = child.try_wait_code().map_err(ProcessError::Io)? {
937 (pid, Some(code))
938 } else {
939 let group_signaled = self.config.create_process_group
940 && unix_signal_process_group(pid as i32, UnixSignal::Kill).is_ok();
941 if !group_signaled {
942 child.kill().map_err(ProcessError::Io)?;
943 }
944 (pid, None)
945 }
946 };
947
948 self.cancel_capture_io();
952 let reaped = already_reaped.or_else(|| {
953 let reap_result =
954 poll_mutex_until(&self.child, deadline, Duration::from_millis(10), |state| {
955 match state.as_mut() {
956 Some(child) => child.child.try_wait_code(),
957 None => Ok(None),
958 }
959 });
960 match reap_result {
961 Ok(Some(code)) => Some(code),
962 _ => None,
963 }
964 });
965 if let Some(code) = reaped {
966 self.set_returncode(code);
967 self.shared.emit_exited(pid, code);
968 }
969 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
970 self, deadline,
971 );
972 Ok(())
973 }
974 #[cfg(windows)]
975 {
976 self.cancel_capture_io();
984 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
995 self,
996 kill_drain_deadline(),
997 );
998 Ok(())
999 }
1000 }
1001
1002 pub fn terminate(&self) -> Result<(), ProcessError> {
1006 self.kill()
1007 }
1008
1009 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
1021 if !self.config.create_process_group {
1022 return Ok(());
1024 }
1025 let pid = self.pid().ok_or(ProcessError::NotRunning)?;
1026 running_process_platform_internal::platform::process::soft_terminate_process_group(pid)
1027 .map_err(ProcessError::Io)
1028 }
1029
1030 #[inline(never)]
1032 pub fn close(&self) -> Result<(), ProcessError> {
1034 public_symbols::rp_native_process_close_public(self)
1035 }
1036
1037 fn close_impl(&self) -> Result<(), ProcessError> {
1038 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
1039 if self.child.lock().expect("child mutex poisoned").is_none() {
1040 return Ok(());
1041 }
1042 if self.poll()?.is_none() {
1043 self.kill()?;
1044 } else {
1045 self.finish_capture_drain();
1046 }
1047 if let Some(watch) = self.process_watch.as_ref() {
1048 watch.close();
1049 }
1050 Ok(())
1051 }
1052
1053 pub fn pid(&self) -> Option<u32> {
1055 self.child
1056 .lock()
1057 .expect("child mutex poisoned")
1058 .as_ref()
1059 .map(|state| state.child.id())
1060 }
1061
1062 pub fn returncode(&self) -> Option<i32> {
1064 let v = self.shared.returncode.load(Ordering::Acquire);
1065 if v == RETURNCODE_NOT_SET {
1066 None
1067 } else {
1068 Some(v as i32)
1069 }
1070 }
1071
1072 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
1074 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1075 return false;
1076 }
1077 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1078 match stream {
1079 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
1080 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
1081 }
1082 }
1083
1084 pub fn has_pending_combined(&self) -> bool {
1086 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1087 !guard.combined_queue.is_empty()
1088 }
1089
1090 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
1092 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1093 return Vec::new();
1094 }
1095 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1096 let queue = match stream {
1097 StreamKind::Stdout => &mut guard.stdout_queue,
1098 StreamKind::Stderr => &mut guard.stderr_queue,
1099 };
1100 queue.drain(..).collect()
1101 }
1102
1103 pub fn drain_combined(&self) -> Vec<StreamEvent> {
1105 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1106 guard.combined_queue.drain(..).collect()
1107 }
1108
1109 pub fn read_stream(
1114 &self,
1115 stream: StreamKind,
1116 timeout: Option<Duration>,
1117 ) -> ReadStatus<Vec<u8>> {
1118 let deadline = timeout.map(|limit| Instant::now() + limit);
1119 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1120
1121 loop {
1122 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1123 return ReadStatus::Eof;
1124 }
1125
1126 let queue = match stream {
1127 StreamKind::Stdout => &mut guard.stdout_queue,
1128 StreamKind::Stderr => &mut guard.stderr_queue,
1129 };
1130 if let Some(line) = queue.pop_front() {
1131 return ReadStatus::Line(line);
1132 }
1133
1134 let closed = match stream {
1135 StreamKind::Stdout => {
1136 if self.config.stderr_mode == StderrMode::Stdout {
1137 guard.stdout_closed && guard.stderr_closed
1138 } else {
1139 guard.stdout_closed
1140 }
1141 }
1142 StreamKind::Stderr => guard.stderr_closed,
1143 };
1144 if closed {
1145 return ReadStatus::Eof;
1146 }
1147
1148 match deadline {
1149 Some(deadline) => {
1150 let now = Instant::now();
1151 if now >= deadline {
1152 return ReadStatus::Timeout;
1153 }
1154 let wait = deadline.saturating_duration_since(now);
1155 let result = self
1156 .shared
1157 .condvar
1158 .wait_timeout(guard, wait)
1159 .expect("queue mutex poisoned");
1160 guard = result.0;
1161 if result.1.timed_out() {
1162 return ReadStatus::Timeout;
1163 }
1164 }
1165 None => {
1166 guard = self
1167 .shared
1168 .condvar
1169 .wait(guard)
1170 .expect("queue mutex poisoned");
1171 }
1172 }
1173 }
1174 }
1175
1176 #[inline(never)]
1178 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1180 public_symbols::rp_native_process_read_combined_public(self, timeout)
1181 }
1182
1183 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1184 crate::rp_rust_debug_scope!("running_process::NativeProcess::read_combined");
1185 let deadline = timeout.map(|limit| Instant::now() + limit);
1186 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1187
1188 loop {
1189 if let Some(event) = guard.combined_queue.pop_front() {
1190 return ReadStatus::Line(event);
1191 }
1192 if guard.stdout_closed && guard.stderr_closed {
1193 return ReadStatus::Eof;
1194 }
1195
1196 match deadline {
1197 Some(deadline) => {
1198 let now = Instant::now();
1199 if now >= deadline {
1200 return ReadStatus::Timeout;
1201 }
1202 let wait = deadline.saturating_duration_since(now);
1203 let result = self
1204 .shared
1205 .condvar
1206 .wait_timeout(guard, wait)
1207 .expect("queue mutex poisoned");
1208 guard = result.0;
1209 if result.1.timed_out() {
1210 return ReadStatus::Timeout;
1211 }
1212 }
1213 None => {
1214 guard = self
1215 .shared
1216 .condvar
1217 .wait(guard)
1218 .expect("queue mutex poisoned");
1219 }
1220 }
1221 }
1222 }
1223
1224 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
1226 self.shared
1227 .queues
1228 .lock()
1229 .expect("queue mutex poisoned")
1230 .stdout_history
1231 .clone()
1232 .into_iter()
1233 .collect()
1234 }
1235
1236 fn captured_stdout_raw(&self) -> Vec<u8> {
1237 self.shared
1238 .queues
1239 .lock()
1240 .expect("queue mutex poisoned")
1241 .stdout_raw
1242 .clone()
1243 }
1244
1245 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
1247 if self.config.stderr_mode == StderrMode::Stdout {
1248 return Vec::new();
1249 }
1250 self.shared
1251 .queues
1252 .lock()
1253 .expect("queue mutex poisoned")
1254 .stderr_history
1255 .clone()
1256 .into_iter()
1257 .collect()
1258 }
1259
1260 fn captured_stderr_raw(&self) -> Vec<u8> {
1261 if self.config.stderr_mode == StderrMode::Stdout {
1262 return Vec::new();
1263 }
1264 self.shared
1265 .queues
1266 .lock()
1267 .expect("queue mutex poisoned")
1268 .stderr_raw
1269 .clone()
1270 }
1271
1272 pub fn captured_combined(&self) -> Vec<StreamEvent> {
1274 self.shared
1275 .queues
1276 .lock()
1277 .expect("queue mutex poisoned")
1278 .combined_history
1279 .clone()
1280 .into_iter()
1281 .collect()
1282 }
1283
1284 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
1286 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1287 return 0;
1288 }
1289 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1290 match stream {
1291 StreamKind::Stdout => guard.stdout_history_bytes,
1292 StreamKind::Stderr => guard.stderr_history_bytes,
1293 }
1294 }
1295
1296 pub fn captured_combined_bytes(&self) -> usize {
1298 self.shared
1299 .queues
1300 .lock()
1301 .expect("queue mutex poisoned")
1302 .combined_history_bytes
1303 }
1304
1305 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
1307 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1308 return 0;
1309 }
1310 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1311 match stream {
1312 StreamKind::Stdout => {
1313 let released = guard.stdout_history_bytes;
1314 guard.stdout_history.clear();
1315 guard.stdout_raw.clear();
1316 guard.stdout_history_bytes = 0;
1317 released
1318 }
1319 StreamKind::Stderr => {
1320 let released = guard.stderr_history_bytes;
1321 guard.stderr_history.clear();
1322 guard.stderr_raw.clear();
1323 guard.stderr_history_bytes = 0;
1324 released
1325 }
1326 }
1327 }
1328
1329 pub fn clear_captured_combined(&self) -> usize {
1331 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1332 let released = guard.combined_history_bytes;
1333 guard.combined_history.clear();
1334 guard.combined_history_bytes = 0;
1335 released
1336 }
1337
1338 fn build_command(&self) -> Command {
1339 let command_override = self
1340 .command_override
1341 .lock()
1342 .expect("command override mutex poisoned")
1343 .take();
1344 let mut command = match command_override {
1345 Some(command) => command,
1346 None => {
1347 let mut command = match &self.config.command {
1348 CommandSpec::Shell(command) => shell_command(command),
1349 CommandSpec::Argv(argv) => {
1350 let mut command = Command::new(&argv[0]);
1351 if argv.len() > 1 {
1352 command.args(&argv[1..]);
1353 }
1354 command
1355 }
1356 };
1357 if let Some(cwd) = &self.config.cwd {
1358 command.current_dir(cwd);
1359 }
1360 if let Some(env) = &self.config.env {
1361 command.env_clear();
1362 command.envs(env.iter().map(|(k, v)| (k, v)));
1363 }
1364 command
1365 }
1366 };
1367 running_process_platform_internal::platform::process::configure_process_command(
1368 &mut command,
1369 running_process_platform_internal::platform::process::ProcessCommandConfig {
1370 creation_flags: self.config.creationflags,
1371 create_process_group: self.config.create_process_group,
1372 nice: self.config.nice,
1373 address_space_limit_bytes: self.config.address_space_limit_bytes,
1374 },
1375 )
1376 .expect("platform command configuration must be valid");
1377 command
1378 }
1379
1380 fn spawn_reader<R>(
1381 &self,
1382 pipe: R,
1383 source_stream: StreamKind,
1384 visible_stream: StreamKind,
1385 on_pipe_done: Box<dyn FnOnce() + Send>,
1386 ) where
1387 R: Read + Send + 'static,
1388 {
1389 let shared = Arc::clone(&self.shared);
1390 shared.active_capture_readers.fetch_add(1, Ordering::AcqRel);
1391 thread::spawn(move || {
1392 let mut reader = pipe;
1393 let mut chunk = vec![0_u8; 65536];
1394 let mut pending = Vec::new();
1395
1396 loop {
1397 match reader.read(&mut chunk) {
1398 Ok(0) => break,
1399 Ok(n) => {
1400 if append_raw(&shared, visible_stream, &chunk[..n]) {
1401 let lines = feed_chunk(&mut pending, &chunk[..n]);
1402 emit_lines(&shared, visible_stream, lines);
1403 } else {
1404 pending.clear();
1405 }
1406 }
1407 Err(_) => break,
1408 }
1409 }
1410
1411 if !pending.is_empty() && !shared.capture_overflowed.load(Ordering::Acquire) {
1412 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1413 }
1414
1415 on_pipe_done();
1420 drop(reader);
1421
1422 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1423 match source_stream {
1424 StreamKind::Stdout => guard.stdout_closed = true,
1425 StreamKind::Stderr => guard.stderr_closed = true,
1426 }
1427 shared.active_capture_readers.fetch_sub(1, Ordering::AcqRel);
1428 shared.condvar.notify_all();
1429 });
1430 }
1431
1432 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1433 let cancellation = Arc::clone(&self.capture_cancellation);
1434 Box::new(move || {
1435 let stream = match stream {
1436 StreamKind::Stdout => {
1437 running_process_platform_internal::platform::process::CaptureStream::Stdout
1438 }
1439 StreamKind::Stderr => {
1440 running_process_platform_internal::platform::process::CaptureStream::Stderr
1441 }
1442 };
1443 running_process_platform_internal::platform::process::capture_reader_done(
1444 &cancellation,
1445 stream,
1446 );
1447 })
1448 }
1449
1450 fn cancel_capture_io(&self) {
1454 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1455 running_process_platform_internal::platform::process::cancel_capture_reader(
1456 &self.capture_cancellation,
1457 );
1458 }
1459
1460 fn set_returncode(&self, code: i32) {
1461 self.shared.returncode.store(code as i64, Ordering::Release);
1462 self.shared.condvar.notify_all();
1463 }
1464
1465 fn finish_capture_drain(&self) {
1475 self.finish_capture_drain_with_deadline(kill_drain_deadline());
1476 }
1477
1478 fn finish_capture_drain_with_deadline(&self, deadline: Instant) {
1479 let drained = self.wait_for_capture_completion_with_deadline_impl(deadline);
1480 if !drained {
1481 self.cancel_capture_io();
1482 }
1483 }
1484
1485 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1488 crate::rp_rust_debug_scope!(
1489 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1490 );
1491 if !self.config.capture {
1492 return true;
1493 }
1494 finalize_capture_completion(&self.shared, deadline)
1495 }
1496
1497 fn wait_for_capture_readers_with_deadline(&self, deadline: Instant) -> bool {
1498 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1499 while self.shared.active_capture_readers.load(Ordering::Acquire) != 0 {
1500 let now = Instant::now();
1501 if now >= deadline {
1502 return false;
1503 }
1504 let (next_guard, result) = self
1505 .shared
1506 .condvar
1507 .wait_timeout(guard, deadline - now)
1508 .expect("queue mutex poisoned");
1509 guard = next_guard;
1510 if result.timed_out() && self.shared.active_capture_readers.load(Ordering::Acquire) != 0
1511 {
1512 return false;
1513 }
1514 }
1515 true
1516 }
1517}
1518
1519fn finalize_capture_completion(shared: &SharedState, deadline: Instant) -> bool {
1531 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1532 while !(guard.stdout_closed && guard.stderr_closed) {
1533 let now = Instant::now();
1534 if now >= deadline {
1535 guard.stdout_closed = true;
1536 guard.stderr_closed = true;
1537 shared.condvar.notify_all();
1538 return false;
1539 }
1540 let (next_guard, result) = shared
1541 .condvar
1542 .wait_timeout(guard, deadline - now)
1543 .expect("queue mutex poisoned");
1544 guard = next_guard;
1545 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1546 guard.stdout_closed = true;
1547 guard.stderr_closed = true;
1548 shared.condvar.notify_all();
1549 return false;
1550 }
1551 }
1552 true
1553}
1554
1555fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1556 if lines.is_empty() || shared.capture_overflowed.load(Ordering::Acquire) {
1557 return;
1558 }
1559 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1560 if shared.capture_overflowed.load(Ordering::Acquire) {
1561 return;
1562 }
1563 for line in lines {
1564 let line_len = line.len();
1565 match stream {
1566 StreamKind::Stdout => {
1567 guard.stdout_history_bytes += line_len;
1568 guard.stdout_history.push_back(line.clone());
1569 guard.stdout_queue.push_back(line.clone());
1570 }
1571 StreamKind::Stderr => {
1572 guard.stderr_history_bytes += line_len;
1573 guard.stderr_history.push_back(line.clone());
1574 guard.stderr_queue.push_back(line.clone());
1575 }
1576 }
1577 let event = StreamEvent { stream, line };
1578 guard.combined_history_bytes += line_len;
1579 guard.combined_history.push_back(event.clone());
1580 guard.combined_queue.push_back(event);
1581 }
1582 shared.condvar.notify_all();
1583}
1584
1585fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) -> bool {
1586 if chunk.is_empty() {
1587 return true;
1588 }
1589 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1590 let accepted = match shared.capture_limit {
1591 Some(limit) => {
1592 let retained = guard
1593 .stdout_raw
1594 .len()
1595 .saturating_add(guard.stderr_raw.len());
1596 chunk.len().min(limit.saturating_sub(retained))
1597 }
1598 None => chunk.len(),
1599 };
1600 match stream {
1601 StreamKind::Stdout => guard.stdout_raw.extend_from_slice(&chunk[..accepted]),
1602 StreamKind::Stderr => guard.stderr_raw.extend_from_slice(&chunk[..accepted]),
1603 }
1604 if accepted != chunk.len() {
1605 shared.capture_overflowed.store(true, Ordering::Release);
1606 false
1607 } else {
1608 true
1609 }
1610}
1611
1612mod bounded;
1613pub use bounded::{run_command, run_command_bounded, run_std_command_bounded};
1614
1615pub(crate) fn shell_command(command: &str) -> Command {
1616 running_process_platform_internal::platform::process::compat_shell_command(command)
1617}
1618
1619#[cfg(test)]
1620mod tests;