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