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