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 use running_process_platform_internal::foreground;
21pub(crate) use running_process_platform_internal::platform;
22
23#[cfg(feature = "async-process")]
24mod async_process;
25#[cfg(feature = "async-process")]
26mod blocking_island;
27#[cfg(feature = "async-process")]
28pub use blocking_island::dispatch_blocking as blocking_island_dispatch;
29pub mod console_detect;
30pub mod containment;
31mod descendant_monitor;
32pub mod env_vars;
33pub mod environment;
34mod helpers;
35#[cfg(feature = "async-process")]
36mod process_runtime;
37#[cfg(feature = "window-icon")]
38pub mod window_icon;
39#[cfg(feature = "daemon-registration")]
50pub mod daemon_registration;
51#[cfg(feature = "daemon-registration")]
53pub mod daemon_registration_compat;
54#[cfg(feature = "daemon-registration-v2")]
61pub mod daemon_registration_v2;
62#[cfg(feature = "daemon-registration-v2")]
64pub mod daemon_registration_v2_compat;
65#[cfg(feature = "frame-v1-codec")]
71pub mod daemon_frame_v1;
72#[cfg(any(feature = "daemon-registration", feature = "daemon-registration-v2"))]
73pub(crate) mod daemon_registration_common;
74#[cfg(feature = "frame-v1-codec")]
80pub mod frame_v1;
81#[cfg(any(feature = "backend-identity", feature = "daemon-registration"))]
85#[path = "broker/host_identity.rs"]
86pub(crate) mod daemon_host_identity;
87pub mod observer;
88#[cfg(feature = "originator-scan")]
89pub mod originator;
90pub mod output_log;
91#[cfg(feature = "client")]
95pub mod proto {
97 pub use running_process_protocol::daemon;
99}
100
101#[cfg(feature = "client")]
102pub mod client;
103
104#[cfg(feature = "client")]
110pub mod broker;
111#[cfg(all(feature = "backend-identity", not(feature = "client")))]
115#[allow(dead_code, unused_imports)]
116mod broker;
117
118#[cfg(feature = "backend-identity")]
125pub mod backend_identity;
126
127#[cfg(feature = "client")]
131pub mod content_hash;
132#[cfg(all(feature = "backend-identity", not(feature = "client")))]
133mod content_hash;
134
135#[cfg(feature = "probe")]
138pub mod probe;
139
140#[cfg(feature = "client")]
146pub mod maintenance;
147
148#[cfg(feature = "client")]
149pub mod cleanup;
150
151#[cfg(feature = "client")]
155pub mod boot_autostart;
156
157#[cfg(feature = "client")]
162pub mod runpm_config;
163
164#[cfg(feature = "test-support")]
169pub mod test_support;
170
171#[cfg(all(feature = "telemetry", not(feature = "daemon")))]
180#[path = "daemon/telemetry.rs"]
181pub mod telemetry;
182
183#[cfg(all(feature = "telemetry", feature = "daemon"))]
184pub use daemon::telemetry;
185
186#[cfg(all(feature = "telemetry", feature = "daemon"))]
196const _: fn(crate::telemetry::TeeHandle) -> daemon::telemetry::TeeHandle = |handle| handle;
197
198#[cfg(feature = "daemon")]
201pub mod daemon;
203#[cfg(feature = "independent-spawn")]
208pub mod independent_spawn;
209pub mod process_tree;
210#[cfg(feature = "pty")]
211pub mod pty;
213mod public_symbols;
214mod rust_debug;
215pub mod spawn;
216mod spawn_contract;
217pub use spawn_contract::{IndependentBackend, SpawnLifetime, SpawnMode, SpawnOptions};
218#[cfg(feature = "independent-spawn")]
219mod spawn_dispatch;
220#[cfg(feature = "independent-spawn")]
221pub use spawn_dispatch::{spawn_with_options, SpawnExit, SpawnHandle};
222pub mod systemd_killmode;
223#[cfg(feature = "terminal-graphics")]
224pub mod terminal_graphics;
225mod types;
226#[cfg(unix)]
227mod unix;
228#[cfg(windows)]
229mod windows;
230
231#[cfg(feature = "async-process")]
232pub use async_process::{
233 AsyncCapturedOutput, AsyncProcess, AsyncProcessBuilder, AsyncProcessSession,
234 AsyncProcessSessionChunk, AsyncProcessSessionControl, AsyncProcessSessionEvent,
235 AsyncProcessSessionOptions, AsyncProcessSessionOutput, AsyncStdio, ProcessTreeKill,
236};
237pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
238pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
239#[cfg(feature = "client")]
241pub use content_hash::blake3_file;
242pub use observer::{
243 CapabilitySupport, CaptureSource, CategoryCapability, DumpResult, EventCategory,
244 ObservationGrade, ObservationPolicy, ObserverCapabilities, ObserverConfig, ObserverEvent,
245 ObserverEventKind, ObserverSubscriber, ProcessEvent, ProcessEventKind, ProcessIdentity,
246 ProcessObservation, ProcessObservationCapabilities, ProcessObservationError, ProcessWatch,
247 ProcessWatchConfigurationError, ProcessWatchCursor, ProcessWatchGap, ProcessWatchLoss,
248 ProcessWatchMatch, ProcessWatchRead, ProcessWatchSubscriber, StackCapture, StackDump,
249};
250#[cfg(feature = "originator-scan")]
251pub use originator::{
252 find_declared_daemon_pids, find_processes_by_originator, OriginatorProcessInfo,
253};
254pub use output_log::{
255 CursorRead, OutputCursor, OutputLog, OutputRecord, SharedOutputCursor, SharedOutputLog,
256};
257#[doc(hidden)]
260pub use running_process_platform_internal::platform::executable as platform_executable;
261#[cfg(target_os = "linux")]
262pub use running_process_platform_internal::platform::process::current_executable_build_id;
263pub use running_process_platform_internal::platform::process::{
265 ProcessInspectError, ProcessInspectErrorKind,
266};
267pub use running_process_platform_internal::process_executable_path;
269pub use running_process_platform_internal::process_same_executable_path;
271pub use running_process_platform_internal::ProcessLiveness;
273pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
274pub use spawn::{
275 spawn, spawn_daemon, spawn_daemon_breaking_away_from_job,
276 spawn_daemon_breaking_away_with_env_policy, spawn_daemon_with_clear_env,
277 spawn_daemon_with_env_policy, spawn_daemon_with_environment,
278 spawn_daemon_with_explicit_environment, spawn_daemon_with_stdio,
279 spawn_daemon_with_stdio_and_env_policy, spawn_with_env_policy, spawn_with_environment,
280 spawn_with_explicit_environment, DaemonChild, DaemonStdio, DaemonStdioSource,
281 EnvironmentPolicy, SpawnStdio, SpawnedChild, SpawnedChildControl, StdioSource, SyncEnvironment,
282 DAEMON_MARKER_ENV_VAR,
283};
284#[cfg(feature = "client-async")]
285pub use spawn::{spawn_tokio, TokioSpawnOptions};
286#[cfg(feature = "terminal-graphics")]
287pub use terminal_graphics::{
288 current_terminal_capabilities, current_terminal_capabilities_with_timeout,
289 detect_terminal_capabilities, CapabilityStatus, EvidenceStrength, GraphicsCapability,
290 GraphicsProtocol, TerminalCapabilities, TerminalCapabilityInput, TerminalGraphicsCapabilities,
291 TerminalProbeEvidence,
292};
293pub use types::{
294 CommandSpec, ProcessConfig, ProcessError, ReadStatus, RunOutput, StderrMode, StdinMode,
295 StreamEvent, StreamKind,
296};
297#[cfg(feature = "window-icon")]
298pub use window_icon::{
299 host_icon_support, icon_support, set_host_icon, set_icon, IconError, IconScope, IconSource,
300 IconSupport, StockIcon,
301};
302
303#[cfg(unix)]
304pub(crate) use helpers::{child_try_wait_error_is_retryable, poll_mutex_until};
305pub(crate) use helpers::{exit_code, feed_chunk, kill_drain_deadline, log_spawned_child_pid};
306pub use running_process_platform_internal::exit_code as native_exit_code;
308pub use running_process_platform_internal::ProcessPriority;
309#[cfg(feature = "async-process")]
310pub use running_process_platform_internal::SpawnAdmission;
311#[cfg(unix)]
312pub use unix::{unix_set_priority, unix_signal_process, unix_signal_process_group, UnixSignal};
313#[cfg(windows)]
314pub(crate) use windows::{assign_child_to_windows_kill_on_close_job_impl, WindowsJobHandle};
315
316#[macro_export]
317macro_rules! rp_rust_debug_scope {
319 ($label:expr) => {
320 let _running_process_rust_debug_scope =
321 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
322 };
323}
324
325#[derive(Default)]
326struct QueueState {
327 stdout_queue: VecDeque<Vec<u8>>,
328 stderr_queue: VecDeque<Vec<u8>>,
329 combined_queue: VecDeque<StreamEvent>,
330 stdout_history: VecDeque<Vec<u8>>,
331 stderr_history: VecDeque<Vec<u8>>,
332 combined_history: VecDeque<StreamEvent>,
333 stdout_raw: VecDeque<Vec<u8>>,
337 stderr_raw: VecDeque<Vec<u8>>,
338 stdout_raw_bytes: usize,
339 stderr_raw_bytes: usize,
340 stdout_history_bytes: usize,
341 stderr_history_bytes: usize,
342 combined_history_bytes: usize,
343 stdout_closed: bool,
344 stderr_closed: bool,
345}
346
347const RETURNCODE_NOT_SET: i64 = i64::MIN;
349
350struct SharedState {
351 queues: Mutex<QueueState>,
352 condvar: Condvar,
353 capture_limit: Option<usize>,
354 capture_overflowed: AtomicBool,
355 active_capture_readers: std::sync::atomic::AtomicUsize,
356 returncode: AtomicI64,
359 observer: Option<ObserverEmitter>,
364 observer_exit_emitted: AtomicBool,
367}
368
369struct ChildState {
370 child: ChildHandle,
371 #[cfg(windows)]
372 _job: WindowsJobHandle,
373}
374
375enum ChildHandle {
376 Standard(Child),
377 ExactTrace(running_process_platform_internal::platform::process::TracedChild),
378}
379
380impl ChildHandle {
381 fn id(&self) -> u32 {
382 match self {
383 Self::Standard(child) => child.id(),
384 Self::ExactTrace(child) => child.id(),
385 }
386 }
387
388 fn try_wait_code(&mut self) -> std::io::Result<Option<i32>> {
389 match self {
390 Self::Standard(child) => child.try_wait().map(|status| status.map(exit_code)),
391 Self::ExactTrace(child) => child.try_wait_code(),
392 }
393 }
394
395 fn kill(&mut self) -> std::io::Result<()> {
396 match self {
397 Self::Standard(child) => child.kill(),
398 Self::ExactTrace(child) => child.kill(),
399 }
400 }
401
402 fn take_stdin(&mut self) -> Option<ChildStdin> {
403 match self {
404 Self::Standard(child) => child.stdin.take(),
405 Self::ExactTrace(child) => child.take_stdin(),
406 }
407 }
408
409 fn take_stdout(&mut self) -> Option<std::process::ChildStdout> {
410 match self {
411 Self::Standard(child) => child.stdout.take(),
412 Self::ExactTrace(child) => child.take_stdout(),
413 }
414 }
415
416 fn take_stderr(&mut self) -> Option<std::process::ChildStderr> {
417 match self {
418 Self::Standard(child) => child.stderr.take(),
419 Self::ExactTrace(child) => child.take_stderr(),
420 }
421 }
422
423 #[cfg(windows)]
424 fn wait_code(&mut self) -> std::io::Result<i32> {
425 match self {
426 Self::Standard(child) => child.wait().map(exit_code),
427 Self::ExactTrace(child) => child.wait_code(),
428 }
429 }
430}
431
432#[cfg(test)]
433#[derive(Debug, Eq, PartialEq)]
434enum CapturePollAction {
435 Wait,
436 Read,
437 Cancel,
438}
439
440#[cfg(test)]
441fn capture_poll_action(capture_revents: i16, wake_revents: i16) -> CapturePollAction {
442 if wake_revents != 0 {
443 CapturePollAction::Cancel
444 } else if capture_revents != 0 {
445 CapturePollAction::Read
446 } else {
447 CapturePollAction::Wait
448 }
449}
450
451fn cleanup_child_after_start_error(child: ChildHandle) {
452 match child {
453 ChildHandle::Standard(mut child) => {
454 let _ = child.kill();
455 thread::spawn(move || {
458 let _ = child.wait();
459 });
460 }
461 ChildHandle::ExactTrace(mut child) => {
462 let _ = child.kill();
464 }
465 }
466}
467
468impl SharedState {
469 #[cfg(test)]
470 fn new(capture: bool) -> Self {
471 Self::with_observer_and_limit(capture, None, None)
472 }
473
474 fn with_observer_and_limit(
475 capture: bool,
476 observer: Option<ObserverEmitter>,
477 capture_limit: Option<usize>,
478 ) -> Self {
479 let queues = QueueState {
480 stdout_closed: !capture,
481 stderr_closed: !capture,
482 ..QueueState::default()
483 };
484 Self {
485 queues: Mutex::new(queues),
486 condvar: Condvar::new(),
487 capture_limit,
488 capture_overflowed: AtomicBool::new(false),
489 active_capture_readers: std::sync::atomic::AtomicUsize::new(0),
490 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
491 observer,
492 observer_exit_emitted: AtomicBool::new(false),
493 }
494 }
495
496 fn emit_exited(&self, pid: u32, exit_code: i32) {
499 let Some(emitter) = self.observer.as_ref() else {
500 return;
501 };
502 if self
503 .observer_exit_emitted
504 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
505 .is_ok()
506 {
507 emitter.emit_exited(pid, exit_code);
508 }
509 }
510}
511
512pub struct NativeProcess {
519 config: ProcessConfig,
520 command_override: Mutex<Option<Command>>,
521 child: Arc<Mutex<Option<ChildState>>>,
522 stdin: Mutex<Option<ChildStdin>>,
523 shared: Arc<SharedState>,
524 process_watch: Option<Arc<ProcessWatchEmitter>>,
525 kill_when_owner_dies: bool,
529 #[cfg(test)]
530 stdin_write_active: AtomicBool,
531 capture_cancellation:
532 Arc<running_process_platform_internal::platform::process::CaptureCancellation>,
533}
534
535impl NativeProcess {
536 pub fn new(config: ProcessConfig) -> Self {
542 Self::new_with_options(config, None, None, None, None)
543 }
544
545 pub fn with_observer(
558 config: ProcessConfig,
559 observer: crate::observer::ObserverConfig,
560 ) -> (Self, ObserverSubscriber) {
561 let (emitter, subscriber) = ObserverEmitter::new(observer);
562 let process = Self::new_with_options(config, Some(emitter), None, None, None);
563 (process, subscriber)
564 }
565
566 pub fn with_observer_and_command(
581 command: Command,
582 config: ProcessConfig,
583 observer: crate::observer::ObserverConfig,
584 ) -> (Self, ObserverSubscriber) {
585 let (emitter, subscriber) = ObserverEmitter::new(observer);
586 let process = Self::new_with_options(config, Some(emitter), None, Some(command), None);
587 (process, subscriber)
588 }
589
590 pub fn with_process_watches(
593 config: ProcessConfig,
594 watches: Vec<ProcessWatch>,
595 policy: ObservationPolicy,
596 ) -> Result<(Self, ProcessWatchSubscriber), ProcessObservationError> {
597 let (emitter, subscriber) = ProcessWatchEmitter::new(watches, policy)?;
598 let process = Self::new_with_options(config, None, None, None, Some(emitter));
599 Ok((process, subscriber))
600 }
601
602 pub fn process_observation_capabilities() -> ProcessObservationCapabilities {
604 ProcessObservationCapabilities::current()
605 }
606
607 fn new_with_capture_limit(config: ProcessConfig, capture_limit: usize) -> Self {
608 Self::new_with_options(config, None, Some(capture_limit), None, None)
609 }
610
611 fn new_with_command_capture_limit(
612 command: Command,
613 config: ProcessConfig,
614 capture_limit: usize,
615 kill_when_owner_dies: bool,
616 ) -> Self {
617 let mut process =
618 Self::new_with_options(config, None, Some(capture_limit), Some(command), None);
619 process.kill_when_owner_dies = kill_when_owner_dies;
620 process
621 }
622
623 fn new_with_options(
624 config: ProcessConfig,
625 observer: Option<ObserverEmitter>,
626 capture_limit: Option<usize>,
627 command_override: Option<Command>,
628 process_watch: Option<Arc<ProcessWatchEmitter>>,
629 ) -> Self {
630 let shared = SharedState::with_observer_and_limit(config.capture, observer, capture_limit);
631 Self {
632 shared: Arc::new(shared),
633 process_watch,
634 command_override: Mutex::new(command_override),
635 child: Arc::new(Mutex::new(None)),
636 stdin: Mutex::new(None),
637 kill_when_owner_dies: false,
638 #[cfg(test)]
639 stdin_write_active: AtomicBool::new(false),
640 config,
641 capture_cancellation: Arc::new(Default::default()),
642 }
643 }
644
645 #[inline(never)]
647 pub fn start(&self) -> Result<(), ProcessError> {
652 public_symbols::rp_native_process_start_public(self)
653 }
654
655 fn start_impl(&self) -> Result<(), ProcessError> {
656 crate::rp_rust_debug_scope!("running_process::NativeProcess::start");
657 let mut guard = self.child.lock().expect("child mutex poisoned");
658 if guard.is_some() {
659 return Err(ProcessError::AlreadyStarted);
660 }
661
662 let mut command = self.build_command();
663 let exact_trace = self
664 .process_watch
665 .as_ref()
666 .is_some_and(|watch| watch.uses_exact_trace());
667 match self.config.stdin_mode {
668 StdinMode::Inherit => {}
669 StdinMode::Piped => {
670 command.stdin(Stdio::piped());
671 }
672 StdinMode::Null => {
673 command.stdin(Stdio::null());
674 }
675 }
676 if self.config.capture {
677 command.stdout(Stdio::piped());
678 command.stderr(Stdio::piped());
679 }
680
681 let mut child = if exact_trace {
682 let event_watch = Arc::clone(self.process_watch.as_ref().expect("exact watch checked"));
683 let completion_watch = Arc::clone(&event_watch);
684 match running_process_platform_internal::platform::process::start_exact_trace(
685 command,
686 Box::new(move |event| event_watch.emit_exact(event)),
687 Box::new(move || completion_watch.close()),
688 ) {
689 Ok(child) => ChildHandle::ExactTrace(child),
690 Err(error) => {
691 if let Some(watch) = self.process_watch.as_ref() {
692 watch.close();
693 }
694 return Err(ProcessError::Spawn(error));
695 }
696 }
697 } else {
698 ChildHandle::Standard(command.spawn().map_err(ProcessError::Spawn)?)
699 };
700 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
701 if let Some(emitter) = self.shared.observer.as_ref() {
704 emitter.emit_started(child.id());
705 }
706 #[cfg(windows)]
711 let job = {
712 let descendant_sink = self
713 .shared
714 .observer
715 .as_ref()
716 .and_then(|e| e.descendant_sink());
717 let job_result = match &child {
718 ChildHandle::Standard(standard_child) => {
719 let direct_pid = standard_child.id();
720 public_symbols::rp_assign_child_to_windows_kill_on_close_job_with_observer_public(
721 standard_child,
722 descendant_sink,
723 self.process_watch.clone(),
724 direct_pid,
725 self.config.address_space_limit_bytes,
726 )
727 }
728 ChildHandle::ExactTrace(_) => unreachable!("Windows exact tracing is unavailable"),
729 };
730 match job_result {
731 Ok(job) => job,
732 Err(error) => {
733 if let Some(watch) = self.process_watch.as_ref() {
734 watch.close();
735 }
736 cleanup_child_after_start_error(child);
737 return Err(ProcessError::Spawn(error));
738 }
739 }
740 };
741 if !exact_trace {
742 descendant_monitor::start(
743 child.id(),
744 self.shared.observer.as_ref(),
745 self.process_watch.as_ref(),
746 );
747 }
748 if self.config.capture {
749 let stdout = child.take_stdout().expect("stdout pipe missing");
750 let stderr = child.take_stderr().expect("stderr pipe missing");
751 let stdout =
752 match running_process_platform_internal::platform::process::prepare_capture_reader(
753 stdout,
754 &self.capture_cancellation,
755 running_process_platform_internal::platform::process::CaptureStream::Stdout,
756 ) {
757 Ok(stdout) => stdout,
758 Err(error) => {
759 cleanup_child_after_start_error(child);
760 return Err(ProcessError::Spawn(error));
761 }
762 };
763 let stderr =
764 match running_process_platform_internal::platform::process::prepare_capture_reader(
765 stderr,
766 &self.capture_cancellation,
767 running_process_platform_internal::platform::process::CaptureStream::Stderr,
768 ) {
769 Ok(stderr) => stderr,
770 Err(error) => {
771 running_process_platform_internal::platform::process::capture_reader_done(
772 &self.capture_cancellation,
773 running_process_platform_internal::platform::process::CaptureStream::Stdout,
774 );
775 cleanup_child_after_start_error(child);
776 return Err(ProcessError::Spawn(error));
777 }
778 };
779 self.spawn_reader(
780 stdout,
781 StreamKind::Stdout,
782 StreamKind::Stdout,
783 self.pipe_done_callback(StreamKind::Stdout),
784 );
785 self.spawn_reader(
786 stderr,
787 StreamKind::Stderr,
788 match self.config.stderr_mode {
789 StderrMode::Stdout => StreamKind::Stdout,
790 StderrMode::Pipe => StreamKind::Stderr,
791 },
792 self.pipe_done_callback(StreamKind::Stderr),
793 );
794 }
795 *self.stdin.lock().expect("stdin mutex poisoned") = child.take_stdin();
796 *guard = Some(ChildState {
797 child,
798 #[cfg(windows)]
799 _job: job,
800 });
801 drop(guard);
802 self.spawn_exit_waiter();
803 Ok(())
804 }
805
806 fn spawn_exit_waiter(&self) {
809 let child = Arc::clone(&self.child);
810 let shared = Arc::clone(&self.shared);
811 let capture = self.config.capture;
812 let capture_cancellation = Arc::clone(&self.capture_cancellation);
813 thread::spawn(move || {
814 loop {
815 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
816 return;
817 }
818 let exited = {
819 let mut guard = child.lock().expect("child mutex poisoned");
820 if let Some(child_state) = guard.as_mut() {
821 let pid = child_state.child.id();
822 match child_state.child.try_wait_code() {
823 Ok(Some(code)) => {
824 shared.returncode.store(code as i64, Ordering::Release);
825 shared.emit_exited(pid, code);
829 shared.condvar.notify_all();
830 true
831 }
832 Ok(None) => false,
833 Err(_error) => {
834 #[cfg(unix)]
835 if child_try_wait_error_is_retryable(&_error) {
836 false
837 } else {
838 return;
839 }
840 #[cfg(windows)]
841 return;
842 }
843 }
844 } else {
845 return;
846 }
847 };
848 if exited {
849 if capture {
864 let drained = finalize_capture_completion(&shared, kill_drain_deadline());
865 if !drained {
866 running_process_platform_internal::platform::process::cancel_capture_reader(
867 &capture_cancellation,
868 );
869 }
870 }
871 return;
877 }
878 thread::sleep(Duration::from_millis(10));
884 }
885 });
886 }
887
888 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
890 if self.child.lock().expect("child mutex poisoned").is_none() {
891 return Err(ProcessError::NotRunning);
892 }
893 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
894 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
895 use std::io::Write;
896 #[cfg(test)]
897 self.stdin_write_active.store(true, Ordering::Release);
898 let write_result = stdin.write_all(data);
899 #[cfg(test)]
900 self.stdin_write_active.store(false, Ordering::Release);
901 write_result.map_err(ProcessError::Io)?;
902 stdin.flush().map_err(ProcessError::Io)?;
903 drop(guard.take());
904 Ok(())
905 }
906
907 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
912 if self.child.lock().expect("child mutex poisoned").is_none() {
913 return Err(ProcessError::NotRunning);
914 }
915 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
916 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
917 use std::io::Write;
918 #[cfg(test)]
919 self.stdin_write_active.store(true, Ordering::Release);
920 let write_result = stdin.write_all(data);
921 #[cfg(test)]
922 self.stdin_write_active.store(false, Ordering::Release);
923 write_result.map_err(ProcessError::Io)?;
924 stdin.flush().map_err(ProcessError::Io)?;
925 Ok(())
926 }
927
928 pub fn close_stdin(&self) -> Result<(), ProcessError> {
931 if self.child.lock().expect("child mutex poisoned").is_none() {
932 return Err(ProcessError::NotRunning);
933 }
934 drop(self.stdin.lock().expect("stdin mutex poisoned").take());
935 Ok(())
936 }
937
938 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
942 if let Some(code) = self.returncode() {
944 return Ok(Some(code));
945 }
946 let mut guard = self.child.lock().expect("child mutex poisoned");
947 let Some(child_state) = guard.as_mut() else {
948 return Ok(self.returncode());
949 };
950 let pid = child_state.child.id();
951 let child = &mut child_state.child;
952 let status = child.try_wait_code().map_err(ProcessError::Io)?;
953 if let Some(code) = status {
954 self.set_returncode(code);
955 self.shared.emit_exited(pid, code);
956 return Ok(Some(code));
957 }
958 Ok(None)
959 }
960
961 #[inline(never)]
963 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
968 public_symbols::rp_native_process_wait_public(self, timeout)
969 }
970
971 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
972 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
973 if self.child.lock().expect("child mutex poisoned").is_none() {
974 return self.returncode().ok_or(ProcessError::NotRunning);
975 }
976 if let Some(code) = self.returncode() {
978 self.finish_capture_drain();
979 return Ok(code);
980 }
981 let start = Instant::now();
982 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
983 loop {
984 let rc = self.shared.returncode.load(Ordering::Acquire);
986 if rc != RETURNCODE_NOT_SET {
987 drop(guard);
988 let code = rc as i32;
989 self.finish_capture_drain();
990 return Ok(code);
991 }
992 if let Some(limit) = timeout {
993 let elapsed = start.elapsed();
994 if elapsed >= limit {
995 return Err(ProcessError::Timeout);
996 }
997 let remaining = limit - elapsed;
998 let wait_time = remaining.min(Duration::from_millis(50));
1000 guard = self
1001 .shared
1002 .condvar
1003 .wait_timeout(guard, wait_time)
1004 .expect("queue mutex poisoned")
1005 .0;
1006 } else {
1007 guard = self
1009 .shared
1010 .condvar
1011 .wait_timeout(guard, Duration::from_millis(50))
1012 .expect("queue mutex poisoned")
1013 .0;
1014 }
1015 }
1016 }
1017
1018 #[inline(never)]
1020 pub fn kill(&self) -> Result<(), ProcessError> {
1022 public_symbols::rp_native_process_kill_public(self)
1023 }
1024
1025 fn kill_impl(&self) -> Result<(), ProcessError> {
1026 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
1027 #[cfg(windows)]
1028 {
1029 let mut guard = self.child.lock().expect("child mutex poisoned");
1030 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
1031 let pid = child.id();
1032 child.kill().map_err(ProcessError::Io)?;
1033 let code = child.wait_code().map_err(ProcessError::Io)?;
1034 self.set_returncode(code);
1035 self.shared.emit_exited(pid, code);
1038 }
1039 #[cfg(unix)]
1040 {
1041 let deadline = kill_drain_deadline();
1042 let (pid, already_reaped) = {
1043 let mut state = self.child.lock().expect("child mutex poisoned");
1044 let child = &mut state.as_mut().ok_or(ProcessError::NotRunning)?.child;
1045 let pid = child.id();
1046 if let Some(code) = child.try_wait_code().map_err(ProcessError::Io)? {
1047 (pid, Some(code))
1048 } else {
1049 let group_signaled = self.config.create_process_group
1050 && unix_signal_process_group(pid as i32, UnixSignal::Kill).is_ok();
1051 if !group_signaled {
1052 child.kill().map_err(ProcessError::Io)?;
1053 }
1054 (pid, None)
1055 }
1056 };
1057
1058 self.cancel_capture_io();
1062 let reaped = already_reaped.or_else(|| {
1063 let reap_result =
1064 poll_mutex_until(&self.child, deadline, Duration::from_millis(10), |state| {
1065 match state.as_mut() {
1066 Some(child) => child.child.try_wait_code(),
1067 None => Ok(None),
1068 }
1069 });
1070 match reap_result {
1071 Ok(Some(code)) => Some(code),
1072 _ => None,
1073 }
1074 });
1075 if let Some(code) = reaped {
1076 self.set_returncode(code);
1077 self.shared.emit_exited(pid, code);
1078 }
1079 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
1080 self, deadline,
1081 );
1082 Ok(())
1083 }
1084 #[cfg(windows)]
1085 {
1086 self.cancel_capture_io();
1094 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
1105 self,
1106 kill_drain_deadline(),
1107 );
1108 Ok(())
1109 }
1110 }
1111
1112 pub fn terminate(&self) -> Result<(), ProcessError> {
1116 self.kill()
1117 }
1118
1119 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
1131 if !self.config.create_process_group {
1132 return Ok(());
1134 }
1135 let pid = self.pid().ok_or(ProcessError::NotRunning)?;
1136 running_process_platform_internal::platform::process::soft_terminate_process_group(pid)
1137 .map_err(ProcessError::Io)
1138 }
1139
1140 #[inline(never)]
1142 pub fn close(&self) -> Result<(), ProcessError> {
1144 public_symbols::rp_native_process_close_public(self)
1145 }
1146
1147 fn close_impl(&self) -> Result<(), ProcessError> {
1148 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
1149 if self.child.lock().expect("child mutex poisoned").is_none() {
1150 return Ok(());
1151 }
1152 if self.poll()?.is_none() {
1153 self.kill()?;
1154 } else {
1155 self.finish_capture_drain();
1156 }
1157 if let Some(watch) = self.process_watch.as_ref() {
1158 watch.close();
1159 }
1160 Ok(())
1161 }
1162
1163 pub fn pid(&self) -> Option<u32> {
1165 self.child
1166 .lock()
1167 .expect("child mutex poisoned")
1168 .as_ref()
1169 .map(|state| state.child.id())
1170 }
1171
1172 pub fn returncode(&self) -> Option<i32> {
1174 let v = self.shared.returncode.load(Ordering::Acquire);
1175 if v == RETURNCODE_NOT_SET {
1176 None
1177 } else {
1178 Some(v as i32)
1179 }
1180 }
1181
1182 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
1184 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1185 return false;
1186 }
1187 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1188 match stream {
1189 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
1190 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
1191 }
1192 }
1193
1194 pub fn has_pending_combined(&self) -> bool {
1196 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1197 !guard.combined_queue.is_empty()
1198 }
1199
1200 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
1202 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1203 return Vec::new();
1204 }
1205 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1206 let queue = match stream {
1207 StreamKind::Stdout => &mut guard.stdout_queue,
1208 StreamKind::Stderr => &mut guard.stderr_queue,
1209 };
1210 queue.drain(..).collect()
1211 }
1212
1213 pub fn drain_stream_raw(&self, stream: StreamKind) -> Vec<u8> {
1222 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1223 return Vec::new();
1224 }
1225 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1226 match stream {
1227 StreamKind::Stdout => {
1228 let mut output = Vec::with_capacity(guard.stdout_raw_bytes);
1229 for chunk in guard.stdout_raw.drain(..) {
1230 output.extend_from_slice(&chunk);
1231 }
1232 guard.stdout_raw_bytes = 0;
1233 output
1234 }
1235 StreamKind::Stderr => {
1236 let mut output = Vec::with_capacity(guard.stderr_raw_bytes);
1237 for chunk in guard.stderr_raw.drain(..) {
1238 output.extend_from_slice(&chunk);
1239 }
1240 guard.stderr_raw_bytes = 0;
1241 output
1242 }
1243 }
1244 }
1245
1246 pub fn drain_combined(&self) -> Vec<StreamEvent> {
1248 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1249 guard.combined_queue.drain(..).collect()
1250 }
1251
1252 pub fn read_stream(
1257 &self,
1258 stream: StreamKind,
1259 timeout: Option<Duration>,
1260 ) -> ReadStatus<Vec<u8>> {
1261 let deadline = timeout.map(|limit| Instant::now() + limit);
1262 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1263
1264 loop {
1265 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1266 return ReadStatus::Eof;
1267 }
1268
1269 let queue = match stream {
1270 StreamKind::Stdout => &mut guard.stdout_queue,
1271 StreamKind::Stderr => &mut guard.stderr_queue,
1272 };
1273 if let Some(line) = queue.pop_front() {
1274 return ReadStatus::Line(line);
1275 }
1276
1277 let closed = match stream {
1278 StreamKind::Stdout => {
1279 if self.config.stderr_mode == StderrMode::Stdout {
1280 guard.stdout_closed && guard.stderr_closed
1281 } else {
1282 guard.stdout_closed
1283 }
1284 }
1285 StreamKind::Stderr => guard.stderr_closed,
1286 };
1287 if closed {
1288 return ReadStatus::Eof;
1289 }
1290
1291 match deadline {
1292 Some(deadline) => {
1293 let now = Instant::now();
1294 if now >= deadline {
1295 return ReadStatus::Timeout;
1296 }
1297 let wait = deadline.saturating_duration_since(now);
1298 let result = self
1299 .shared
1300 .condvar
1301 .wait_timeout(guard, wait)
1302 .expect("queue mutex poisoned");
1303 guard = result.0;
1304 if result.1.timed_out() {
1305 return ReadStatus::Timeout;
1306 }
1307 }
1308 None => {
1309 guard = self
1310 .shared
1311 .condvar
1312 .wait(guard)
1313 .expect("queue mutex poisoned");
1314 }
1315 }
1316 }
1317 }
1318
1319 #[inline(never)]
1321 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1323 public_symbols::rp_native_process_read_combined_public(self, timeout)
1324 }
1325
1326 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1327 crate::rp_rust_debug_scope!("running_process::NativeProcess::read_combined");
1328 let deadline = timeout.map(|limit| Instant::now() + limit);
1329 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1330
1331 loop {
1332 if let Some(event) = guard.combined_queue.pop_front() {
1333 return ReadStatus::Line(event);
1334 }
1335 if guard.stdout_closed && guard.stderr_closed {
1336 return ReadStatus::Eof;
1337 }
1338
1339 match deadline {
1340 Some(deadline) => {
1341 let now = Instant::now();
1342 if now >= deadline {
1343 return ReadStatus::Timeout;
1344 }
1345 let wait = deadline.saturating_duration_since(now);
1346 let result = self
1347 .shared
1348 .condvar
1349 .wait_timeout(guard, wait)
1350 .expect("queue mutex poisoned");
1351 guard = result.0;
1352 if result.1.timed_out() {
1353 return ReadStatus::Timeout;
1354 }
1355 }
1356 None => {
1357 guard = self
1358 .shared
1359 .condvar
1360 .wait(guard)
1361 .expect("queue mutex poisoned");
1362 }
1363 }
1364 }
1365 }
1366
1367 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
1369 self.shared
1370 .queues
1371 .lock()
1372 .expect("queue mutex poisoned")
1373 .stdout_history
1374 .clone()
1375 .into_iter()
1376 .collect()
1377 }
1378
1379 fn captured_stdout_raw(&self) -> Vec<u8> {
1380 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1381 guard.stdout_raw.iter().flatten().copied().collect()
1382 }
1383
1384 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
1386 if self.config.stderr_mode == StderrMode::Stdout {
1387 return Vec::new();
1388 }
1389 self.shared
1390 .queues
1391 .lock()
1392 .expect("queue mutex poisoned")
1393 .stderr_history
1394 .clone()
1395 .into_iter()
1396 .collect()
1397 }
1398
1399 fn captured_stderr_raw(&self) -> Vec<u8> {
1400 if self.config.stderr_mode == StderrMode::Stdout {
1401 return Vec::new();
1402 }
1403 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1404 guard.stderr_raw.iter().flatten().copied().collect()
1405 }
1406
1407 pub fn captured_combined(&self) -> Vec<StreamEvent> {
1409 self.shared
1410 .queues
1411 .lock()
1412 .expect("queue mutex poisoned")
1413 .combined_history
1414 .clone()
1415 .into_iter()
1416 .collect()
1417 }
1418
1419 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
1421 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1422 return 0;
1423 }
1424 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1425 match stream {
1426 StreamKind::Stdout => guard.stdout_history_bytes,
1427 StreamKind::Stderr => guard.stderr_history_bytes,
1428 }
1429 }
1430
1431 pub fn captured_combined_bytes(&self) -> usize {
1433 self.shared
1434 .queues
1435 .lock()
1436 .expect("queue mutex poisoned")
1437 .combined_history_bytes
1438 }
1439
1440 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
1449 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1450 return 0;
1451 }
1452 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1453 match stream {
1454 StreamKind::Stdout => {
1455 let released = guard.stdout_history_bytes;
1456 guard.stdout_history.clear();
1457 guard.stdout_raw.clear();
1458 guard.stdout_raw_bytes = 0;
1459 guard.stdout_history_bytes = 0;
1460 released
1461 }
1462 StreamKind::Stderr => {
1463 let released = guard.stderr_history_bytes;
1464 guard.stderr_history.clear();
1465 guard.stderr_raw.clear();
1466 guard.stderr_raw_bytes = 0;
1467 guard.stderr_history_bytes = 0;
1468 released
1469 }
1470 }
1471 }
1472
1473 pub fn clear_captured_combined(&self) -> usize {
1475 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1476 let released = guard.combined_history_bytes;
1477 guard.combined_history.clear();
1478 guard.combined_history_bytes = 0;
1479 released
1480 }
1481
1482 fn build_command(&self) -> Command {
1483 let command_override = self
1484 .command_override
1485 .lock()
1486 .expect("command override mutex poisoned")
1487 .take();
1488 let mut command = match command_override {
1489 Some(command) => command,
1490 None => {
1491 let mut command = match &self.config.command {
1492 CommandSpec::Shell(command) => shell_command(command),
1493 CommandSpec::Argv(argv) => {
1494 let mut command = Command::new(&argv[0]);
1495 if argv.len() > 1 {
1496 command.args(&argv[1..]);
1497 }
1498 command
1499 }
1500 };
1501 if let Some(cwd) = &self.config.cwd {
1502 command.current_dir(cwd);
1503 }
1504 if let Some(env) = &self.config.env {
1505 command.env_clear();
1506 command.envs(env.iter().map(|(k, v)| (k, v)));
1507 }
1508 command
1509 }
1510 };
1511 let platform_config =
1512 running_process_platform_internal::platform::process::ProcessCommandConfig {
1513 creation_flags: self.config.creationflags,
1514 create_process_group: self.config.create_process_group,
1515 nice: self.config.nice,
1516 address_space_limit_bytes: self.config.address_space_limit_bytes,
1517 };
1518 let configured = if self.kill_when_owner_dies {
1519 running_process_platform_internal::platform::process::
1520 configure_process_command_for_bounded_owner_death(&mut command, platform_config)
1521 } else {
1522 running_process_platform_internal::platform::process::configure_process_command(
1523 &mut command,
1524 platform_config,
1525 )
1526 };
1527 configured.expect("platform command configuration must be valid");
1528 command
1529 }
1530
1531 fn spawn_reader<R>(
1532 &self,
1533 pipe: R,
1534 source_stream: StreamKind,
1535 visible_stream: StreamKind,
1536 on_pipe_done: Box<dyn FnOnce() + Send>,
1537 ) where
1538 R: Read + Send + 'static,
1539 {
1540 let shared = Arc::clone(&self.shared);
1541 shared.active_capture_readers.fetch_add(1, Ordering::AcqRel);
1542 thread::spawn(move || {
1543 let mut reader = pipe;
1544 let mut chunk = vec![0_u8; 65536];
1545 let mut pending = Vec::new();
1546
1547 loop {
1548 match reader.read(&mut chunk) {
1549 Ok(0) => break,
1550 Ok(n) => {
1551 if append_raw(&shared, visible_stream, &chunk[..n]) {
1552 let lines = feed_chunk(&mut pending, &chunk[..n]);
1553 emit_lines(&shared, visible_stream, lines);
1554 } else {
1555 pending.clear();
1556 }
1557 }
1558 Err(_) => break,
1559 }
1560 }
1561
1562 if !pending.is_empty() && !shared.capture_overflowed.load(Ordering::Acquire) {
1563 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1564 }
1565
1566 on_pipe_done();
1571 drop(reader);
1572
1573 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1574 match source_stream {
1575 StreamKind::Stdout => guard.stdout_closed = true,
1576 StreamKind::Stderr => guard.stderr_closed = true,
1577 }
1578 shared.active_capture_readers.fetch_sub(1, Ordering::AcqRel);
1579 shared.condvar.notify_all();
1580 });
1581 }
1582
1583 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1584 let cancellation = Arc::clone(&self.capture_cancellation);
1585 Box::new(move || {
1586 let stream = match stream {
1587 StreamKind::Stdout => {
1588 running_process_platform_internal::platform::process::CaptureStream::Stdout
1589 }
1590 StreamKind::Stderr => {
1591 running_process_platform_internal::platform::process::CaptureStream::Stderr
1592 }
1593 };
1594 running_process_platform_internal::platform::process::capture_reader_done(
1595 &cancellation,
1596 stream,
1597 );
1598 })
1599 }
1600
1601 fn cancel_capture_io(&self) {
1605 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1606 running_process_platform_internal::platform::process::cancel_capture_reader(
1607 &self.capture_cancellation,
1608 );
1609 }
1610
1611 fn set_returncode(&self, code: i32) {
1612 self.shared.returncode.store(code as i64, Ordering::Release);
1613 self.shared.condvar.notify_all();
1614 }
1615
1616 fn finish_capture_drain(&self) {
1626 self.finish_capture_drain_with_deadline(kill_drain_deadline());
1627 }
1628
1629 fn finish_capture_drain_with_deadline(&self, deadline: Instant) {
1630 let drained = self.wait_for_capture_completion_with_deadline_impl(deadline);
1631 if !drained {
1632 self.cancel_capture_io();
1633 }
1634 }
1635
1636 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1639 crate::rp_rust_debug_scope!(
1640 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1641 );
1642 if !self.config.capture {
1643 return true;
1644 }
1645 finalize_capture_completion(&self.shared, deadline)
1646 }
1647
1648 fn wait_for_capture_readers_with_deadline(&self, deadline: Instant) -> bool {
1649 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1650 while self.shared.active_capture_readers.load(Ordering::Acquire) != 0 {
1651 let now = Instant::now();
1652 if now >= deadline {
1653 return false;
1654 }
1655 let (next_guard, result) = self
1656 .shared
1657 .condvar
1658 .wait_timeout(guard, deadline - now)
1659 .expect("queue mutex poisoned");
1660 guard = next_guard;
1661 if result.timed_out() && self.shared.active_capture_readers.load(Ordering::Acquire) != 0
1662 {
1663 return false;
1664 }
1665 }
1666 true
1667 }
1668}
1669
1670fn finalize_capture_completion(shared: &SharedState, deadline: Instant) -> bool {
1682 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1683 while !(guard.stdout_closed && guard.stderr_closed) {
1684 let now = Instant::now();
1685 if now >= deadline {
1686 guard.stdout_closed = true;
1687 guard.stderr_closed = true;
1688 shared.condvar.notify_all();
1689 return false;
1690 }
1691 let (next_guard, result) = shared
1692 .condvar
1693 .wait_timeout(guard, deadline - now)
1694 .expect("queue mutex poisoned");
1695 guard = next_guard;
1696 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1697 guard.stdout_closed = true;
1698 guard.stderr_closed = true;
1699 shared.condvar.notify_all();
1700 return false;
1701 }
1702 }
1703 true
1704}
1705
1706fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1707 if lines.is_empty() || shared.capture_overflowed.load(Ordering::Acquire) {
1708 return;
1709 }
1710 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1711 if shared.capture_overflowed.load(Ordering::Acquire) {
1712 return;
1713 }
1714 for line in lines {
1715 let line_len = line.len();
1716 match stream {
1717 StreamKind::Stdout => {
1718 guard.stdout_history_bytes += line_len;
1719 guard.stdout_history.push_back(line.clone());
1720 guard.stdout_queue.push_back(line.clone());
1721 }
1722 StreamKind::Stderr => {
1723 guard.stderr_history_bytes += line_len;
1724 guard.stderr_history.push_back(line.clone());
1725 guard.stderr_queue.push_back(line.clone());
1726 }
1727 }
1728 let event = StreamEvent { stream, line };
1729 guard.combined_history_bytes += line_len;
1730 guard.combined_history.push_back(event.clone());
1731 guard.combined_queue.push_back(event);
1732 }
1733 shared.condvar.notify_all();
1734}
1735
1736fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) -> bool {
1737 if chunk.is_empty() {
1738 return true;
1739 }
1740 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1741 let accepted = match shared.capture_limit {
1742 Some(limit) => {
1743 let retained = guard
1744 .stdout_raw_bytes
1745 .saturating_add(guard.stderr_raw_bytes);
1746 chunk.len().min(limit.saturating_sub(retained))
1747 }
1748 None => chunk.len(),
1749 };
1750 if accepted != 0 {
1751 let accepted_chunk = chunk[..accepted].to_vec();
1752 match stream {
1753 StreamKind::Stdout => {
1754 guard.stdout_raw_bytes += accepted;
1755 guard.stdout_raw.push_back(accepted_chunk);
1756 }
1757 StreamKind::Stderr => {
1758 guard.stderr_raw_bytes += accepted;
1759 guard.stderr_raw.push_back(accepted_chunk);
1760 }
1761 }
1762 }
1763 if accepted != chunk.len() {
1764 shared.capture_overflowed.store(true, Ordering::Release);
1765 false
1766 } else {
1767 shared.condvar.notify_all();
1768 true
1769 }
1770}
1771
1772mod bounded;
1773pub use bounded::{
1774 run_command, run_command_bounded, run_std_command_bounded,
1775 run_std_command_bounded_with_options, BoundedRunOptions,
1776};
1777
1778pub(crate) fn shell_command(command: &str) -> Command {
1779 running_process_platform_internal::platform::process::compat_shell_command(command)
1780}
1781
1782#[cfg(test)]
1783mod tests;