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 = "client")]
72pub mod content_hash;
73
74#[cfg(feature = "probe")]
77pub mod probe;
78
79#[cfg(feature = "client")]
85pub mod maintenance;
86
87#[cfg(feature = "client")]
88pub mod cleanup;
89
90#[cfg(feature = "client")]
94pub mod boot_autostart;
95
96#[cfg(feature = "client")]
101pub mod runpm_config;
102
103#[cfg(feature = "test-support")]
108pub mod test_support;
109
110#[cfg(feature = "telemetry")]
113#[path = "daemon/telemetry.rs"]
114pub mod telemetry;
115
116#[cfg(feature = "daemon")]
119pub mod daemon;
121pub mod process_tree;
122#[cfg(feature = "pty")]
123pub mod pty;
125mod public_symbols;
126mod rust_debug;
127pub mod spawn;
128pub mod systemd_killmode;
129pub mod terminal_graphics;
130mod types;
131#[cfg(unix)]
132mod unix;
133#[cfg(windows)]
134mod windows;
135
136#[cfg(feature = "async-process")]
137pub use async_process::AsyncProcess;
138pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
139pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
140#[cfg(feature = "client")]
142pub use content_hash::blake3_file;
143pub use observer::{
144 CapabilitySupport, CategoryCapability, EventCategory, ObserverCapabilities, ObserverConfig,
145 ObserverEvent, ObserverEventKind, ObserverSubscriber,
146};
147#[cfg(feature = "originator-scan")]
148pub use originator::{
149 find_declared_daemon_pids, find_processes_by_originator, OriginatorProcessInfo,
150};
151pub use output_log::{
152 CursorRead, OutputCursor, OutputLog, OutputRecord, SharedOutputCursor, SharedOutputLog,
153};
154pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
155pub use spawn::{
156 spawn, spawn_daemon, spawn_daemon_breaking_away_from_job,
157 spawn_daemon_breaking_away_with_env_policy, spawn_daemon_with_clear_env,
158 spawn_daemon_with_env_policy, spawn_daemon_with_stdio, spawn_daemon_with_stdio_and_env_policy,
159 spawn_with_env_policy, DaemonChild, DaemonStdio, DaemonStdioSource, EnvironmentPolicy,
160 SpawnStdio, SpawnedChild, StdioSource, DAEMON_MARKER_ENV_VAR,
161};
162#[cfg(feature = "client-async")]
163pub use spawn::{spawn_tokio, TokioSpawnOptions};
164pub use terminal_graphics::{
165 current_terminal_capabilities, current_terminal_capabilities_with_timeout,
166 detect_terminal_capabilities, CapabilityStatus, EvidenceStrength, GraphicsCapability,
167 GraphicsProtocol, TerminalCapabilities, TerminalCapabilityInput, TerminalGraphicsCapabilities,
168 TerminalProbeEvidence,
169};
170pub use types::{
171 CommandSpec, ProcessConfig, ProcessError, ReadStatus, RunOutput, StderrMode, StdinMode,
172 StreamEvent, StreamKind,
173};
174pub use window_icon::{
175 host_icon_support, icon_support, set_host_icon, set_icon, IconError, IconScope, IconSource,
176 IconSupport, StockIcon,
177};
178
179#[cfg(unix)]
180pub(crate) use helpers::{
181 child_signal_disposition, child_try_wait_error_is_retryable, completed_reap_after_signal,
182 poll_mutex_until, with_child_lock_for_signal, ChildSignalDisposition,
183};
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::{
189 assign_child_to_windows_kill_on_close_job_impl, windows_creation_flags, CapturePipeHandles,
190 WindowsJobHandle,
191};
192
193#[macro_export]
194macro_rules! rp_rust_debug_scope {
196 ($label:expr) => {
197 let _running_process_rust_debug_scope =
198 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
199 };
200}
201
202#[derive(Default)]
203struct QueueState {
204 stdout_queue: VecDeque<Vec<u8>>,
205 stderr_queue: VecDeque<Vec<u8>>,
206 combined_queue: VecDeque<StreamEvent>,
207 stdout_history: VecDeque<Vec<u8>>,
208 stderr_history: VecDeque<Vec<u8>>,
209 combined_history: VecDeque<StreamEvent>,
210 stdout_raw: Vec<u8>,
211 stderr_raw: Vec<u8>,
212 stdout_history_bytes: usize,
213 stderr_history_bytes: usize,
214 combined_history_bytes: usize,
215 stdout_closed: bool,
216 stderr_closed: bool,
217}
218
219const RETURNCODE_NOT_SET: i64 = i64::MIN;
221
222struct SharedState {
223 queues: Mutex<QueueState>,
224 condvar: Condvar,
225 capture_limit: Option<usize>,
226 capture_overflowed: AtomicBool,
227 active_capture_readers: std::sync::atomic::AtomicUsize,
228 returncode: AtomicI64,
231 observer: Option<ObserverEmitter>,
236 observer_exit_emitted: AtomicBool,
239}
240
241struct ChildState {
242 child: Child,
243 #[cfg(windows)]
244 _job: WindowsJobHandle,
245}
246
247#[cfg(unix)]
248#[derive(Default)]
249struct UnixCaptureWakers {
250 stdout: Option<UnixStream>,
251 stderr: Option<UnixStream>,
252}
253
254#[cfg(unix)]
255struct UnixCancelableReader<R> {
256 reader: R,
257 wake_reader: UnixStream,
258}
259
260#[cfg(any(test, unix))]
261#[derive(Debug, Eq, PartialEq)]
262enum CapturePollAction {
263 Wait,
264 Read,
265 Cancel,
266}
267
268#[cfg(any(test, unix))]
269fn capture_poll_action(capture_revents: i16, wake_revents: i16) -> CapturePollAction {
270 if wake_revents != 0 {
271 CapturePollAction::Cancel
272 } else if capture_revents != 0 {
273 CapturePollAction::Read
274 } else {
275 CapturePollAction::Wait
276 }
277}
278
279#[cfg(unix)]
280impl<R: Read + AsRawFd> Read for UnixCancelableReader<R> {
281 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
282 if buf.is_empty() {
283 return Ok(0);
284 }
285 loop {
286 let mut poll_fds = [
287 libc::pollfd {
288 fd: self.reader.as_raw_fd(),
289 events: libc::POLLIN | libc::POLLHUP | libc::POLLERR,
290 revents: 0,
291 },
292 libc::pollfd {
293 fd: self.wake_reader.as_raw_fd(),
294 events: libc::POLLIN | libc::POLLHUP | libc::POLLERR,
295 revents: 0,
296 },
297 ];
298 let polled = unsafe { libc::poll(poll_fds.as_mut_ptr(), poll_fds.len() as _, -1) };
299 if polled < 0 {
300 let error = std::io::Error::last_os_error();
301 if error.kind() == std::io::ErrorKind::Interrupted {
302 continue;
303 }
304 return Err(error);
305 }
306 match capture_poll_action(poll_fds[0].revents, poll_fds[1].revents) {
307 CapturePollAction::Cancel => {
308 return Err(std::io::Error::new(
309 std::io::ErrorKind::Interrupted,
310 "capture reader cancelled",
311 ));
312 }
313 CapturePollAction::Read => match self.reader.read(buf) {
314 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => continue,
315 result => return result,
316 },
317 CapturePollAction::Wait => {}
318 }
319 }
320 }
321}
322
323#[cfg(unix)]
324fn set_nonblocking(fd: RawFd) -> std::io::Result<()> {
325 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
326 if flags < 0 {
327 return Err(std::io::Error::last_os_error());
328 }
329 if unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0 {
330 return Err(std::io::Error::last_os_error());
331 }
332 Ok(())
333}
334
335#[cfg(unix)]
336fn cleanup_child_after_start_error(mut child: Child) {
337 let _ = child.kill();
338 thread::spawn(move || {
341 let _ = child.wait();
342 });
343}
344
345impl SharedState {
346 #[cfg(test)]
347 fn new(capture: bool) -> Self {
348 Self::with_observer_and_limit(capture, None, None)
349 }
350
351 fn with_observer_and_limit(
352 capture: bool,
353 observer: Option<ObserverEmitter>,
354 capture_limit: Option<usize>,
355 ) -> Self {
356 let queues = QueueState {
357 stdout_closed: !capture,
358 stderr_closed: !capture,
359 ..QueueState::default()
360 };
361 Self {
362 queues: Mutex::new(queues),
363 condvar: Condvar::new(),
364 capture_limit,
365 capture_overflowed: AtomicBool::new(false),
366 active_capture_readers: std::sync::atomic::AtomicUsize::new(0),
367 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
368 observer,
369 observer_exit_emitted: AtomicBool::new(false),
370 }
371 }
372
373 fn emit_exited(&self, pid: u32, exit_code: i32) {
376 let Some(emitter) = self.observer.as_ref() else {
377 return;
378 };
379 if self
380 .observer_exit_emitted
381 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
382 .is_ok()
383 {
384 emitter.emit_exited(pid, exit_code);
385 }
386 }
387}
388
389pub struct NativeProcess {
396 config: ProcessConfig,
397 command_override: Mutex<Option<Command>>,
398 child: Arc<Mutex<Option<ChildState>>>,
399 stdin: Mutex<Option<ChildStdin>>,
400 shared: Arc<SharedState>,
401 #[cfg(test)]
402 stdin_write_active: AtomicBool,
403 #[cfg(windows)]
404 capture_pipe_handles: Arc<Mutex<CapturePipeHandles>>,
405 #[cfg(unix)]
406 capture_wakers: Arc<Mutex<UnixCaptureWakers>>,
407}
408
409impl NativeProcess {
410 pub fn new(config: ProcessConfig) -> Self {
416 Self::new_with_options(config, None, None, None)
417 }
418
419 pub fn with_observer(
432 config: ProcessConfig,
433 observer: crate::observer::ObserverConfig,
434 ) -> (Self, ObserverSubscriber) {
435 let (emitter, subscriber) = ObserverEmitter::new(observer);
436 let process = Self::new_with_options(config, Some(emitter), None, None);
437 (process, subscriber)
438 }
439
440 fn new_with_capture_limit(config: ProcessConfig, capture_limit: usize) -> Self {
441 Self::new_with_options(config, None, Some(capture_limit), None)
442 }
443
444 fn new_with_command_capture_limit(
445 command: Command,
446 config: ProcessConfig,
447 capture_limit: usize,
448 ) -> Self {
449 Self::new_with_options(config, None, Some(capture_limit), Some(command))
450 }
451
452 fn new_with_options(
453 config: ProcessConfig,
454 observer: Option<ObserverEmitter>,
455 capture_limit: Option<usize>,
456 command_override: Option<Command>,
457 ) -> Self {
458 let shared = SharedState::with_observer_and_limit(config.capture, observer, capture_limit);
459 Self {
460 shared: Arc::new(shared),
461 command_override: Mutex::new(command_override),
462 child: Arc::new(Mutex::new(None)),
463 stdin: Mutex::new(None),
464 #[cfg(test)]
465 stdin_write_active: AtomicBool::new(false),
466 config,
467 #[cfg(windows)]
468 capture_pipe_handles: Arc::new(Mutex::new(CapturePipeHandles::default())),
469 #[cfg(unix)]
470 capture_wakers: Arc::new(Mutex::new(UnixCaptureWakers::default())),
471 }
472 }
473
474 #[inline(never)]
476 pub fn start(&self) -> Result<(), ProcessError> {
481 public_symbols::rp_native_process_start_public(self)
482 }
483
484 fn start_impl(&self) -> Result<(), ProcessError> {
485 crate::rp_rust_debug_scope!("running_process::NativeProcess::start");
486 let mut guard = self.child.lock().expect("child mutex poisoned");
487 if guard.is_some() {
488 return Err(ProcessError::AlreadyStarted);
489 }
490
491 let mut command = self.build_command();
492 match self.config.stdin_mode {
493 StdinMode::Inherit => {}
494 StdinMode::Piped => {
495 command.stdin(Stdio::piped());
496 }
497 StdinMode::Null => {
498 command.stdin(Stdio::null());
499 }
500 }
501 if self.config.capture {
502 command.stdout(Stdio::piped());
503 command.stderr(Stdio::piped());
504 }
505
506 let mut child = command.spawn().map_err(ProcessError::Spawn)?;
507 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
508 if let Some(emitter) = self.shared.observer.as_ref() {
511 emitter.emit_started(child.id());
512 }
513 #[cfg(windows)]
518 let job = {
519 let descendant_sink = self
520 .shared
521 .observer
522 .as_ref()
523 .and_then(|e| e.descendant_sink());
524 let direct_pid = child.id();
525 public_symbols::rp_assign_child_to_windows_kill_on_close_job_with_observer_public(
526 &child,
527 descendant_sink,
528 direct_pid,
529 )
530 .map_err(ProcessError::Spawn)?
531 };
532 #[cfg(target_os = "linux")]
536 {
537 if let Some(emitter) = self.shared.observer.as_ref() {
538 if let Some((sink, stop)) = emitter.descendant_pump() {
539 crate::observer::descendants_linux::enable_subreaper();
540 crate::observer::descendants_linux::spawn_pump(child.id(), sink, stop);
541 }
542 }
543 }
544 #[cfg(target_os = "macos")]
548 {
549 if let Some(emitter) = self.shared.observer.as_ref() {
550 if let Some((sink, stop)) = emitter.descendant_pump() {
551 crate::observer::descendants_macos::spawn_pump(child.id(), sink, stop);
552 }
553 }
554 }
555 if self.config.capture {
556 let stdout = child.stdout.take().expect("stdout pipe missing");
557 let stderr = child.stderr.take().expect("stderr pipe missing");
558 #[cfg(windows)]
559 {
560 use std::os::windows::io::AsRawHandle;
561 let mut handles = self
562 .capture_pipe_handles
563 .lock()
564 .expect("capture pipe handles mutex poisoned");
565 handles.stdout = Some(stdout.as_raw_handle() as usize);
566 handles.stderr = Some(stderr.as_raw_handle() as usize);
567 }
568 #[cfg(unix)]
569 let ((stdout, stdout_waker), (stderr, stderr_waker)) =
570 match Self::prepare_unix_capture_reader(stdout).and_then(|stdout| {
571 Self::prepare_unix_capture_reader(stderr).map(|stderr| (stdout, stderr))
572 }) {
573 Ok(readers) => readers,
574 Err(error) => {
575 cleanup_child_after_start_error(child);
576 return Err(ProcessError::Spawn(error));
577 }
578 };
579 #[cfg(unix)]
580 {
581 let mut wakers = self
582 .capture_wakers
583 .lock()
584 .expect("capture wakers mutex poisoned");
585 wakers.stdout = Some(stdout_waker);
586 wakers.stderr = Some(stderr_waker);
587 }
588 self.spawn_reader(
589 stdout,
590 StreamKind::Stdout,
591 StreamKind::Stdout,
592 self.pipe_done_callback(StreamKind::Stdout),
593 );
594 self.spawn_reader(
595 stderr,
596 StreamKind::Stderr,
597 match self.config.stderr_mode {
598 StderrMode::Stdout => StreamKind::Stdout,
599 StderrMode::Pipe => StreamKind::Stderr,
600 },
601 self.pipe_done_callback(StreamKind::Stderr),
602 );
603 }
604 *self.stdin.lock().expect("stdin mutex poisoned") = child.stdin.take();
605 *guard = Some(ChildState {
606 child,
607 #[cfg(windows)]
608 _job: job,
609 });
610 drop(guard);
611 self.spawn_exit_waiter();
612 Ok(())
613 }
614
615 fn spawn_exit_waiter(&self) {
618 let child = Arc::clone(&self.child);
619 let shared = Arc::clone(&self.shared);
620 let capture = self.config.capture;
621 #[cfg(windows)]
622 let capture_pipe_handles = Arc::clone(&self.capture_pipe_handles);
623 #[cfg(unix)]
624 let capture_wakers = Arc::clone(&self.capture_wakers);
625 thread::spawn(move || {
626 loop {
627 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
628 return;
629 }
630 let exited = {
631 let mut guard = child.lock().expect("child mutex poisoned");
632 if let Some(child_state) = guard.as_mut() {
633 let pid = child_state.child.id();
634 match child_state.child.try_wait() {
635 Ok(Some(status)) => {
636 let code = exit_code(status);
637 shared.returncode.store(code as i64, Ordering::Release);
638 shared.emit_exited(pid, code);
642 shared.condvar.notify_all();
643 true
644 }
645 Ok(None) => false,
646 Err(_error) => {
647 #[cfg(unix)]
648 if child_try_wait_error_is_retryable(&_error) {
649 false
650 } else {
651 return;
652 }
653 #[cfg(windows)]
654 return;
655 }
656 }
657 } else {
658 return;
659 }
660 };
661 if exited {
662 if capture {
677 let drained = finalize_capture_completion(&shared, kill_drain_deadline());
678 #[cfg(windows)]
679 if !drained {
680 cancel_capture_pipe_io(&capture_pipe_handles);
681 }
682 #[cfg(unix)]
683 if !drained {
684 cancel_capture_pipe_io(&capture_wakers);
685 }
686 #[cfg(not(any(windows, unix)))]
687 let _ = drained;
688 }
689 return;
690 }
691 thread::sleep(Duration::from_millis(10));
697 }
698 });
699 }
700
701 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
703 if self.child.lock().expect("child mutex poisoned").is_none() {
704 return Err(ProcessError::NotRunning);
705 }
706 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
707 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
708 use std::io::Write;
709 #[cfg(test)]
710 self.stdin_write_active.store(true, Ordering::Release);
711 let write_result = stdin.write_all(data);
712 #[cfg(test)]
713 self.stdin_write_active.store(false, Ordering::Release);
714 write_result.map_err(ProcessError::Io)?;
715 stdin.flush().map_err(ProcessError::Io)?;
716 drop(guard.take());
717 Ok(())
718 }
719
720 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
725 if self.child.lock().expect("child mutex poisoned").is_none() {
726 return Err(ProcessError::NotRunning);
727 }
728 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
729 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
730 use std::io::Write;
731 #[cfg(test)]
732 self.stdin_write_active.store(true, Ordering::Release);
733 let write_result = stdin.write_all(data);
734 #[cfg(test)]
735 self.stdin_write_active.store(false, Ordering::Release);
736 write_result.map_err(ProcessError::Io)?;
737 stdin.flush().map_err(ProcessError::Io)?;
738 Ok(())
739 }
740
741 pub fn close_stdin(&self) -> Result<(), ProcessError> {
744 if self.child.lock().expect("child mutex poisoned").is_none() {
745 return Err(ProcessError::NotRunning);
746 }
747 drop(self.stdin.lock().expect("stdin mutex poisoned").take());
748 Ok(())
749 }
750
751 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
755 if let Some(code) = self.returncode() {
757 return Ok(Some(code));
758 }
759 let mut guard = self.child.lock().expect("child mutex poisoned");
760 let Some(child_state) = guard.as_mut() else {
761 return Ok(self.returncode());
762 };
763 let pid = child_state.child.id();
764 let child = &mut child_state.child;
765 let status = child.try_wait().map_err(ProcessError::Io)?;
766 if let Some(status) = status {
767 let code = exit_code(status);
768 self.set_returncode(code);
769 self.shared.emit_exited(pid, code);
770 return Ok(Some(code));
771 }
772 Ok(None)
773 }
774
775 #[inline(never)]
777 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
782 public_symbols::rp_native_process_wait_public(self, timeout)
783 }
784
785 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
786 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
787 if self.child.lock().expect("child mutex poisoned").is_none() {
788 return self.returncode().ok_or(ProcessError::NotRunning);
789 }
790 if let Some(code) = self.returncode() {
792 self.finish_capture_drain();
793 return Ok(code);
794 }
795 let start = Instant::now();
796 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
797 loop {
798 let rc = self.shared.returncode.load(Ordering::Acquire);
800 if rc != RETURNCODE_NOT_SET {
801 drop(guard);
802 let code = rc as i32;
803 self.finish_capture_drain();
804 return Ok(code);
805 }
806 if let Some(limit) = timeout {
807 let elapsed = start.elapsed();
808 if elapsed >= limit {
809 return Err(ProcessError::Timeout);
810 }
811 let remaining = limit - elapsed;
812 let wait_time = remaining.min(Duration::from_millis(50));
814 guard = self
815 .shared
816 .condvar
817 .wait_timeout(guard, wait_time)
818 .expect("queue mutex poisoned")
819 .0;
820 } else {
821 guard = self
823 .shared
824 .condvar
825 .wait_timeout(guard, Duration::from_millis(50))
826 .expect("queue mutex poisoned")
827 .0;
828 }
829 }
830 }
831
832 #[inline(never)]
834 pub fn kill(&self) -> Result<(), ProcessError> {
836 public_symbols::rp_native_process_kill_public(self)
837 }
838
839 fn kill_impl(&self) -> Result<(), ProcessError> {
840 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
841 #[cfg(windows)]
842 {
843 let mut guard = self.child.lock().expect("child mutex poisoned");
844 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
845 let pid = child.id();
846 child.kill().map_err(ProcessError::Io)?;
847 let status = child.wait().map_err(ProcessError::Io)?;
848 let code = exit_code(status);
849 self.set_returncode(code);
850 self.shared.emit_exited(pid, code);
853 }
854 #[cfg(unix)]
855 {
856 let deadline = kill_drain_deadline();
857 let (pid, already_reaped) = with_child_lock_for_signal(&self.child, |state| {
858 let child = &mut state.as_mut().ok_or(ProcessError::NotRunning)?.child;
859 let pid = child.id();
860 match child_signal_disposition(child.try_wait()).map_err(ProcessError::Io)? {
861 ChildSignalDisposition::AlreadyExited(status) => Ok((pid, Some(status))),
862 ChildSignalDisposition::Signal => {
863 let group_signaled = self.config.create_process_group
864 && unix_signal_process_group(pid as i32, UnixSignal::Kill).is_ok();
865 if !group_signaled {
866 child.kill().map_err(ProcessError::Io)?;
867 }
868 Ok((pid, None))
869 }
870 }
871 })?;
872
873 self.cancel_capture_io();
877 let reaped = already_reaped.or_else(|| {
878 let reap_result =
879 poll_mutex_until(&self.child, deadline, Duration::from_millis(10), |state| {
880 match state.as_mut() {
881 Some(child) => child.child.try_wait(),
882 None => Ok(None),
883 }
884 });
885 completed_reap_after_signal(reap_result)
886 });
887 if let Some(status) = reaped {
888 let code = exit_code(status);
889 self.set_returncode(code);
890 self.shared.emit_exited(pid, code);
891 }
892 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
893 self, deadline,
894 );
895 Ok(())
896 }
897 #[cfg(windows)]
898 {
899 #[cfg(any(windows, unix))]
907 self.cancel_capture_io();
908 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
919 self,
920 kill_drain_deadline(),
921 );
922 Ok(())
923 }
924 }
925
926 pub fn terminate(&self) -> Result<(), ProcessError> {
930 self.kill()
931 }
932
933 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
948 #[cfg(unix)]
949 {
950 if !self.config.create_process_group {
951 return Ok(());
952 }
953 let pid = match self.pid() {
954 Some(p) => p as i32,
955 None => return Err(ProcessError::NotRunning),
956 };
957 let result = unsafe { libc::kill(-pid, libc::SIGTERM) };
958 if result != 0 {
959 let err = std::io::Error::last_os_error();
960 if err.raw_os_error() != Some(libc::ESRCH) {
961 return Err(ProcessError::Io(err));
962 }
963 }
964 Ok(())
965 }
966 #[cfg(windows)]
967 {
968 if !self.config.create_process_group {
969 return Ok(());
974 }
975 let pid = match self.pid() {
976 Some(p) => p,
977 None => return Err(ProcessError::NotRunning),
978 };
979 let ok = unsafe {
983 winapi::um::wincon::GenerateConsoleCtrlEvent(
984 winapi::um::wincon::CTRL_BREAK_EVENT,
985 pid,
986 )
987 };
988 if ok == 0 {
989 let err = std::io::Error::last_os_error();
990 if err.raw_os_error() != Some(6) {
996 return Err(ProcessError::Io(err));
997 }
998 }
999 Ok(())
1000 }
1001 }
1002
1003 #[inline(never)]
1005 pub fn close(&self) -> Result<(), ProcessError> {
1007 public_symbols::rp_native_process_close_public(self)
1008 }
1009
1010 fn close_impl(&self) -> Result<(), ProcessError> {
1011 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
1012 if self.child.lock().expect("child mutex poisoned").is_none() {
1013 return Ok(());
1014 }
1015 if self.poll()?.is_none() {
1016 self.kill()?;
1017 } else {
1018 self.finish_capture_drain();
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 #[cfg(windows)]
1338 {
1339 use std::os::windows::process::CommandExt;
1340
1341 let flags = windows_creation_flags(
1349 self.config.creationflags,
1350 self.config.create_process_group,
1351 self.config.nice,
1352 crate::windows::parent_has_console(),
1353 );
1354 if flags != 0 {
1355 command.creation_flags(flags);
1356 }
1357 }
1358 #[cfg(unix)]
1359 {
1360 let create_process_group = self.config.create_process_group;
1361 let nice = self.config.nice;
1362
1363 if create_process_group || nice.is_some() {
1364 use std::os::unix::process::CommandExt;
1365
1366 unsafe {
1367 command.pre_exec(move || {
1368 if create_process_group && libc::setpgid(0, 0) == -1 {
1369 return Err(std::io::Error::last_os_error());
1370 }
1371 if let Some(nice) = nice {
1372 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
1373 if result == -1 {
1374 return Err(std::io::Error::last_os_error());
1375 }
1376 }
1377 Ok(())
1378 });
1379 }
1380 }
1381 }
1382 command
1383 }
1384
1385 fn spawn_reader<R>(
1386 &self,
1387 pipe: R,
1388 source_stream: StreamKind,
1389 visible_stream: StreamKind,
1390 on_pipe_done: Box<dyn FnOnce() + Send>,
1391 ) where
1392 R: Read + Send + 'static,
1393 {
1394 let shared = Arc::clone(&self.shared);
1395 shared.active_capture_readers.fetch_add(1, Ordering::AcqRel);
1396 thread::spawn(move || {
1397 let mut reader = pipe;
1398 let mut chunk = vec![0_u8; 65536];
1399 let mut pending = Vec::new();
1400
1401 loop {
1402 match reader.read(&mut chunk) {
1403 Ok(0) => break,
1404 Ok(n) => {
1405 if append_raw(&shared, visible_stream, &chunk[..n]) {
1406 let lines = feed_chunk(&mut pending, &chunk[..n]);
1407 emit_lines(&shared, visible_stream, lines);
1408 } else {
1409 pending.clear();
1410 }
1411 }
1412 Err(_) => break,
1413 }
1414 }
1415
1416 if !pending.is_empty() && !shared.capture_overflowed.load(Ordering::Acquire) {
1417 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1418 }
1419
1420 on_pipe_done();
1425 drop(reader);
1426
1427 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1428 match source_stream {
1429 StreamKind::Stdout => guard.stdout_closed = true,
1430 StreamKind::Stderr => guard.stderr_closed = true,
1431 }
1432 shared.active_capture_readers.fetch_sub(1, Ordering::AcqRel);
1433 shared.condvar.notify_all();
1434 });
1435 }
1436
1437 #[cfg(unix)]
1438 fn prepare_unix_capture_reader<R: Read + AsRawFd>(
1439 reader: R,
1440 ) -> std::io::Result<(UnixCancelableReader<R>, UnixStream)> {
1441 set_nonblocking(reader.as_raw_fd())?;
1442 let (wake_reader, wake_writer) = UnixStream::pair()?;
1443 wake_writer.set_nonblocking(true)?;
1444 Ok((
1445 UnixCancelableReader {
1446 reader,
1447 wake_reader,
1448 },
1449 wake_writer,
1450 ))
1451 }
1452
1453 #[cfg(windows)]
1454 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1455 let handles = Arc::clone(&self.capture_pipe_handles);
1456 Box::new(move || {
1457 let mut guard = handles.lock().expect("capture pipe handles mutex poisoned");
1458 match stream {
1459 StreamKind::Stdout => guard.stdout = None,
1460 StreamKind::Stderr => guard.stderr = None,
1461 }
1462 })
1463 }
1464
1465 #[cfg(unix)]
1466 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1467 let wakers = Arc::clone(&self.capture_wakers);
1468 Box::new(move || {
1469 let mut guard = wakers.lock().expect("capture wakers mutex poisoned");
1470 match stream {
1471 StreamKind::Stdout => guard.stdout = None,
1472 StreamKind::Stderr => guard.stderr = None,
1473 }
1474 })
1475 }
1476
1477 #[cfg(not(any(windows, unix)))]
1478 fn pipe_done_callback(&self, _stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1479 Box::new(|| {})
1480 }
1481
1482 #[cfg(windows)]
1486 fn cancel_capture_io(&self) {
1487 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1488 cancel_capture_pipe_io(&self.capture_pipe_handles);
1489 }
1490
1491 #[cfg(unix)]
1492 fn cancel_capture_io(&self) {
1493 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1494 cancel_capture_pipe_io(&self.capture_wakers);
1495 }
1496
1497 fn set_returncode(&self, code: i32) {
1498 self.shared.returncode.store(code as i64, Ordering::Release);
1499 self.shared.condvar.notify_all();
1500 }
1501
1502 fn finish_capture_drain(&self) {
1512 self.finish_capture_drain_with_deadline(kill_drain_deadline());
1513 }
1514
1515 fn finish_capture_drain_with_deadline(&self, deadline: Instant) {
1516 let drained = self.wait_for_capture_completion_with_deadline_impl(deadline);
1517 #[cfg(any(windows, unix))]
1518 if !drained {
1519 self.cancel_capture_io();
1520 }
1521 #[cfg(not(any(windows, unix)))]
1522 let _ = drained;
1523 }
1524
1525 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1528 crate::rp_rust_debug_scope!(
1529 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1530 );
1531 if !self.config.capture {
1532 return true;
1533 }
1534 finalize_capture_completion(&self.shared, deadline)
1535 }
1536
1537 fn wait_for_capture_readers_with_deadline(&self, deadline: Instant) -> bool {
1538 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1539 while self.shared.active_capture_readers.load(Ordering::Acquire) != 0 {
1540 let now = Instant::now();
1541 if now >= deadline {
1542 return false;
1543 }
1544 let (next_guard, result) = self
1545 .shared
1546 .condvar
1547 .wait_timeout(guard, deadline - now)
1548 .expect("queue mutex poisoned");
1549 guard = next_guard;
1550 if result.timed_out() && self.shared.active_capture_readers.load(Ordering::Acquire) != 0
1551 {
1552 return false;
1553 }
1554 }
1555 true
1556 }
1557}
1558
1559#[cfg(windows)]
1565fn cancel_capture_pipe_io(handles: &Mutex<CapturePipeHandles>) {
1566 use winapi::shared::ntdef::HANDLE;
1567 use winapi::um::ioapiset::CancelIoEx;
1568 let guard = handles.lock().expect("capture pipe handles mutex poisoned");
1569 if let Some(h) = guard.stdout {
1570 unsafe {
1576 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1577 }
1578 }
1579 if let Some(h) = guard.stderr {
1580 unsafe {
1581 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1582 }
1583 }
1584}
1585
1586#[cfg(unix)]
1587fn cancel_capture_pipe_io(wakers: &Mutex<UnixCaptureWakers>) {
1588 use std::os::fd::AsRawFd;
1589
1590 let guard = wakers.lock().expect("capture wakers mutex poisoned");
1591 let byte = [1_u8; 1];
1592 for writer in [&guard.stdout, &guard.stderr].into_iter().flatten() {
1593 let _ = unsafe { libc::write(writer.as_raw_fd(), byte.as_ptr().cast(), byte.len()) };
1597 }
1598}
1599
1600fn finalize_capture_completion(shared: &SharedState, deadline: Instant) -> bool {
1607 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1608 while !(guard.stdout_closed && guard.stderr_closed) {
1609 let now = Instant::now();
1610 if now >= deadline {
1611 guard.stdout_closed = true;
1612 guard.stderr_closed = true;
1613 shared.condvar.notify_all();
1614 return false;
1615 }
1616 let (next_guard, result) = shared
1617 .condvar
1618 .wait_timeout(guard, deadline - now)
1619 .expect("queue mutex poisoned");
1620 guard = next_guard;
1621 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1622 guard.stdout_closed = true;
1623 guard.stderr_closed = true;
1624 shared.condvar.notify_all();
1625 return false;
1626 }
1627 }
1628 true
1629}
1630
1631fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1632 if lines.is_empty() || shared.capture_overflowed.load(Ordering::Acquire) {
1633 return;
1634 }
1635 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1636 if shared.capture_overflowed.load(Ordering::Acquire) {
1637 return;
1638 }
1639 for line in lines {
1640 let line_len = line.len();
1641 match stream {
1642 StreamKind::Stdout => {
1643 guard.stdout_history_bytes += line_len;
1644 guard.stdout_history.push_back(line.clone());
1645 guard.stdout_queue.push_back(line.clone());
1646 }
1647 StreamKind::Stderr => {
1648 guard.stderr_history_bytes += line_len;
1649 guard.stderr_history.push_back(line.clone());
1650 guard.stderr_queue.push_back(line.clone());
1651 }
1652 }
1653 let event = StreamEvent { stream, line };
1654 guard.combined_history_bytes += line_len;
1655 guard.combined_history.push_back(event.clone());
1656 guard.combined_queue.push_back(event);
1657 }
1658 shared.condvar.notify_all();
1659}
1660
1661fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) -> bool {
1662 if chunk.is_empty() {
1663 return true;
1664 }
1665 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1666 let accepted = match shared.capture_limit {
1667 Some(limit) => {
1668 let retained = guard
1669 .stdout_raw
1670 .len()
1671 .saturating_add(guard.stderr_raw.len());
1672 chunk.len().min(limit.saturating_sub(retained))
1673 }
1674 None => chunk.len(),
1675 };
1676 match stream {
1677 StreamKind::Stdout => guard.stdout_raw.extend_from_slice(&chunk[..accepted]),
1678 StreamKind::Stderr => guard.stderr_raw.extend_from_slice(&chunk[..accepted]),
1679 }
1680 if accepted != chunk.len() {
1681 shared.capture_overflowed.store(true, Ordering::Release);
1682 false
1683 } else {
1684 true
1685 }
1686}
1687
1688pub fn run_command(
1694 mut config: ProcessConfig,
1695 timeout: Option<Duration>,
1696) -> Result<RunOutput, ProcessError> {
1697 config.capture = true;
1698 let process = NativeProcess::new(config);
1699 process.start()?;
1700
1701 let exit_code = match process.wait(timeout) {
1702 Ok(code) => code,
1703 Err(ProcessError::Timeout) => {
1704 match process.kill() {
1705 Ok(()) | Err(ProcessError::NotRunning) => {}
1706 Err(error) => return Err(error),
1707 }
1708 return Err(ProcessError::Timeout);
1709 }
1710 Err(error) => return Err(error),
1711 };
1712
1713 Ok(RunOutput {
1714 stdout: process.captured_stdout_raw(),
1715 stderr: process.captured_stderr_raw(),
1716 exit_code,
1717 })
1718}
1719
1720struct BoundedRunCleanup<'a> {
1721 process: &'a NativeProcess,
1722 armed: bool,
1723}
1724
1725impl BoundedRunCleanup<'_> {
1726 fn disarm(&mut self) {
1727 self.armed = false;
1728 }
1729}
1730
1731impl Drop for BoundedRunCleanup<'_> {
1732 fn drop(&mut self) {
1733 if !self.armed {
1734 return;
1735 }
1736
1737 #[cfg(any(windows, unix))]
1741 self.process.cancel_capture_io();
1742 let _ = self.process.poll();
1743 if self.process.returncode().is_none() {
1744 let _ = self.process.kill();
1745 } else {
1746 self.process.finish_capture_drain();
1747 }
1748 let _ = self
1749 .process
1750 .wait_for_capture_readers_with_deadline(kill_drain_deadline());
1751 }
1752}
1753
1754fn run_native_process_bounded(
1755 process: NativeProcess,
1756 timeout: Option<Duration>,
1757 output_limit: usize,
1758) -> Result<RunOutput, ProcessError> {
1759 process.start()?;
1760 let mut cleanup = BoundedRunCleanup {
1761 process: &process,
1762 armed: true,
1763 };
1764 let started = Instant::now();
1765
1766 let exit_code = loop {
1767 if process.shared.capture_overflowed.load(Ordering::Acquire) {
1768 return Err(ProcessError::OutputLimitExceeded {
1769 limit: output_limit,
1770 });
1771 }
1772 if let Some(code) = process.poll()? {
1773 process.finish_capture_drain();
1774 break code;
1775 }
1776 if timeout.is_some_and(|limit| started.elapsed() >= limit) {
1777 return Err(ProcessError::Timeout);
1778 }
1779 thread::sleep(Duration::from_millis(5));
1780 };
1781
1782 if !process.wait_for_capture_readers_with_deadline(kill_drain_deadline()) {
1783 return Err(ProcessError::Io(std::io::Error::new(
1784 std::io::ErrorKind::TimedOut,
1785 "capture readers did not stop after process exit",
1786 )));
1787 }
1788 if process.shared.capture_overflowed.load(Ordering::Acquire) {
1789 return Err(ProcessError::OutputLimitExceeded {
1790 limit: output_limit,
1791 });
1792 }
1793
1794 let output = RunOutput {
1795 stdout: process.captured_stdout_raw(),
1796 stderr: process.captured_stderr_raw(),
1797 exit_code,
1798 };
1799 cleanup.disarm();
1800 Ok(output)
1801}
1802
1803pub fn run_command_bounded(
1812 mut config: ProcessConfig,
1813 timeout: Option<Duration>,
1814 output_limit: usize,
1815) -> Result<RunOutput, ProcessError> {
1816 config.capture = true;
1817 config.create_process_group = true;
1818 let process = NativeProcess::new_with_capture_limit(config, output_limit);
1819 run_native_process_bounded(process, timeout, output_limit)
1820}
1821
1822pub fn run_std_command_bounded(
1829 command: Command,
1830 timeout: Option<Duration>,
1831 output_limit: usize,
1832) -> Result<RunOutput, ProcessError> {
1833 let config = ProcessConfig {
1834 command: CommandSpec::Argv(vec!["running-process-command-override".to_string()]),
1838 cwd: None,
1839 env: None,
1840 capture: true,
1841 stderr_mode: StderrMode::Pipe,
1842 creationflags: None,
1843 create_process_group: true,
1844 stdin_mode: StdinMode::Null,
1845 nice: None,
1846 };
1847 let process = NativeProcess::new_with_command_capture_limit(command, config, output_limit);
1848 run_native_process_bounded(process, timeout, output_limit)
1849}
1850
1851pub(crate) fn shell_command(command: &str) -> Command {
1852 #[cfg(windows)]
1853 {
1854 use std::os::windows::process::CommandExt;
1855
1856 let mut cmd = Command::new("cmd");
1857 cmd.raw_arg("/D /S /C \"");
1858 cmd.raw_arg(command);
1859 cmd.raw_arg("\"");
1860 cmd
1861 }
1862 #[cfg(not(windows))]
1863 {
1864 let mut cmd = Command::new("sh");
1865 cmd.arg("-lc").arg(command);
1866 cmd
1867 }
1868}
1869
1870#[cfg(test)]
1871mod tests;