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 observer;
32#[cfg(feature = "originator-scan")]
33pub mod originator;
34#[cfg(feature = "client")]
39pub mod proto {
41 #[allow(missing_docs)]
43 pub mod daemon {
44 include!(concat!(env!("OUT_DIR"), "/running_process.daemon.v1.rs"));
45 }
46}
47
48#[cfg(feature = "client")]
49pub mod client;
50
51#[cfg(feature = "client")]
56pub mod broker;
57
58#[cfg(feature = "client")]
64pub mod maintenance;
65
66#[cfg(feature = "client")]
67pub mod cleanup;
68
69#[cfg(feature = "client")]
73pub mod boot_autostart;
74
75#[cfg(feature = "client")]
80pub mod runpm_config;
81
82#[cfg(feature = "test-support")]
87pub mod test_support;
88
89#[cfg(feature = "telemetry")]
92#[path = "daemon/telemetry.rs"]
93pub mod telemetry;
94
95#[cfg(feature = "daemon")]
98pub mod daemon;
100pub mod process_tree;
101#[cfg(feature = "pty")]
102pub mod pty;
104mod public_symbols;
105mod rust_debug;
106pub mod spawn;
107pub mod systemd_killmode;
108pub mod terminal_graphics;
109mod types;
110#[cfg(unix)]
111mod unix;
112#[cfg(windows)]
113mod windows;
114
115pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
116pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
117pub use observer::{
118 CapabilitySupport, CategoryCapability, EventCategory, ObserverCapabilities, ObserverConfig,
119 ObserverEvent, ObserverEventKind, ObserverSubscriber,
120};
121#[cfg(feature = "originator-scan")]
122pub use originator::{find_processes_by_originator, OriginatorProcessInfo};
123pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
124pub use spawn::{
125 spawn, spawn_daemon, spawn_daemon_with_clear_env, spawn_daemon_with_env_policy,
126 spawn_with_env_policy, DaemonChild, EnvironmentPolicy, SpawnStdio, SpawnedChild, StdioSource,
127};
128pub use terminal_graphics::{
129 current_terminal_capabilities, current_terminal_capabilities_with_timeout,
130 detect_terminal_capabilities, CapabilityStatus, EvidenceStrength, GraphicsCapability,
131 GraphicsProtocol, TerminalCapabilities, TerminalCapabilityInput, TerminalGraphicsCapabilities,
132 TerminalProbeEvidence,
133};
134pub use types::{
135 CommandSpec, ProcessConfig, ProcessError, ReadStatus, RunOutput, StderrMode, StdinMode,
136 StreamEvent, StreamKind,
137};
138
139pub(crate) use helpers::{exit_code, feed_chunk, kill_drain_deadline, log_spawned_child_pid};
140#[cfg(unix)]
141pub(crate) use helpers::{
142 child_signal_disposition, child_try_wait_error_is_retryable, completed_reap_after_signal,
143 poll_mutex_until, with_child_lock_for_signal, ChildSignalDisposition,
144};
145#[cfg(unix)]
146pub use unix::{unix_set_priority, unix_signal_process, unix_signal_process_group, UnixSignal};
147#[cfg(windows)]
148pub(crate) use windows::{
149 assign_child_to_windows_kill_on_close_job_impl, windows_creation_flags, CapturePipeHandles,
150 WindowsJobHandle,
151};
152
153#[macro_export]
154macro_rules! rp_rust_debug_scope {
156 ($label:expr) => {
157 let _running_process_rust_debug_scope =
158 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
159 };
160}
161
162#[derive(Default)]
163struct QueueState {
164 stdout_queue: VecDeque<Vec<u8>>,
165 stderr_queue: VecDeque<Vec<u8>>,
166 combined_queue: VecDeque<StreamEvent>,
167 stdout_history: VecDeque<Vec<u8>>,
168 stderr_history: VecDeque<Vec<u8>>,
169 combined_history: VecDeque<StreamEvent>,
170 stdout_raw: Vec<u8>,
171 stderr_raw: Vec<u8>,
172 stdout_history_bytes: usize,
173 stderr_history_bytes: usize,
174 combined_history_bytes: usize,
175 stdout_closed: bool,
176 stderr_closed: bool,
177}
178
179const RETURNCODE_NOT_SET: i64 = i64::MIN;
181
182struct SharedState {
183 queues: Mutex<QueueState>,
184 condvar: Condvar,
185 returncode: AtomicI64,
188 observer: Option<ObserverEmitter>,
193 observer_exit_emitted: AtomicBool,
196}
197
198struct ChildState {
199 child: Child,
200 #[cfg(windows)]
201 _job: WindowsJobHandle,
202}
203
204#[cfg(unix)]
205#[derive(Default)]
206struct UnixCaptureWakers {
207 stdout: Option<UnixStream>,
208 stderr: Option<UnixStream>,
209}
210
211#[cfg(unix)]
212struct UnixCancelableReader<R> {
213 reader: R,
214 wake_reader: UnixStream,
215}
216
217#[cfg(any(test, unix))]
218#[derive(Debug, Eq, PartialEq)]
219enum CapturePollAction {
220 Wait,
221 Read,
222 Cancel,
223}
224
225#[cfg(any(test, unix))]
226fn capture_poll_action(capture_revents: i16, wake_revents: i16) -> CapturePollAction {
227 if wake_revents != 0 {
228 CapturePollAction::Cancel
229 } else if capture_revents != 0 {
230 CapturePollAction::Read
231 } else {
232 CapturePollAction::Wait
233 }
234}
235
236#[cfg(unix)]
237impl<R: Read + AsRawFd> Read for UnixCancelableReader<R> {
238 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
239 if buf.is_empty() {
240 return Ok(0);
241 }
242 loop {
243 let mut poll_fds = [
244 libc::pollfd {
245 fd: self.reader.as_raw_fd(),
246 events: libc::POLLIN | libc::POLLHUP | libc::POLLERR,
247 revents: 0,
248 },
249 libc::pollfd {
250 fd: self.wake_reader.as_raw_fd(),
251 events: libc::POLLIN | libc::POLLHUP | libc::POLLERR,
252 revents: 0,
253 },
254 ];
255 let polled = unsafe { libc::poll(poll_fds.as_mut_ptr(), poll_fds.len() as _, -1) };
256 if polled < 0 {
257 let error = std::io::Error::last_os_error();
258 if error.kind() == std::io::ErrorKind::Interrupted {
259 continue;
260 }
261 return Err(error);
262 }
263 match capture_poll_action(poll_fds[0].revents, poll_fds[1].revents) {
264 CapturePollAction::Cancel => {
265 return Err(std::io::Error::new(
266 std::io::ErrorKind::Interrupted,
267 "capture reader cancelled",
268 ));
269 }
270 CapturePollAction::Read => match self.reader.read(buf) {
271 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => continue,
272 result => return result,
273 },
274 CapturePollAction::Wait => {}
275 }
276 }
277 }
278}
279
280#[cfg(unix)]
281fn set_nonblocking(fd: RawFd) -> std::io::Result<()> {
282 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
283 if flags < 0 {
284 return Err(std::io::Error::last_os_error());
285 }
286 if unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0 {
287 return Err(std::io::Error::last_os_error());
288 }
289 Ok(())
290}
291
292#[cfg(unix)]
293fn cleanup_child_after_start_error(mut child: Child) {
294 let _ = child.kill();
295 thread::spawn(move || {
298 let _ = child.wait();
299 });
300}
301
302impl SharedState {
303 fn new(capture: bool) -> Self {
304 Self::with_observer(capture, None)
305 }
306
307 fn with_observer(capture: bool, observer: Option<ObserverEmitter>) -> Self {
308 let queues = QueueState {
309 stdout_closed: !capture,
310 stderr_closed: !capture,
311 ..QueueState::default()
312 };
313 Self {
314 queues: Mutex::new(queues),
315 condvar: Condvar::new(),
316 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
317 observer,
318 observer_exit_emitted: AtomicBool::new(false),
319 }
320 }
321
322 fn emit_exited(&self, pid: u32, exit_code: i32) {
325 let Some(emitter) = self.observer.as_ref() else {
326 return;
327 };
328 if self
329 .observer_exit_emitted
330 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
331 .is_ok()
332 {
333 emitter.emit_exited(pid, exit_code);
334 }
335 }
336}
337
338pub struct NativeProcess {
345 config: ProcessConfig,
346 child: Arc<Mutex<Option<ChildState>>>,
347 stdin: Mutex<Option<ChildStdin>>,
348 shared: Arc<SharedState>,
349 #[cfg(test)]
350 stdin_write_active: AtomicBool,
351 #[cfg(windows)]
352 capture_pipe_handles: Arc<Mutex<CapturePipeHandles>>,
353 #[cfg(unix)]
354 capture_wakers: Arc<Mutex<UnixCaptureWakers>>,
355}
356
357impl NativeProcess {
358 pub fn new(config: ProcessConfig) -> Self {
364 Self::new_with_observer(config, None)
365 }
366
367 pub fn with_observer(
380 config: ProcessConfig,
381 observer: crate::observer::ObserverConfig,
382 ) -> (Self, ObserverSubscriber) {
383 let (emitter, subscriber) = ObserverEmitter::new(observer);
384 let process = Self::new_with_observer(config, Some(emitter));
385 (process, subscriber)
386 }
387
388 fn new_with_observer(config: ProcessConfig, observer: Option<ObserverEmitter>) -> Self {
389 let shared = match observer {
390 None => SharedState::new(config.capture),
392 Some(emitter) => SharedState::with_observer(config.capture, Some(emitter)),
393 };
394 Self {
395 shared: Arc::new(shared),
396 child: Arc::new(Mutex::new(None)),
397 stdin: Mutex::new(None),
398 #[cfg(test)]
399 stdin_write_active: AtomicBool::new(false),
400 config,
401 #[cfg(windows)]
402 capture_pipe_handles: Arc::new(Mutex::new(CapturePipeHandles::default())),
403 #[cfg(unix)]
404 capture_wakers: Arc::new(Mutex::new(UnixCaptureWakers::default())),
405 }
406 }
407
408 #[inline(never)]
410 pub fn start(&self) -> Result<(), ProcessError> {
415 public_symbols::rp_native_process_start_public(self)
416 }
417
418 fn start_impl(&self) -> Result<(), ProcessError> {
419 crate::rp_rust_debug_scope!("running_process::NativeProcess::start");
420 let mut guard = self.child.lock().expect("child mutex poisoned");
421 if guard.is_some() {
422 return Err(ProcessError::AlreadyStarted);
423 }
424
425 let mut command = self.build_command();
426 match self.config.stdin_mode {
427 StdinMode::Inherit => {}
428 StdinMode::Piped => {
429 command.stdin(Stdio::piped());
430 }
431 StdinMode::Null => {
432 command.stdin(Stdio::null());
433 }
434 }
435 if self.config.capture {
436 command.stdout(Stdio::piped());
437 command.stderr(Stdio::piped());
438 }
439
440 let mut child = command.spawn().map_err(ProcessError::Spawn)?;
441 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
442 if let Some(emitter) = self.shared.observer.as_ref() {
445 emitter.emit_started(child.id());
446 }
447 #[cfg(windows)]
452 let job = {
453 let descendant_sink = self
454 .shared
455 .observer
456 .as_ref()
457 .and_then(|e| e.descendant_sink());
458 let direct_pid = child.id();
459 public_symbols::rp_assign_child_to_windows_kill_on_close_job_with_observer_public(
460 &child,
461 descendant_sink,
462 direct_pid,
463 )
464 .map_err(ProcessError::Spawn)?
465 };
466 #[cfg(target_os = "linux")]
470 {
471 if let Some(emitter) = self.shared.observer.as_ref() {
472 if let Some((sink, stop)) = emitter.descendant_pump() {
473 crate::observer::descendants_linux::enable_subreaper();
474 crate::observer::descendants_linux::spawn_pump(child.id(), sink, stop);
475 }
476 }
477 }
478 #[cfg(target_os = "macos")]
482 {
483 if let Some(emitter) = self.shared.observer.as_ref() {
484 if let Some((sink, stop)) = emitter.descendant_pump() {
485 crate::observer::descendants_macos::spawn_pump(child.id(), sink, stop);
486 }
487 }
488 }
489 if self.config.capture {
490 let stdout = child.stdout.take().expect("stdout pipe missing");
491 let stderr = child.stderr.take().expect("stderr pipe missing");
492 #[cfg(windows)]
493 {
494 use std::os::windows::io::AsRawHandle;
495 let mut handles = self
496 .capture_pipe_handles
497 .lock()
498 .expect("capture pipe handles mutex poisoned");
499 handles.stdout = Some(stdout.as_raw_handle() as usize);
500 handles.stderr = Some(stderr.as_raw_handle() as usize);
501 }
502 #[cfg(unix)]
503 let ((stdout, stdout_waker), (stderr, stderr_waker)) =
504 match Self::prepare_unix_capture_reader(stdout).and_then(|stdout| {
505 Self::prepare_unix_capture_reader(stderr).map(|stderr| (stdout, stderr))
506 }) {
507 Ok(readers) => readers,
508 Err(error) => {
509 cleanup_child_after_start_error(child);
510 return Err(ProcessError::Spawn(error));
511 }
512 };
513 #[cfg(unix)]
514 {
515 let mut wakers = self
516 .capture_wakers
517 .lock()
518 .expect("capture wakers mutex poisoned");
519 wakers.stdout = Some(stdout_waker);
520 wakers.stderr = Some(stderr_waker);
521 }
522 self.spawn_reader(
523 stdout,
524 StreamKind::Stdout,
525 StreamKind::Stdout,
526 self.pipe_done_callback(StreamKind::Stdout),
527 );
528 self.spawn_reader(
529 stderr,
530 StreamKind::Stderr,
531 match self.config.stderr_mode {
532 StderrMode::Stdout => StreamKind::Stdout,
533 StderrMode::Pipe => StreamKind::Stderr,
534 },
535 self.pipe_done_callback(StreamKind::Stderr),
536 );
537 }
538 *self.stdin.lock().expect("stdin mutex poisoned") = child.stdin.take();
539 *guard = Some(ChildState {
540 child,
541 #[cfg(windows)]
542 _job: job,
543 });
544 drop(guard);
545 self.spawn_exit_waiter();
546 Ok(())
547 }
548
549 fn spawn_exit_waiter(&self) {
552 let child = Arc::clone(&self.child);
553 let shared = Arc::clone(&self.shared);
554 let capture = self.config.capture;
555 #[cfg(windows)]
556 let capture_pipe_handles = Arc::clone(&self.capture_pipe_handles);
557 #[cfg(unix)]
558 let capture_wakers = Arc::clone(&self.capture_wakers);
559 thread::spawn(move || {
560 loop {
561 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
562 return;
563 }
564 let exited = {
565 let mut guard = child.lock().expect("child mutex poisoned");
566 if let Some(child_state) = guard.as_mut() {
567 let pid = child_state.child.id();
568 match child_state.child.try_wait() {
569 Ok(Some(status)) => {
570 let code = exit_code(status);
571 shared.returncode.store(code as i64, Ordering::Release);
572 shared.emit_exited(pid, code);
576 shared.condvar.notify_all();
577 true
578 }
579 Ok(None) => false,
580 Err(_error) => {
581 #[cfg(unix)]
582 if child_try_wait_error_is_retryable(&_error) {
583 false
584 } else {
585 return;
586 }
587 #[cfg(windows)]
588 return;
589 }
590 }
591 } else {
592 return;
593 }
594 };
595 if exited {
596 if capture {
611 let drained = finalize_capture_completion(&shared, kill_drain_deadline());
612 #[cfg(windows)]
613 if !drained {
614 cancel_capture_pipe_io(&capture_pipe_handles);
615 }
616 #[cfg(unix)]
617 if !drained {
618 cancel_capture_pipe_io(&capture_wakers);
619 }
620 #[cfg(not(any(windows, unix)))]
621 let _ = drained;
622 }
623 return;
624 }
625 thread::sleep(Duration::from_millis(10));
631 }
632 });
633 }
634
635 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
637 if self.child.lock().expect("child mutex poisoned").is_none() {
638 return Err(ProcessError::NotRunning);
639 }
640 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
641 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
642 use std::io::Write;
643 #[cfg(test)]
644 self.stdin_write_active.store(true, Ordering::Release);
645 let write_result = stdin.write_all(data);
646 #[cfg(test)]
647 self.stdin_write_active.store(false, Ordering::Release);
648 write_result.map_err(ProcessError::Io)?;
649 stdin.flush().map_err(ProcessError::Io)?;
650 drop(guard.take());
651 Ok(())
652 }
653
654 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
659 if self.child.lock().expect("child mutex poisoned").is_none() {
660 return Err(ProcessError::NotRunning);
661 }
662 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
663 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
664 use std::io::Write;
665 #[cfg(test)]
666 self.stdin_write_active.store(true, Ordering::Release);
667 let write_result = stdin.write_all(data);
668 #[cfg(test)]
669 self.stdin_write_active.store(false, Ordering::Release);
670 write_result.map_err(ProcessError::Io)?;
671 stdin.flush().map_err(ProcessError::Io)?;
672 Ok(())
673 }
674
675 pub fn close_stdin(&self) -> Result<(), ProcessError> {
678 if self.child.lock().expect("child mutex poisoned").is_none() {
679 return Err(ProcessError::NotRunning);
680 }
681 drop(self.stdin.lock().expect("stdin mutex poisoned").take());
682 Ok(())
683 }
684
685 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
689 if let Some(code) = self.returncode() {
691 return Ok(Some(code));
692 }
693 let mut guard = self.child.lock().expect("child mutex poisoned");
694 let Some(child_state) = guard.as_mut() else {
695 return Ok(self.returncode());
696 };
697 let pid = child_state.child.id();
698 let child = &mut child_state.child;
699 let status = child.try_wait().map_err(ProcessError::Io)?;
700 if let Some(status) = status {
701 let code = exit_code(status);
702 self.set_returncode(code);
703 self.shared.emit_exited(pid, code);
704 return Ok(Some(code));
705 }
706 Ok(None)
707 }
708
709 #[inline(never)]
711 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
716 public_symbols::rp_native_process_wait_public(self, timeout)
717 }
718
719 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
720 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
721 if self.child.lock().expect("child mutex poisoned").is_none() {
722 return self.returncode().ok_or(ProcessError::NotRunning);
723 }
724 if let Some(code) = self.returncode() {
726 self.finish_capture_drain();
727 return Ok(code);
728 }
729 let start = Instant::now();
730 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
731 loop {
732 let rc = self.shared.returncode.load(Ordering::Acquire);
734 if rc != RETURNCODE_NOT_SET {
735 drop(guard);
736 let code = rc as i32;
737 self.finish_capture_drain();
738 return Ok(code);
739 }
740 if let Some(limit) = timeout {
741 let elapsed = start.elapsed();
742 if elapsed >= limit {
743 return Err(ProcessError::Timeout);
744 }
745 let remaining = limit - elapsed;
746 let wait_time = remaining.min(Duration::from_millis(50));
748 guard = self
749 .shared
750 .condvar
751 .wait_timeout(guard, wait_time)
752 .expect("queue mutex poisoned")
753 .0;
754 } else {
755 guard = self
757 .shared
758 .condvar
759 .wait_timeout(guard, Duration::from_millis(50))
760 .expect("queue mutex poisoned")
761 .0;
762 }
763 }
764 }
765
766 #[inline(never)]
768 pub fn kill(&self) -> Result<(), ProcessError> {
770 public_symbols::rp_native_process_kill_public(self)
771 }
772
773 fn kill_impl(&self) -> Result<(), ProcessError> {
774 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
775 #[cfg(windows)]
776 {
777 let mut guard = self.child.lock().expect("child mutex poisoned");
778 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
779 let pid = child.id();
780 child.kill().map_err(ProcessError::Io)?;
781 let status = child.wait().map_err(ProcessError::Io)?;
782 let code = exit_code(status);
783 self.set_returncode(code);
784 self.shared.emit_exited(pid, code);
787 }
788 #[cfg(unix)]
789 {
790 let deadline = kill_drain_deadline();
791 let (pid, already_reaped) = with_child_lock_for_signal(&self.child, |state| {
792 let child = &mut state.as_mut().ok_or(ProcessError::NotRunning)?.child;
793 let pid = child.id();
794 match child_signal_disposition(child.try_wait()).map_err(ProcessError::Io)? {
795 ChildSignalDisposition::AlreadyExited(status) => Ok((pid, Some(status))),
796 ChildSignalDisposition::Signal => {
797 let group_signaled = self.config.create_process_group
798 && unix_signal_process_group(pid as i32, UnixSignal::Kill).is_ok();
799 if !group_signaled {
800 child.kill().map_err(ProcessError::Io)?;
801 }
802 Ok((pid, None))
803 }
804 }
805 })?;
806
807 self.cancel_capture_io();
811 let reaped = already_reaped.or_else(|| {
812 let reap_result =
813 poll_mutex_until(&self.child, deadline, Duration::from_millis(10), |state| {
814 match state.as_mut() {
815 Some(child) => child.child.try_wait(),
816 None => Ok(None),
817 }
818 });
819 completed_reap_after_signal(reap_result)
820 });
821 if let Some(status) = reaped {
822 let code = exit_code(status);
823 self.set_returncode(code);
824 self.shared.emit_exited(pid, code);
825 }
826 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
827 self, deadline,
828 );
829 Ok(())
830 }
831 #[cfg(windows)]
832 {
833 #[cfg(any(windows, unix))]
841 self.cancel_capture_io();
842 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
853 self,
854 kill_drain_deadline(),
855 );
856 Ok(())
857 }
858 }
859
860 pub fn terminate(&self) -> Result<(), ProcessError> {
864 self.kill()
865 }
866
867 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
882 #[cfg(unix)]
883 {
884 if !self.config.create_process_group {
885 return Ok(());
886 }
887 let pid = match self.pid() {
888 Some(p) => p as i32,
889 None => return Err(ProcessError::NotRunning),
890 };
891 let result = unsafe { libc::kill(-pid, libc::SIGTERM) };
892 if result != 0 {
893 let err = std::io::Error::last_os_error();
894 if err.raw_os_error() != Some(libc::ESRCH) {
895 return Err(ProcessError::Io(err));
896 }
897 }
898 Ok(())
899 }
900 #[cfg(windows)]
901 {
902 if !self.config.create_process_group {
903 return Ok(());
908 }
909 let pid = match self.pid() {
910 Some(p) => p,
911 None => return Err(ProcessError::NotRunning),
912 };
913 let ok = unsafe {
917 winapi::um::wincon::GenerateConsoleCtrlEvent(
918 winapi::um::wincon::CTRL_BREAK_EVENT,
919 pid,
920 )
921 };
922 if ok == 0 {
923 let err = std::io::Error::last_os_error();
924 if err.raw_os_error() != Some(6) {
930 return Err(ProcessError::Io(err));
931 }
932 }
933 Ok(())
934 }
935 }
936
937 #[inline(never)]
939 pub fn close(&self) -> Result<(), ProcessError> {
941 public_symbols::rp_native_process_close_public(self)
942 }
943
944 fn close_impl(&self) -> Result<(), ProcessError> {
945 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
946 if self.child.lock().expect("child mutex poisoned").is_none() {
947 return Ok(());
948 }
949 if self.poll()?.is_none() {
950 self.kill()?;
951 } else {
952 self.finish_capture_drain();
953 }
954 Ok(())
955 }
956
957 pub fn pid(&self) -> Option<u32> {
959 self.child
960 .lock()
961 .expect("child mutex poisoned")
962 .as_ref()
963 .map(|state| state.child.id())
964 }
965
966 pub fn returncode(&self) -> Option<i32> {
968 let v = self.shared.returncode.load(Ordering::Acquire);
969 if v == RETURNCODE_NOT_SET {
970 None
971 } else {
972 Some(v as i32)
973 }
974 }
975
976 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
978 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
979 return false;
980 }
981 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
982 match stream {
983 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
984 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
985 }
986 }
987
988 pub fn has_pending_combined(&self) -> bool {
990 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
991 !guard.combined_queue.is_empty()
992 }
993
994 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
996 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
997 return Vec::new();
998 }
999 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1000 let queue = match stream {
1001 StreamKind::Stdout => &mut guard.stdout_queue,
1002 StreamKind::Stderr => &mut guard.stderr_queue,
1003 };
1004 queue.drain(..).collect()
1005 }
1006
1007 pub fn drain_combined(&self) -> Vec<StreamEvent> {
1009 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1010 guard.combined_queue.drain(..).collect()
1011 }
1012
1013 pub fn read_stream(
1018 &self,
1019 stream: StreamKind,
1020 timeout: Option<Duration>,
1021 ) -> ReadStatus<Vec<u8>> {
1022 let deadline = timeout.map(|limit| Instant::now() + limit);
1023 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1024
1025 loop {
1026 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1027 return ReadStatus::Eof;
1028 }
1029
1030 let queue = match stream {
1031 StreamKind::Stdout => &mut guard.stdout_queue,
1032 StreamKind::Stderr => &mut guard.stderr_queue,
1033 };
1034 if let Some(line) = queue.pop_front() {
1035 return ReadStatus::Line(line);
1036 }
1037
1038 let closed = match stream {
1039 StreamKind::Stdout => {
1040 if self.config.stderr_mode == StderrMode::Stdout {
1041 guard.stdout_closed && guard.stderr_closed
1042 } else {
1043 guard.stdout_closed
1044 }
1045 }
1046 StreamKind::Stderr => guard.stderr_closed,
1047 };
1048 if closed {
1049 return ReadStatus::Eof;
1050 }
1051
1052 match deadline {
1053 Some(deadline) => {
1054 let now = Instant::now();
1055 if now >= deadline {
1056 return ReadStatus::Timeout;
1057 }
1058 let wait = deadline.saturating_duration_since(now);
1059 let result = self
1060 .shared
1061 .condvar
1062 .wait_timeout(guard, wait)
1063 .expect("queue mutex poisoned");
1064 guard = result.0;
1065 if result.1.timed_out() {
1066 return ReadStatus::Timeout;
1067 }
1068 }
1069 None => {
1070 guard = self
1071 .shared
1072 .condvar
1073 .wait(guard)
1074 .expect("queue mutex poisoned");
1075 }
1076 }
1077 }
1078 }
1079
1080 #[inline(never)]
1082 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1084 public_symbols::rp_native_process_read_combined_public(self, timeout)
1085 }
1086
1087 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1088 crate::rp_rust_debug_scope!("running_process::NativeProcess::read_combined");
1089 let deadline = timeout.map(|limit| Instant::now() + limit);
1090 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1091
1092 loop {
1093 if let Some(event) = guard.combined_queue.pop_front() {
1094 return ReadStatus::Line(event);
1095 }
1096 if guard.stdout_closed && guard.stderr_closed {
1097 return ReadStatus::Eof;
1098 }
1099
1100 match deadline {
1101 Some(deadline) => {
1102 let now = Instant::now();
1103 if now >= deadline {
1104 return ReadStatus::Timeout;
1105 }
1106 let wait = deadline.saturating_duration_since(now);
1107 let result = self
1108 .shared
1109 .condvar
1110 .wait_timeout(guard, wait)
1111 .expect("queue mutex poisoned");
1112 guard = result.0;
1113 if result.1.timed_out() {
1114 return ReadStatus::Timeout;
1115 }
1116 }
1117 None => {
1118 guard = self
1119 .shared
1120 .condvar
1121 .wait(guard)
1122 .expect("queue mutex poisoned");
1123 }
1124 }
1125 }
1126 }
1127
1128 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
1130 self.shared
1131 .queues
1132 .lock()
1133 .expect("queue mutex poisoned")
1134 .stdout_history
1135 .clone()
1136 .into_iter()
1137 .collect()
1138 }
1139
1140 fn captured_stdout_raw(&self) -> Vec<u8> {
1141 self.shared
1142 .queues
1143 .lock()
1144 .expect("queue mutex poisoned")
1145 .stdout_raw
1146 .clone()
1147 }
1148
1149 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
1151 if self.config.stderr_mode == StderrMode::Stdout {
1152 return Vec::new();
1153 }
1154 self.shared
1155 .queues
1156 .lock()
1157 .expect("queue mutex poisoned")
1158 .stderr_history
1159 .clone()
1160 .into_iter()
1161 .collect()
1162 }
1163
1164 fn captured_stderr_raw(&self) -> Vec<u8> {
1165 if self.config.stderr_mode == StderrMode::Stdout {
1166 return Vec::new();
1167 }
1168 self.shared
1169 .queues
1170 .lock()
1171 .expect("queue mutex poisoned")
1172 .stderr_raw
1173 .clone()
1174 }
1175
1176 pub fn captured_combined(&self) -> Vec<StreamEvent> {
1178 self.shared
1179 .queues
1180 .lock()
1181 .expect("queue mutex poisoned")
1182 .combined_history
1183 .clone()
1184 .into_iter()
1185 .collect()
1186 }
1187
1188 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
1190 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1191 return 0;
1192 }
1193 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1194 match stream {
1195 StreamKind::Stdout => guard.stdout_history_bytes,
1196 StreamKind::Stderr => guard.stderr_history_bytes,
1197 }
1198 }
1199
1200 pub fn captured_combined_bytes(&self) -> usize {
1202 self.shared
1203 .queues
1204 .lock()
1205 .expect("queue mutex poisoned")
1206 .combined_history_bytes
1207 }
1208
1209 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
1211 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1212 return 0;
1213 }
1214 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1215 match stream {
1216 StreamKind::Stdout => {
1217 let released = guard.stdout_history_bytes;
1218 guard.stdout_history.clear();
1219 guard.stdout_raw.clear();
1220 guard.stdout_history_bytes = 0;
1221 released
1222 }
1223 StreamKind::Stderr => {
1224 let released = guard.stderr_history_bytes;
1225 guard.stderr_history.clear();
1226 guard.stderr_raw.clear();
1227 guard.stderr_history_bytes = 0;
1228 released
1229 }
1230 }
1231 }
1232
1233 pub fn clear_captured_combined(&self) -> usize {
1235 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1236 let released = guard.combined_history_bytes;
1237 guard.combined_history.clear();
1238 guard.combined_history_bytes = 0;
1239 released
1240 }
1241
1242 fn build_command(&self) -> Command {
1243 let mut command = match &self.config.command {
1244 CommandSpec::Shell(command) => shell_command(command),
1245 CommandSpec::Argv(argv) => {
1246 let mut command = Command::new(&argv[0]);
1247 if argv.len() > 1 {
1248 command.args(&argv[1..]);
1249 }
1250 command
1251 }
1252 };
1253 if let Some(cwd) = &self.config.cwd {
1254 command.current_dir(cwd);
1255 }
1256 if let Some(env) = &self.config.env {
1257 command.env_clear();
1258 command.envs(env.iter().map(|(k, v)| (k, v)));
1259 }
1260 #[cfg(windows)]
1261 {
1262 use std::os::windows::process::CommandExt;
1263
1264 let flags = windows_creation_flags(
1272 self.config.creationflags,
1273 self.config.create_process_group,
1274 self.config.nice,
1275 crate::windows::parent_has_console(),
1276 );
1277 if flags != 0 {
1278 command.creation_flags(flags);
1279 }
1280 }
1281 #[cfg(unix)]
1282 {
1283 let create_process_group = self.config.create_process_group;
1284 let nice = self.config.nice;
1285
1286 if create_process_group || nice.is_some() {
1287 use std::os::unix::process::CommandExt;
1288
1289 unsafe {
1290 command.pre_exec(move || {
1291 if create_process_group && libc::setpgid(0, 0) == -1 {
1292 return Err(std::io::Error::last_os_error());
1293 }
1294 if let Some(nice) = nice {
1295 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
1296 if result == -1 {
1297 return Err(std::io::Error::last_os_error());
1298 }
1299 }
1300 Ok(())
1301 });
1302 }
1303 }
1304 }
1305 command
1306 }
1307
1308 fn spawn_reader<R>(
1309 &self,
1310 pipe: R,
1311 source_stream: StreamKind,
1312 visible_stream: StreamKind,
1313 on_pipe_done: Box<dyn FnOnce() + Send>,
1314 ) where
1315 R: Read + Send + 'static,
1316 {
1317 let shared = Arc::clone(&self.shared);
1318 thread::spawn(move || {
1319 let mut reader = pipe;
1320 let mut chunk = vec![0_u8; 65536];
1321 let mut pending = Vec::new();
1322
1323 loop {
1324 match reader.read(&mut chunk) {
1325 Ok(0) => break,
1326 Ok(n) => {
1327 append_raw(&shared, visible_stream, &chunk[..n]);
1328 let lines = feed_chunk(&mut pending, &chunk[..n]);
1329 emit_lines(&shared, visible_stream, lines);
1330 }
1331 Err(_) => break,
1332 }
1333 }
1334
1335 if !pending.is_empty() {
1336 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1337 }
1338
1339 on_pipe_done();
1344 drop(reader);
1345
1346 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1347 match source_stream {
1348 StreamKind::Stdout => guard.stdout_closed = true,
1349 StreamKind::Stderr => guard.stderr_closed = true,
1350 }
1351 shared.condvar.notify_all();
1352 });
1353 }
1354
1355 #[cfg(unix)]
1356 fn prepare_unix_capture_reader<R: Read + AsRawFd>(
1357 reader: R,
1358 ) -> std::io::Result<(UnixCancelableReader<R>, UnixStream)> {
1359 set_nonblocking(reader.as_raw_fd())?;
1360 let (wake_reader, wake_writer) = UnixStream::pair()?;
1361 wake_writer.set_nonblocking(true)?;
1362 Ok((
1363 UnixCancelableReader {
1364 reader,
1365 wake_reader,
1366 },
1367 wake_writer,
1368 ))
1369 }
1370
1371 #[cfg(windows)]
1372 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1373 let handles = Arc::clone(&self.capture_pipe_handles);
1374 Box::new(move || {
1375 let mut guard = handles.lock().expect("capture pipe handles mutex poisoned");
1376 match stream {
1377 StreamKind::Stdout => guard.stdout = None,
1378 StreamKind::Stderr => guard.stderr = None,
1379 }
1380 })
1381 }
1382
1383 #[cfg(unix)]
1384 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1385 let wakers = Arc::clone(&self.capture_wakers);
1386 Box::new(move || {
1387 let mut guard = wakers.lock().expect("capture wakers mutex poisoned");
1388 match stream {
1389 StreamKind::Stdout => guard.stdout = None,
1390 StreamKind::Stderr => guard.stderr = None,
1391 }
1392 })
1393 }
1394
1395 #[cfg(not(any(windows, unix)))]
1396 fn pipe_done_callback(&self, _stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1397 Box::new(|| {})
1398 }
1399
1400 #[cfg(windows)]
1404 fn cancel_capture_io(&self) {
1405 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1406 cancel_capture_pipe_io(&self.capture_pipe_handles);
1407 }
1408
1409 #[cfg(unix)]
1410 fn cancel_capture_io(&self) {
1411 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1412 cancel_capture_pipe_io(&self.capture_wakers);
1413 }
1414
1415 fn set_returncode(&self, code: i32) {
1416 self.shared.returncode.store(code as i64, Ordering::Release);
1417 self.shared.condvar.notify_all();
1418 }
1419
1420 fn finish_capture_drain(&self) {
1430 self.finish_capture_drain_with_deadline(kill_drain_deadline());
1431 }
1432
1433 fn finish_capture_drain_with_deadline(&self, deadline: Instant) {
1434 let drained = self.wait_for_capture_completion_with_deadline_impl(deadline);
1435 #[cfg(any(windows, unix))]
1436 if !drained {
1437 self.cancel_capture_io();
1438 }
1439 #[cfg(not(any(windows, unix)))]
1440 let _ = drained;
1441 }
1442
1443 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1446 crate::rp_rust_debug_scope!(
1447 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1448 );
1449 if !self.config.capture {
1450 return true;
1451 }
1452 finalize_capture_completion(&self.shared, deadline)
1453 }
1454}
1455
1456#[cfg(windows)]
1462fn cancel_capture_pipe_io(handles: &Mutex<CapturePipeHandles>) {
1463 use winapi::shared::ntdef::HANDLE;
1464 use winapi::um::ioapiset::CancelIoEx;
1465 let guard = handles.lock().expect("capture pipe handles mutex poisoned");
1466 if let Some(h) = guard.stdout {
1467 unsafe {
1473 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1474 }
1475 }
1476 if let Some(h) = guard.stderr {
1477 unsafe {
1478 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1479 }
1480 }
1481}
1482
1483#[cfg(unix)]
1484fn cancel_capture_pipe_io(wakers: &Mutex<UnixCaptureWakers>) {
1485 use std::os::fd::AsRawFd;
1486
1487 let guard = wakers.lock().expect("capture wakers mutex poisoned");
1488 let byte = [1_u8; 1];
1489 for writer in [&guard.stdout, &guard.stderr].into_iter().flatten() {
1490 let _ = unsafe { libc::write(writer.as_raw_fd(), byte.as_ptr().cast(), byte.len()) };
1494 }
1495}
1496
1497fn finalize_capture_completion(shared: &SharedState, deadline: Instant) -> bool {
1504 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1505 while !(guard.stdout_closed && guard.stderr_closed) {
1506 let now = Instant::now();
1507 if now >= deadline {
1508 guard.stdout_closed = true;
1509 guard.stderr_closed = true;
1510 shared.condvar.notify_all();
1511 return false;
1512 }
1513 let (next_guard, result) = shared
1514 .condvar
1515 .wait_timeout(guard, deadline - now)
1516 .expect("queue mutex poisoned");
1517 guard = next_guard;
1518 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1519 guard.stdout_closed = true;
1520 guard.stderr_closed = true;
1521 shared.condvar.notify_all();
1522 return false;
1523 }
1524 }
1525 true
1526}
1527
1528fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1529 if lines.is_empty() {
1530 return;
1531 }
1532 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1533 for line in lines {
1534 let line_len = line.len();
1535 match stream {
1536 StreamKind::Stdout => {
1537 guard.stdout_history_bytes += line_len;
1538 guard.stdout_history.push_back(line.clone());
1539 guard.stdout_queue.push_back(line.clone());
1540 }
1541 StreamKind::Stderr => {
1542 guard.stderr_history_bytes += line_len;
1543 guard.stderr_history.push_back(line.clone());
1544 guard.stderr_queue.push_back(line.clone());
1545 }
1546 }
1547 let event = StreamEvent { stream, line };
1548 guard.combined_history_bytes += line_len;
1549 guard.combined_history.push_back(event.clone());
1550 guard.combined_queue.push_back(event);
1551 }
1552 shared.condvar.notify_all();
1553}
1554
1555fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) {
1556 if chunk.is_empty() {
1557 return;
1558 }
1559 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1560 match stream {
1561 StreamKind::Stdout => guard.stdout_raw.extend_from_slice(chunk),
1562 StreamKind::Stderr => guard.stderr_raw.extend_from_slice(chunk),
1563 }
1564}
1565
1566pub fn run_command(
1572 mut config: ProcessConfig,
1573 timeout: Option<Duration>,
1574) -> Result<RunOutput, ProcessError> {
1575 config.capture = true;
1576 let process = NativeProcess::new(config);
1577 process.start()?;
1578
1579 let exit_code = match process.wait(timeout) {
1580 Ok(code) => code,
1581 Err(ProcessError::Timeout) => {
1582 match process.kill() {
1583 Ok(()) | Err(ProcessError::NotRunning) => {}
1584 Err(error) => return Err(error),
1585 }
1586 return Err(ProcessError::Timeout);
1587 }
1588 Err(error) => return Err(error),
1589 };
1590
1591 Ok(RunOutput {
1592 stdout: process.captured_stdout_raw(),
1593 stderr: process.captured_stderr_raw(),
1594 exit_code,
1595 })
1596}
1597
1598pub(crate) fn shell_command(command: &str) -> Command {
1599 #[cfg(windows)]
1600 {
1601 use std::os::windows::process::CommandExt;
1602
1603 let mut cmd = Command::new("cmd");
1604 cmd.raw_arg("/D /S /C \"");
1605 cmd.raw_arg(command);
1606 cmd.raw_arg("\"");
1607 cmd
1608 }
1609 #[cfg(not(windows))]
1610 {
1611 let mut cmd = Command::new("sh");
1612 cmd.arg("-lc").arg(command);
1613 cmd
1614 }
1615}
1616
1617#[cfg(test)]
1618mod tests;