1use std::collections::VecDeque;
10use std::io::Read;
11#[cfg(unix)]
12use std::os::fd::{AsRawFd, RawFd};
13#[cfg(unix)]
14use std::os::unix::net::UnixStream;
15use std::process::{Child, ChildStdin, Command, Stdio};
16use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
17use std::sync::{Arc, Condvar, Mutex};
18use std::thread;
19use std::time::{Duration, Instant};
20
21use crate::observer::ObserverEmitter;
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;
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")]
49pub mod proto {
51 #[allow(missing_docs)]
53 pub mod daemon {
54 include!(concat!(env!("OUT_DIR"), "/running_process.daemon.v1.rs"));
55 }
56}
57
58#[cfg(feature = "client")]
59pub mod client;
60
61#[cfg(feature = "client")]
66pub mod broker;
67
68#[cfg(feature = "probe")]
71pub mod probe;
72
73#[cfg(feature = "client")]
79pub mod maintenance;
80
81#[cfg(feature = "client")]
82pub mod cleanup;
83
84#[cfg(feature = "client")]
88pub mod boot_autostart;
89
90#[cfg(feature = "client")]
95pub mod runpm_config;
96
97#[cfg(feature = "test-support")]
102pub mod test_support;
103
104#[cfg(feature = "telemetry")]
107#[path = "daemon/telemetry.rs"]
108pub mod telemetry;
109
110#[cfg(feature = "daemon")]
113pub mod daemon;
115pub mod process_tree;
116#[cfg(feature = "pty")]
117pub mod pty;
119mod public_symbols;
120mod rust_debug;
121pub mod spawn;
122pub mod systemd_killmode;
123pub mod terminal_graphics;
124mod types;
125#[cfg(unix)]
126mod unix;
127#[cfg(windows)]
128mod windows;
129
130#[cfg(feature = "async-process")]
131pub use async_process::AsyncProcess;
132pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
133pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
134pub use observer::{
135 CapabilitySupport, CategoryCapability, EventCategory, ObserverCapabilities, ObserverConfig,
136 ObserverEvent, ObserverEventKind, ObserverSubscriber,
137};
138#[cfg(feature = "originator-scan")]
139pub use originator::{
140 find_declared_daemon_pids, find_processes_by_originator, OriginatorProcessInfo,
141};
142pub use output_log::{
143 CursorRead, OutputCursor, OutputLog, OutputRecord, SharedOutputCursor, SharedOutputLog,
144};
145pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
146pub use spawn::{
147 spawn, spawn_daemon, spawn_daemon_breaking_away_from_job,
148 spawn_daemon_breaking_away_with_env_policy, spawn_daemon_with_clear_env,
149 spawn_daemon_with_env_policy, spawn_daemon_with_stdio, spawn_daemon_with_stdio_and_env_policy,
150 spawn_with_env_policy, DaemonChild, DaemonStdio, DaemonStdioSource, EnvironmentPolicy,
151 SpawnStdio, SpawnedChild, StdioSource, DAEMON_MARKER_ENV_VAR,
152};
153#[cfg(feature = "client-async")]
154pub use spawn::{spawn_tokio, TokioSpawnOptions};
155pub use terminal_graphics::{
156 current_terminal_capabilities, current_terminal_capabilities_with_timeout,
157 detect_terminal_capabilities, CapabilityStatus, EvidenceStrength, GraphicsCapability,
158 GraphicsProtocol, TerminalCapabilities, TerminalCapabilityInput, TerminalGraphicsCapabilities,
159 TerminalProbeEvidence,
160};
161pub use types::{
162 CommandSpec, ProcessConfig, ProcessError, ReadStatus, RunOutput, StderrMode, StdinMode,
163 StreamEvent, StreamKind,
164};
165pub use window_icon::{
166 host_icon_support, icon_support, set_host_icon, set_icon, IconError, IconScope, IconSource,
167 IconSupport, StockIcon,
168};
169
170#[cfg(unix)]
171pub(crate) use helpers::{
172 child_signal_disposition, child_try_wait_error_is_retryable, completed_reap_after_signal,
173 poll_mutex_until, with_child_lock_for_signal, ChildSignalDisposition,
174};
175pub(crate) use helpers::{exit_code, feed_chunk, kill_drain_deadline, log_spawned_child_pid};
176#[cfg(unix)]
177pub use unix::{unix_set_priority, unix_signal_process, unix_signal_process_group, UnixSignal};
178#[cfg(windows)]
179pub(crate) use windows::{
180 assign_child_to_windows_kill_on_close_job_impl, windows_creation_flags, CapturePipeHandles,
181 WindowsJobHandle,
182};
183
184#[macro_export]
185macro_rules! rp_rust_debug_scope {
187 ($label:expr) => {
188 let _running_process_rust_debug_scope =
189 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
190 };
191}
192
193#[derive(Default)]
194struct QueueState {
195 stdout_queue: VecDeque<Vec<u8>>,
196 stderr_queue: VecDeque<Vec<u8>>,
197 combined_queue: VecDeque<StreamEvent>,
198 stdout_history: VecDeque<Vec<u8>>,
199 stderr_history: VecDeque<Vec<u8>>,
200 combined_history: VecDeque<StreamEvent>,
201 stdout_raw: Vec<u8>,
202 stderr_raw: Vec<u8>,
203 stdout_history_bytes: usize,
204 stderr_history_bytes: usize,
205 combined_history_bytes: usize,
206 stdout_closed: bool,
207 stderr_closed: bool,
208}
209
210const RETURNCODE_NOT_SET: i64 = i64::MIN;
212
213struct SharedState {
214 queues: Mutex<QueueState>,
215 condvar: Condvar,
216 capture_limit: Option<usize>,
217 capture_overflowed: AtomicBool,
218 active_capture_readers: std::sync::atomic::AtomicUsize,
219 returncode: AtomicI64,
222 observer: Option<ObserverEmitter>,
227 observer_exit_emitted: AtomicBool,
230}
231
232struct ChildState {
233 child: Child,
234 #[cfg(windows)]
235 _job: WindowsJobHandle,
236}
237
238#[cfg(unix)]
239#[derive(Default)]
240struct UnixCaptureWakers {
241 stdout: Option<UnixStream>,
242 stderr: Option<UnixStream>,
243}
244
245#[cfg(unix)]
246struct UnixCancelableReader<R> {
247 reader: R,
248 wake_reader: UnixStream,
249}
250
251#[cfg(any(test, unix))]
252#[derive(Debug, Eq, PartialEq)]
253enum CapturePollAction {
254 Wait,
255 Read,
256 Cancel,
257}
258
259#[cfg(any(test, unix))]
260fn capture_poll_action(capture_revents: i16, wake_revents: i16) -> CapturePollAction {
261 if wake_revents != 0 {
262 CapturePollAction::Cancel
263 } else if capture_revents != 0 {
264 CapturePollAction::Read
265 } else {
266 CapturePollAction::Wait
267 }
268}
269
270#[cfg(unix)]
271impl<R: Read + AsRawFd> Read for UnixCancelableReader<R> {
272 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
273 if buf.is_empty() {
274 return Ok(0);
275 }
276 loop {
277 let mut poll_fds = [
278 libc::pollfd {
279 fd: self.reader.as_raw_fd(),
280 events: libc::POLLIN | libc::POLLHUP | libc::POLLERR,
281 revents: 0,
282 },
283 libc::pollfd {
284 fd: self.wake_reader.as_raw_fd(),
285 events: libc::POLLIN | libc::POLLHUP | libc::POLLERR,
286 revents: 0,
287 },
288 ];
289 let polled = unsafe { libc::poll(poll_fds.as_mut_ptr(), poll_fds.len() as _, -1) };
290 if polled < 0 {
291 let error = std::io::Error::last_os_error();
292 if error.kind() == std::io::ErrorKind::Interrupted {
293 continue;
294 }
295 return Err(error);
296 }
297 match capture_poll_action(poll_fds[0].revents, poll_fds[1].revents) {
298 CapturePollAction::Cancel => {
299 return Err(std::io::Error::new(
300 std::io::ErrorKind::Interrupted,
301 "capture reader cancelled",
302 ));
303 }
304 CapturePollAction::Read => match self.reader.read(buf) {
305 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => continue,
306 result => return result,
307 },
308 CapturePollAction::Wait => {}
309 }
310 }
311 }
312}
313
314#[cfg(unix)]
315fn set_nonblocking(fd: RawFd) -> std::io::Result<()> {
316 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
317 if flags < 0 {
318 return Err(std::io::Error::last_os_error());
319 }
320 if unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0 {
321 return Err(std::io::Error::last_os_error());
322 }
323 Ok(())
324}
325
326#[cfg(unix)]
327fn cleanup_child_after_start_error(mut child: Child) {
328 let _ = child.kill();
329 thread::spawn(move || {
332 let _ = child.wait();
333 });
334}
335
336impl SharedState {
337 #[cfg(test)]
338 fn new(capture: bool) -> Self {
339 Self::with_observer_and_limit(capture, None, None)
340 }
341
342 fn with_observer_and_limit(
343 capture: bool,
344 observer: Option<ObserverEmitter>,
345 capture_limit: Option<usize>,
346 ) -> Self {
347 let queues = QueueState {
348 stdout_closed: !capture,
349 stderr_closed: !capture,
350 ..QueueState::default()
351 };
352 Self {
353 queues: Mutex::new(queues),
354 condvar: Condvar::new(),
355 capture_limit,
356 capture_overflowed: AtomicBool::new(false),
357 active_capture_readers: std::sync::atomic::AtomicUsize::new(0),
358 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
359 observer,
360 observer_exit_emitted: AtomicBool::new(false),
361 }
362 }
363
364 fn emit_exited(&self, pid: u32, exit_code: i32) {
367 let Some(emitter) = self.observer.as_ref() else {
368 return;
369 };
370 if self
371 .observer_exit_emitted
372 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
373 .is_ok()
374 {
375 emitter.emit_exited(pid, exit_code);
376 }
377 }
378}
379
380pub struct NativeProcess {
387 config: ProcessConfig,
388 command_override: Mutex<Option<Command>>,
389 child: Arc<Mutex<Option<ChildState>>>,
390 stdin: Mutex<Option<ChildStdin>>,
391 shared: Arc<SharedState>,
392 #[cfg(test)]
393 stdin_write_active: AtomicBool,
394 #[cfg(windows)]
395 capture_pipe_handles: Arc<Mutex<CapturePipeHandles>>,
396 #[cfg(unix)]
397 capture_wakers: Arc<Mutex<UnixCaptureWakers>>,
398}
399
400impl NativeProcess {
401 pub fn new(config: ProcessConfig) -> Self {
407 Self::new_with_options(config, 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);
428 (process, subscriber)
429 }
430
431 fn new_with_capture_limit(config: ProcessConfig, capture_limit: usize) -> Self {
432 Self::new_with_options(config, None, Some(capture_limit), None)
433 }
434
435 fn new_with_command_capture_limit(
436 command: Command,
437 config: ProcessConfig,
438 capture_limit: usize,
439 ) -> Self {
440 Self::new_with_options(config, None, Some(capture_limit), Some(command))
441 }
442
443 fn new_with_options(
444 config: ProcessConfig,
445 observer: Option<ObserverEmitter>,
446 capture_limit: Option<usize>,
447 command_override: Option<Command>,
448 ) -> Self {
449 let shared = SharedState::with_observer_and_limit(config.capture, observer, capture_limit);
450 Self {
451 shared: Arc::new(shared),
452 command_override: Mutex::new(command_override),
453 child: Arc::new(Mutex::new(None)),
454 stdin: Mutex::new(None),
455 #[cfg(test)]
456 stdin_write_active: AtomicBool::new(false),
457 config,
458 #[cfg(windows)]
459 capture_pipe_handles: Arc::new(Mutex::new(CapturePipeHandles::default())),
460 #[cfg(unix)]
461 capture_wakers: Arc::new(Mutex::new(UnixCaptureWakers::default())),
462 }
463 }
464
465 #[inline(never)]
467 pub fn start(&self) -> Result<(), ProcessError> {
472 public_symbols::rp_native_process_start_public(self)
473 }
474
475 fn start_impl(&self) -> Result<(), ProcessError> {
476 crate::rp_rust_debug_scope!("running_process::NativeProcess::start");
477 let mut guard = self.child.lock().expect("child mutex poisoned");
478 if guard.is_some() {
479 return Err(ProcessError::AlreadyStarted);
480 }
481
482 let mut command = self.build_command();
483 match self.config.stdin_mode {
484 StdinMode::Inherit => {}
485 StdinMode::Piped => {
486 command.stdin(Stdio::piped());
487 }
488 StdinMode::Null => {
489 command.stdin(Stdio::null());
490 }
491 }
492 if self.config.capture {
493 command.stdout(Stdio::piped());
494 command.stderr(Stdio::piped());
495 }
496
497 let mut child = command.spawn().map_err(ProcessError::Spawn)?;
498 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
499 if let Some(emitter) = self.shared.observer.as_ref() {
502 emitter.emit_started(child.id());
503 }
504 #[cfg(windows)]
509 let job = {
510 let descendant_sink = self
511 .shared
512 .observer
513 .as_ref()
514 .and_then(|e| e.descendant_sink());
515 let direct_pid = child.id();
516 public_symbols::rp_assign_child_to_windows_kill_on_close_job_with_observer_public(
517 &child,
518 descendant_sink,
519 direct_pid,
520 )
521 .map_err(ProcessError::Spawn)?
522 };
523 #[cfg(target_os = "linux")]
527 {
528 if let Some(emitter) = self.shared.observer.as_ref() {
529 if let Some((sink, stop)) = emitter.descendant_pump() {
530 crate::observer::descendants_linux::enable_subreaper();
531 crate::observer::descendants_linux::spawn_pump(child.id(), sink, stop);
532 }
533 }
534 }
535 #[cfg(target_os = "macos")]
539 {
540 if let Some(emitter) = self.shared.observer.as_ref() {
541 if let Some((sink, stop)) = emitter.descendant_pump() {
542 crate::observer::descendants_macos::spawn_pump(child.id(), sink, stop);
543 }
544 }
545 }
546 if self.config.capture {
547 let stdout = child.stdout.take().expect("stdout pipe missing");
548 let stderr = child.stderr.take().expect("stderr pipe missing");
549 #[cfg(windows)]
550 {
551 use std::os::windows::io::AsRawHandle;
552 let mut handles = self
553 .capture_pipe_handles
554 .lock()
555 .expect("capture pipe handles mutex poisoned");
556 handles.stdout = Some(stdout.as_raw_handle() as usize);
557 handles.stderr = Some(stderr.as_raw_handle() as usize);
558 }
559 #[cfg(unix)]
560 let ((stdout, stdout_waker), (stderr, stderr_waker)) =
561 match Self::prepare_unix_capture_reader(stdout).and_then(|stdout| {
562 Self::prepare_unix_capture_reader(stderr).map(|stderr| (stdout, stderr))
563 }) {
564 Ok(readers) => readers,
565 Err(error) => {
566 cleanup_child_after_start_error(child);
567 return Err(ProcessError::Spawn(error));
568 }
569 };
570 #[cfg(unix)]
571 {
572 let mut wakers = self
573 .capture_wakers
574 .lock()
575 .expect("capture wakers mutex poisoned");
576 wakers.stdout = Some(stdout_waker);
577 wakers.stderr = Some(stderr_waker);
578 }
579 self.spawn_reader(
580 stdout,
581 StreamKind::Stdout,
582 StreamKind::Stdout,
583 self.pipe_done_callback(StreamKind::Stdout),
584 );
585 self.spawn_reader(
586 stderr,
587 StreamKind::Stderr,
588 match self.config.stderr_mode {
589 StderrMode::Stdout => StreamKind::Stdout,
590 StderrMode::Pipe => StreamKind::Stderr,
591 },
592 self.pipe_done_callback(StreamKind::Stderr),
593 );
594 }
595 *self.stdin.lock().expect("stdin mutex poisoned") = child.stdin.take();
596 *guard = Some(ChildState {
597 child,
598 #[cfg(windows)]
599 _job: job,
600 });
601 drop(guard);
602 self.spawn_exit_waiter();
603 Ok(())
604 }
605
606 fn spawn_exit_waiter(&self) {
609 let child = Arc::clone(&self.child);
610 let shared = Arc::clone(&self.shared);
611 let capture = self.config.capture;
612 #[cfg(windows)]
613 let capture_pipe_handles = Arc::clone(&self.capture_pipe_handles);
614 #[cfg(unix)]
615 let capture_wakers = Arc::clone(&self.capture_wakers);
616 thread::spawn(move || {
617 loop {
618 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
619 return;
620 }
621 let exited = {
622 let mut guard = child.lock().expect("child mutex poisoned");
623 if let Some(child_state) = guard.as_mut() {
624 let pid = child_state.child.id();
625 match child_state.child.try_wait() {
626 Ok(Some(status)) => {
627 let code = exit_code(status);
628 shared.returncode.store(code as i64, Ordering::Release);
629 shared.emit_exited(pid, code);
633 shared.condvar.notify_all();
634 true
635 }
636 Ok(None) => false,
637 Err(_error) => {
638 #[cfg(unix)]
639 if child_try_wait_error_is_retryable(&_error) {
640 false
641 } else {
642 return;
643 }
644 #[cfg(windows)]
645 return;
646 }
647 }
648 } else {
649 return;
650 }
651 };
652 if exited {
653 if capture {
668 let drained = finalize_capture_completion(&shared, kill_drain_deadline());
669 #[cfg(windows)]
670 if !drained {
671 cancel_capture_pipe_io(&capture_pipe_handles);
672 }
673 #[cfg(unix)]
674 if !drained {
675 cancel_capture_pipe_io(&capture_wakers);
676 }
677 #[cfg(not(any(windows, unix)))]
678 let _ = drained;
679 }
680 return;
681 }
682 thread::sleep(Duration::from_millis(10));
688 }
689 });
690 }
691
692 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
694 if self.child.lock().expect("child mutex poisoned").is_none() {
695 return Err(ProcessError::NotRunning);
696 }
697 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
698 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
699 use std::io::Write;
700 #[cfg(test)]
701 self.stdin_write_active.store(true, Ordering::Release);
702 let write_result = stdin.write_all(data);
703 #[cfg(test)]
704 self.stdin_write_active.store(false, Ordering::Release);
705 write_result.map_err(ProcessError::Io)?;
706 stdin.flush().map_err(ProcessError::Io)?;
707 drop(guard.take());
708 Ok(())
709 }
710
711 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
716 if self.child.lock().expect("child mutex poisoned").is_none() {
717 return Err(ProcessError::NotRunning);
718 }
719 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
720 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
721 use std::io::Write;
722 #[cfg(test)]
723 self.stdin_write_active.store(true, Ordering::Release);
724 let write_result = stdin.write_all(data);
725 #[cfg(test)]
726 self.stdin_write_active.store(false, Ordering::Release);
727 write_result.map_err(ProcessError::Io)?;
728 stdin.flush().map_err(ProcessError::Io)?;
729 Ok(())
730 }
731
732 pub fn close_stdin(&self) -> Result<(), ProcessError> {
735 if self.child.lock().expect("child mutex poisoned").is_none() {
736 return Err(ProcessError::NotRunning);
737 }
738 drop(self.stdin.lock().expect("stdin mutex poisoned").take());
739 Ok(())
740 }
741
742 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
746 if let Some(code) = self.returncode() {
748 return Ok(Some(code));
749 }
750 let mut guard = self.child.lock().expect("child mutex poisoned");
751 let Some(child_state) = guard.as_mut() else {
752 return Ok(self.returncode());
753 };
754 let pid = child_state.child.id();
755 let child = &mut child_state.child;
756 let status = child.try_wait().map_err(ProcessError::Io)?;
757 if let Some(status) = status {
758 let code = exit_code(status);
759 self.set_returncode(code);
760 self.shared.emit_exited(pid, code);
761 return Ok(Some(code));
762 }
763 Ok(None)
764 }
765
766 #[inline(never)]
768 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
773 public_symbols::rp_native_process_wait_public(self, timeout)
774 }
775
776 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
777 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
778 if self.child.lock().expect("child mutex poisoned").is_none() {
779 return self.returncode().ok_or(ProcessError::NotRunning);
780 }
781 if let Some(code) = self.returncode() {
783 self.finish_capture_drain();
784 return Ok(code);
785 }
786 let start = Instant::now();
787 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
788 loop {
789 let rc = self.shared.returncode.load(Ordering::Acquire);
791 if rc != RETURNCODE_NOT_SET {
792 drop(guard);
793 let code = rc as i32;
794 self.finish_capture_drain();
795 return Ok(code);
796 }
797 if let Some(limit) = timeout {
798 let elapsed = start.elapsed();
799 if elapsed >= limit {
800 return Err(ProcessError::Timeout);
801 }
802 let remaining = limit - elapsed;
803 let wait_time = remaining.min(Duration::from_millis(50));
805 guard = self
806 .shared
807 .condvar
808 .wait_timeout(guard, wait_time)
809 .expect("queue mutex poisoned")
810 .0;
811 } else {
812 guard = self
814 .shared
815 .condvar
816 .wait_timeout(guard, Duration::from_millis(50))
817 .expect("queue mutex poisoned")
818 .0;
819 }
820 }
821 }
822
823 #[inline(never)]
825 pub fn kill(&self) -> Result<(), ProcessError> {
827 public_symbols::rp_native_process_kill_public(self)
828 }
829
830 fn kill_impl(&self) -> Result<(), ProcessError> {
831 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
832 #[cfg(windows)]
833 {
834 let mut guard = self.child.lock().expect("child mutex poisoned");
835 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
836 let pid = child.id();
837 child.kill().map_err(ProcessError::Io)?;
838 let status = child.wait().map_err(ProcessError::Io)?;
839 let code = exit_code(status);
840 self.set_returncode(code);
841 self.shared.emit_exited(pid, code);
844 }
845 #[cfg(unix)]
846 {
847 let deadline = kill_drain_deadline();
848 let (pid, already_reaped) = with_child_lock_for_signal(&self.child, |state| {
849 let child = &mut state.as_mut().ok_or(ProcessError::NotRunning)?.child;
850 let pid = child.id();
851 match child_signal_disposition(child.try_wait()).map_err(ProcessError::Io)? {
852 ChildSignalDisposition::AlreadyExited(status) => Ok((pid, Some(status))),
853 ChildSignalDisposition::Signal => {
854 let group_signaled = self.config.create_process_group
855 && unix_signal_process_group(pid as i32, UnixSignal::Kill).is_ok();
856 if !group_signaled {
857 child.kill().map_err(ProcessError::Io)?;
858 }
859 Ok((pid, None))
860 }
861 }
862 })?;
863
864 self.cancel_capture_io();
868 let reaped = already_reaped.or_else(|| {
869 let reap_result =
870 poll_mutex_until(&self.child, deadline, Duration::from_millis(10), |state| {
871 match state.as_mut() {
872 Some(child) => child.child.try_wait(),
873 None => Ok(None),
874 }
875 });
876 completed_reap_after_signal(reap_result)
877 });
878 if let Some(status) = reaped {
879 let code = exit_code(status);
880 self.set_returncode(code);
881 self.shared.emit_exited(pid, code);
882 }
883 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
884 self, deadline,
885 );
886 Ok(())
887 }
888 #[cfg(windows)]
889 {
890 #[cfg(any(windows, unix))]
898 self.cancel_capture_io();
899 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
910 self,
911 kill_drain_deadline(),
912 );
913 Ok(())
914 }
915 }
916
917 pub fn terminate(&self) -> Result<(), ProcessError> {
921 self.kill()
922 }
923
924 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
939 #[cfg(unix)]
940 {
941 if !self.config.create_process_group {
942 return Ok(());
943 }
944 let pid = match self.pid() {
945 Some(p) => p as i32,
946 None => return Err(ProcessError::NotRunning),
947 };
948 let result = unsafe { libc::kill(-pid, libc::SIGTERM) };
949 if result != 0 {
950 let err = std::io::Error::last_os_error();
951 if err.raw_os_error() != Some(libc::ESRCH) {
952 return Err(ProcessError::Io(err));
953 }
954 }
955 Ok(())
956 }
957 #[cfg(windows)]
958 {
959 if !self.config.create_process_group {
960 return Ok(());
965 }
966 let pid = match self.pid() {
967 Some(p) => p,
968 None => return Err(ProcessError::NotRunning),
969 };
970 let ok = unsafe {
974 winapi::um::wincon::GenerateConsoleCtrlEvent(
975 winapi::um::wincon::CTRL_BREAK_EVENT,
976 pid,
977 )
978 };
979 if ok == 0 {
980 let err = std::io::Error::last_os_error();
981 if err.raw_os_error() != Some(6) {
987 return Err(ProcessError::Io(err));
988 }
989 }
990 Ok(())
991 }
992 }
993
994 #[inline(never)]
996 pub fn close(&self) -> Result<(), ProcessError> {
998 public_symbols::rp_native_process_close_public(self)
999 }
1000
1001 fn close_impl(&self) -> Result<(), ProcessError> {
1002 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
1003 if self.child.lock().expect("child mutex poisoned").is_none() {
1004 return Ok(());
1005 }
1006 if self.poll()?.is_none() {
1007 self.kill()?;
1008 } else {
1009 self.finish_capture_drain();
1010 }
1011 Ok(())
1012 }
1013
1014 pub fn pid(&self) -> Option<u32> {
1016 self.child
1017 .lock()
1018 .expect("child mutex poisoned")
1019 .as_ref()
1020 .map(|state| state.child.id())
1021 }
1022
1023 pub fn returncode(&self) -> Option<i32> {
1025 let v = self.shared.returncode.load(Ordering::Acquire);
1026 if v == RETURNCODE_NOT_SET {
1027 None
1028 } else {
1029 Some(v as i32)
1030 }
1031 }
1032
1033 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
1035 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1036 return false;
1037 }
1038 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1039 match stream {
1040 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
1041 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
1042 }
1043 }
1044
1045 pub fn has_pending_combined(&self) -> bool {
1047 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1048 !guard.combined_queue.is_empty()
1049 }
1050
1051 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
1053 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1054 return Vec::new();
1055 }
1056 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1057 let queue = match stream {
1058 StreamKind::Stdout => &mut guard.stdout_queue,
1059 StreamKind::Stderr => &mut guard.stderr_queue,
1060 };
1061 queue.drain(..).collect()
1062 }
1063
1064 pub fn drain_combined(&self) -> Vec<StreamEvent> {
1066 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1067 guard.combined_queue.drain(..).collect()
1068 }
1069
1070 pub fn read_stream(
1075 &self,
1076 stream: StreamKind,
1077 timeout: Option<Duration>,
1078 ) -> ReadStatus<Vec<u8>> {
1079 let deadline = timeout.map(|limit| Instant::now() + limit);
1080 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1081
1082 loop {
1083 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1084 return ReadStatus::Eof;
1085 }
1086
1087 let queue = match stream {
1088 StreamKind::Stdout => &mut guard.stdout_queue,
1089 StreamKind::Stderr => &mut guard.stderr_queue,
1090 };
1091 if let Some(line) = queue.pop_front() {
1092 return ReadStatus::Line(line);
1093 }
1094
1095 let closed = match stream {
1096 StreamKind::Stdout => {
1097 if self.config.stderr_mode == StderrMode::Stdout {
1098 guard.stdout_closed && guard.stderr_closed
1099 } else {
1100 guard.stdout_closed
1101 }
1102 }
1103 StreamKind::Stderr => guard.stderr_closed,
1104 };
1105 if closed {
1106 return ReadStatus::Eof;
1107 }
1108
1109 match deadline {
1110 Some(deadline) => {
1111 let now = Instant::now();
1112 if now >= deadline {
1113 return ReadStatus::Timeout;
1114 }
1115 let wait = deadline.saturating_duration_since(now);
1116 let result = self
1117 .shared
1118 .condvar
1119 .wait_timeout(guard, wait)
1120 .expect("queue mutex poisoned");
1121 guard = result.0;
1122 if result.1.timed_out() {
1123 return ReadStatus::Timeout;
1124 }
1125 }
1126 None => {
1127 guard = self
1128 .shared
1129 .condvar
1130 .wait(guard)
1131 .expect("queue mutex poisoned");
1132 }
1133 }
1134 }
1135 }
1136
1137 #[inline(never)]
1139 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1141 public_symbols::rp_native_process_read_combined_public(self, timeout)
1142 }
1143
1144 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1145 crate::rp_rust_debug_scope!("running_process::NativeProcess::read_combined");
1146 let deadline = timeout.map(|limit| Instant::now() + limit);
1147 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1148
1149 loop {
1150 if let Some(event) = guard.combined_queue.pop_front() {
1151 return ReadStatus::Line(event);
1152 }
1153 if guard.stdout_closed && guard.stderr_closed {
1154 return ReadStatus::Eof;
1155 }
1156
1157 match deadline {
1158 Some(deadline) => {
1159 let now = Instant::now();
1160 if now >= deadline {
1161 return ReadStatus::Timeout;
1162 }
1163 let wait = deadline.saturating_duration_since(now);
1164 let result = self
1165 .shared
1166 .condvar
1167 .wait_timeout(guard, wait)
1168 .expect("queue mutex poisoned");
1169 guard = result.0;
1170 if result.1.timed_out() {
1171 return ReadStatus::Timeout;
1172 }
1173 }
1174 None => {
1175 guard = self
1176 .shared
1177 .condvar
1178 .wait(guard)
1179 .expect("queue mutex poisoned");
1180 }
1181 }
1182 }
1183 }
1184
1185 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
1187 self.shared
1188 .queues
1189 .lock()
1190 .expect("queue mutex poisoned")
1191 .stdout_history
1192 .clone()
1193 .into_iter()
1194 .collect()
1195 }
1196
1197 fn captured_stdout_raw(&self) -> Vec<u8> {
1198 self.shared
1199 .queues
1200 .lock()
1201 .expect("queue mutex poisoned")
1202 .stdout_raw
1203 .clone()
1204 }
1205
1206 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
1208 if self.config.stderr_mode == StderrMode::Stdout {
1209 return Vec::new();
1210 }
1211 self.shared
1212 .queues
1213 .lock()
1214 .expect("queue mutex poisoned")
1215 .stderr_history
1216 .clone()
1217 .into_iter()
1218 .collect()
1219 }
1220
1221 fn captured_stderr_raw(&self) -> Vec<u8> {
1222 if self.config.stderr_mode == StderrMode::Stdout {
1223 return Vec::new();
1224 }
1225 self.shared
1226 .queues
1227 .lock()
1228 .expect("queue mutex poisoned")
1229 .stderr_raw
1230 .clone()
1231 }
1232
1233 pub fn captured_combined(&self) -> Vec<StreamEvent> {
1235 self.shared
1236 .queues
1237 .lock()
1238 .expect("queue mutex poisoned")
1239 .combined_history
1240 .clone()
1241 .into_iter()
1242 .collect()
1243 }
1244
1245 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
1247 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1248 return 0;
1249 }
1250 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1251 match stream {
1252 StreamKind::Stdout => guard.stdout_history_bytes,
1253 StreamKind::Stderr => guard.stderr_history_bytes,
1254 }
1255 }
1256
1257 pub fn captured_combined_bytes(&self) -> usize {
1259 self.shared
1260 .queues
1261 .lock()
1262 .expect("queue mutex poisoned")
1263 .combined_history_bytes
1264 }
1265
1266 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
1268 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1269 return 0;
1270 }
1271 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1272 match stream {
1273 StreamKind::Stdout => {
1274 let released = guard.stdout_history_bytes;
1275 guard.stdout_history.clear();
1276 guard.stdout_raw.clear();
1277 guard.stdout_history_bytes = 0;
1278 released
1279 }
1280 StreamKind::Stderr => {
1281 let released = guard.stderr_history_bytes;
1282 guard.stderr_history.clear();
1283 guard.stderr_raw.clear();
1284 guard.stderr_history_bytes = 0;
1285 released
1286 }
1287 }
1288 }
1289
1290 pub fn clear_captured_combined(&self) -> usize {
1292 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1293 let released = guard.combined_history_bytes;
1294 guard.combined_history.clear();
1295 guard.combined_history_bytes = 0;
1296 released
1297 }
1298
1299 fn build_command(&self) -> Command {
1300 let command_override = self
1301 .command_override
1302 .lock()
1303 .expect("command override mutex poisoned")
1304 .take();
1305 let mut command = match command_override {
1306 Some(command) => command,
1307 None => {
1308 let mut command = match &self.config.command {
1309 CommandSpec::Shell(command) => shell_command(command),
1310 CommandSpec::Argv(argv) => {
1311 let mut command = Command::new(&argv[0]);
1312 if argv.len() > 1 {
1313 command.args(&argv[1..]);
1314 }
1315 command
1316 }
1317 };
1318 if let Some(cwd) = &self.config.cwd {
1319 command.current_dir(cwd);
1320 }
1321 if let Some(env) = &self.config.env {
1322 command.env_clear();
1323 command.envs(env.iter().map(|(k, v)| (k, v)));
1324 }
1325 command
1326 }
1327 };
1328 #[cfg(windows)]
1329 {
1330 use std::os::windows::process::CommandExt;
1331
1332 let flags = windows_creation_flags(
1340 self.config.creationflags,
1341 self.config.create_process_group,
1342 self.config.nice,
1343 crate::windows::parent_has_console(),
1344 );
1345 if flags != 0 {
1346 command.creation_flags(flags);
1347 }
1348 }
1349 #[cfg(unix)]
1350 {
1351 let create_process_group = self.config.create_process_group;
1352 let nice = self.config.nice;
1353
1354 if create_process_group || nice.is_some() {
1355 use std::os::unix::process::CommandExt;
1356
1357 unsafe {
1358 command.pre_exec(move || {
1359 if create_process_group && libc::setpgid(0, 0) == -1 {
1360 return Err(std::io::Error::last_os_error());
1361 }
1362 if let Some(nice) = nice {
1363 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
1364 if result == -1 {
1365 return Err(std::io::Error::last_os_error());
1366 }
1367 }
1368 Ok(())
1369 });
1370 }
1371 }
1372 }
1373 command
1374 }
1375
1376 fn spawn_reader<R>(
1377 &self,
1378 pipe: R,
1379 source_stream: StreamKind,
1380 visible_stream: StreamKind,
1381 on_pipe_done: Box<dyn FnOnce() + Send>,
1382 ) where
1383 R: Read + Send + 'static,
1384 {
1385 let shared = Arc::clone(&self.shared);
1386 shared.active_capture_readers.fetch_add(1, Ordering::AcqRel);
1387 thread::spawn(move || {
1388 let mut reader = pipe;
1389 let mut chunk = vec![0_u8; 65536];
1390 let mut pending = Vec::new();
1391
1392 loop {
1393 match reader.read(&mut chunk) {
1394 Ok(0) => break,
1395 Ok(n) => {
1396 if append_raw(&shared, visible_stream, &chunk[..n]) {
1397 let lines = feed_chunk(&mut pending, &chunk[..n]);
1398 emit_lines(&shared, visible_stream, lines);
1399 } else {
1400 pending.clear();
1401 }
1402 }
1403 Err(_) => break,
1404 }
1405 }
1406
1407 if !pending.is_empty() && !shared.capture_overflowed.load(Ordering::Acquire) {
1408 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1409 }
1410
1411 on_pipe_done();
1416 drop(reader);
1417
1418 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1419 match source_stream {
1420 StreamKind::Stdout => guard.stdout_closed = true,
1421 StreamKind::Stderr => guard.stderr_closed = true,
1422 }
1423 shared.active_capture_readers.fetch_sub(1, Ordering::AcqRel);
1424 shared.condvar.notify_all();
1425 });
1426 }
1427
1428 #[cfg(unix)]
1429 fn prepare_unix_capture_reader<R: Read + AsRawFd>(
1430 reader: R,
1431 ) -> std::io::Result<(UnixCancelableReader<R>, UnixStream)> {
1432 set_nonblocking(reader.as_raw_fd())?;
1433 let (wake_reader, wake_writer) = UnixStream::pair()?;
1434 wake_writer.set_nonblocking(true)?;
1435 Ok((
1436 UnixCancelableReader {
1437 reader,
1438 wake_reader,
1439 },
1440 wake_writer,
1441 ))
1442 }
1443
1444 #[cfg(windows)]
1445 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1446 let handles = Arc::clone(&self.capture_pipe_handles);
1447 Box::new(move || {
1448 let mut guard = handles.lock().expect("capture pipe handles mutex poisoned");
1449 match stream {
1450 StreamKind::Stdout => guard.stdout = None,
1451 StreamKind::Stderr => guard.stderr = None,
1452 }
1453 })
1454 }
1455
1456 #[cfg(unix)]
1457 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1458 let wakers = Arc::clone(&self.capture_wakers);
1459 Box::new(move || {
1460 let mut guard = wakers.lock().expect("capture wakers mutex poisoned");
1461 match stream {
1462 StreamKind::Stdout => guard.stdout = None,
1463 StreamKind::Stderr => guard.stderr = None,
1464 }
1465 })
1466 }
1467
1468 #[cfg(not(any(windows, unix)))]
1469 fn pipe_done_callback(&self, _stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1470 Box::new(|| {})
1471 }
1472
1473 #[cfg(windows)]
1477 fn cancel_capture_io(&self) {
1478 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1479 cancel_capture_pipe_io(&self.capture_pipe_handles);
1480 }
1481
1482 #[cfg(unix)]
1483 fn cancel_capture_io(&self) {
1484 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1485 cancel_capture_pipe_io(&self.capture_wakers);
1486 }
1487
1488 fn set_returncode(&self, code: i32) {
1489 self.shared.returncode.store(code as i64, Ordering::Release);
1490 self.shared.condvar.notify_all();
1491 }
1492
1493 fn finish_capture_drain(&self) {
1503 self.finish_capture_drain_with_deadline(kill_drain_deadline());
1504 }
1505
1506 fn finish_capture_drain_with_deadline(&self, deadline: Instant) {
1507 let drained = self.wait_for_capture_completion_with_deadline_impl(deadline);
1508 #[cfg(any(windows, unix))]
1509 if !drained {
1510 self.cancel_capture_io();
1511 }
1512 #[cfg(not(any(windows, unix)))]
1513 let _ = drained;
1514 }
1515
1516 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1519 crate::rp_rust_debug_scope!(
1520 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1521 );
1522 if !self.config.capture {
1523 return true;
1524 }
1525 finalize_capture_completion(&self.shared, deadline)
1526 }
1527
1528 fn wait_for_capture_readers_with_deadline(&self, deadline: Instant) -> bool {
1529 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1530 while self.shared.active_capture_readers.load(Ordering::Acquire) != 0 {
1531 let now = Instant::now();
1532 if now >= deadline {
1533 return false;
1534 }
1535 let (next_guard, result) = self
1536 .shared
1537 .condvar
1538 .wait_timeout(guard, deadline - now)
1539 .expect("queue mutex poisoned");
1540 guard = next_guard;
1541 if result.timed_out() && self.shared.active_capture_readers.load(Ordering::Acquire) != 0
1542 {
1543 return false;
1544 }
1545 }
1546 true
1547 }
1548}
1549
1550#[cfg(windows)]
1556fn cancel_capture_pipe_io(handles: &Mutex<CapturePipeHandles>) {
1557 use winapi::shared::ntdef::HANDLE;
1558 use winapi::um::ioapiset::CancelIoEx;
1559 let guard = handles.lock().expect("capture pipe handles mutex poisoned");
1560 if let Some(h) = guard.stdout {
1561 unsafe {
1567 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1568 }
1569 }
1570 if let Some(h) = guard.stderr {
1571 unsafe {
1572 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1573 }
1574 }
1575}
1576
1577#[cfg(unix)]
1578fn cancel_capture_pipe_io(wakers: &Mutex<UnixCaptureWakers>) {
1579 use std::os::fd::AsRawFd;
1580
1581 let guard = wakers.lock().expect("capture wakers mutex poisoned");
1582 let byte = [1_u8; 1];
1583 for writer in [&guard.stdout, &guard.stderr].into_iter().flatten() {
1584 let _ = unsafe { libc::write(writer.as_raw_fd(), byte.as_ptr().cast(), byte.len()) };
1588 }
1589}
1590
1591fn finalize_capture_completion(shared: &SharedState, deadline: Instant) -> bool {
1598 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1599 while !(guard.stdout_closed && guard.stderr_closed) {
1600 let now = Instant::now();
1601 if now >= deadline {
1602 guard.stdout_closed = true;
1603 guard.stderr_closed = true;
1604 shared.condvar.notify_all();
1605 return false;
1606 }
1607 let (next_guard, result) = shared
1608 .condvar
1609 .wait_timeout(guard, deadline - now)
1610 .expect("queue mutex poisoned");
1611 guard = next_guard;
1612 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1613 guard.stdout_closed = true;
1614 guard.stderr_closed = true;
1615 shared.condvar.notify_all();
1616 return false;
1617 }
1618 }
1619 true
1620}
1621
1622fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1623 if lines.is_empty() || shared.capture_overflowed.load(Ordering::Acquire) {
1624 return;
1625 }
1626 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1627 if shared.capture_overflowed.load(Ordering::Acquire) {
1628 return;
1629 }
1630 for line in lines {
1631 let line_len = line.len();
1632 match stream {
1633 StreamKind::Stdout => {
1634 guard.stdout_history_bytes += line_len;
1635 guard.stdout_history.push_back(line.clone());
1636 guard.stdout_queue.push_back(line.clone());
1637 }
1638 StreamKind::Stderr => {
1639 guard.stderr_history_bytes += line_len;
1640 guard.stderr_history.push_back(line.clone());
1641 guard.stderr_queue.push_back(line.clone());
1642 }
1643 }
1644 let event = StreamEvent { stream, line };
1645 guard.combined_history_bytes += line_len;
1646 guard.combined_history.push_back(event.clone());
1647 guard.combined_queue.push_back(event);
1648 }
1649 shared.condvar.notify_all();
1650}
1651
1652fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) -> bool {
1653 if chunk.is_empty() {
1654 return true;
1655 }
1656 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1657 let accepted = match shared.capture_limit {
1658 Some(limit) => {
1659 let retained = guard
1660 .stdout_raw
1661 .len()
1662 .saturating_add(guard.stderr_raw.len());
1663 chunk.len().min(limit.saturating_sub(retained))
1664 }
1665 None => chunk.len(),
1666 };
1667 match stream {
1668 StreamKind::Stdout => guard.stdout_raw.extend_from_slice(&chunk[..accepted]),
1669 StreamKind::Stderr => guard.stderr_raw.extend_from_slice(&chunk[..accepted]),
1670 }
1671 if accepted != chunk.len() {
1672 shared.capture_overflowed.store(true, Ordering::Release);
1673 false
1674 } else {
1675 true
1676 }
1677}
1678
1679pub fn run_command(
1685 mut config: ProcessConfig,
1686 timeout: Option<Duration>,
1687) -> Result<RunOutput, ProcessError> {
1688 config.capture = true;
1689 let process = NativeProcess::new(config);
1690 process.start()?;
1691
1692 let exit_code = match process.wait(timeout) {
1693 Ok(code) => code,
1694 Err(ProcessError::Timeout) => {
1695 match process.kill() {
1696 Ok(()) | Err(ProcessError::NotRunning) => {}
1697 Err(error) => return Err(error),
1698 }
1699 return Err(ProcessError::Timeout);
1700 }
1701 Err(error) => return Err(error),
1702 };
1703
1704 Ok(RunOutput {
1705 stdout: process.captured_stdout_raw(),
1706 stderr: process.captured_stderr_raw(),
1707 exit_code,
1708 })
1709}
1710
1711struct BoundedRunCleanup<'a> {
1712 process: &'a NativeProcess,
1713 armed: bool,
1714}
1715
1716impl BoundedRunCleanup<'_> {
1717 fn disarm(&mut self) {
1718 self.armed = false;
1719 }
1720}
1721
1722impl Drop for BoundedRunCleanup<'_> {
1723 fn drop(&mut self) {
1724 if !self.armed {
1725 return;
1726 }
1727
1728 #[cfg(any(windows, unix))]
1732 self.process.cancel_capture_io();
1733 let _ = self.process.poll();
1734 if self.process.returncode().is_none() {
1735 let _ = self.process.kill();
1736 } else {
1737 self.process.finish_capture_drain();
1738 }
1739 let _ = self
1740 .process
1741 .wait_for_capture_readers_with_deadline(kill_drain_deadline());
1742 }
1743}
1744
1745fn run_native_process_bounded(
1746 process: NativeProcess,
1747 timeout: Option<Duration>,
1748 output_limit: usize,
1749) -> Result<RunOutput, ProcessError> {
1750 process.start()?;
1751 let mut cleanup = BoundedRunCleanup {
1752 process: &process,
1753 armed: true,
1754 };
1755 let started = Instant::now();
1756
1757 let exit_code = loop {
1758 if process.shared.capture_overflowed.load(Ordering::Acquire) {
1759 return Err(ProcessError::OutputLimitExceeded {
1760 limit: output_limit,
1761 });
1762 }
1763 if let Some(code) = process.poll()? {
1764 process.finish_capture_drain();
1765 break code;
1766 }
1767 if timeout.is_some_and(|limit| started.elapsed() >= limit) {
1768 return Err(ProcessError::Timeout);
1769 }
1770 thread::sleep(Duration::from_millis(5));
1771 };
1772
1773 if !process.wait_for_capture_readers_with_deadline(kill_drain_deadline()) {
1774 return Err(ProcessError::Io(std::io::Error::new(
1775 std::io::ErrorKind::TimedOut,
1776 "capture readers did not stop after process exit",
1777 )));
1778 }
1779 if process.shared.capture_overflowed.load(Ordering::Acquire) {
1780 return Err(ProcessError::OutputLimitExceeded {
1781 limit: output_limit,
1782 });
1783 }
1784
1785 let output = RunOutput {
1786 stdout: process.captured_stdout_raw(),
1787 stderr: process.captured_stderr_raw(),
1788 exit_code,
1789 };
1790 cleanup.disarm();
1791 Ok(output)
1792}
1793
1794pub fn run_command_bounded(
1803 mut config: ProcessConfig,
1804 timeout: Option<Duration>,
1805 output_limit: usize,
1806) -> Result<RunOutput, ProcessError> {
1807 config.capture = true;
1808 config.create_process_group = true;
1809 let process = NativeProcess::new_with_capture_limit(config, output_limit);
1810 run_native_process_bounded(process, timeout, output_limit)
1811}
1812
1813pub fn run_std_command_bounded(
1820 command: Command,
1821 timeout: Option<Duration>,
1822 output_limit: usize,
1823) -> Result<RunOutput, ProcessError> {
1824 let config = ProcessConfig {
1825 command: CommandSpec::Argv(vec!["running-process-command-override".to_string()]),
1829 cwd: None,
1830 env: None,
1831 capture: true,
1832 stderr_mode: StderrMode::Pipe,
1833 creationflags: None,
1834 create_process_group: true,
1835 stdin_mode: StdinMode::Null,
1836 nice: None,
1837 };
1838 let process = NativeProcess::new_with_command_capture_limit(command, config, output_limit);
1839 run_native_process_bounded(process, timeout, output_limit)
1840}
1841
1842pub(crate) fn shell_command(command: &str) -> Command {
1843 #[cfg(windows)]
1844 {
1845 use std::os::windows::process::CommandExt;
1846
1847 let mut cmd = Command::new("cmd");
1848 cmd.raw_arg("/D /S /C \"");
1849 cmd.raw_arg(command);
1850 cmd.raw_arg("\"");
1851 cmd
1852 }
1853 #[cfg(not(windows))]
1854 {
1855 let mut cmd = Command::new("sh");
1856 cmd.arg("-lc").arg(command);
1857 cmd
1858 }
1859}
1860
1861#[cfg(test)]
1862mod tests;