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