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::{
123 find_declared_daemon_pids, find_processes_by_originator, OriginatorProcessInfo,
124};
125pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
126pub use spawn::{
127 spawn, spawn_daemon, spawn_daemon_breaking_away_from_job,
128 spawn_daemon_breaking_away_with_env_policy, spawn_daemon_with_clear_env,
129 spawn_daemon_with_env_policy, spawn_with_env_policy, DaemonChild, EnvironmentPolicy,
130 SpawnStdio, SpawnedChild, StdioSource, DAEMON_MARKER_ENV_VAR,
131};
132pub use terminal_graphics::{
133 current_terminal_capabilities, current_terminal_capabilities_with_timeout,
134 detect_terminal_capabilities, CapabilityStatus, EvidenceStrength, GraphicsCapability,
135 GraphicsProtocol, TerminalCapabilities, TerminalCapabilityInput, TerminalGraphicsCapabilities,
136 TerminalProbeEvidence,
137};
138pub use types::{
139 CommandSpec, ProcessConfig, ProcessError, ReadStatus, RunOutput, StderrMode, StdinMode,
140 StreamEvent, StreamKind,
141};
142
143#[cfg(unix)]
144pub(crate) use helpers::{
145 child_signal_disposition, child_try_wait_error_is_retryable, completed_reap_after_signal,
146 poll_mutex_until, with_child_lock_for_signal, ChildSignalDisposition,
147};
148pub(crate) use helpers::{exit_code, feed_chunk, kill_drain_deadline, log_spawned_child_pid};
149#[cfg(unix)]
150pub use unix::{unix_set_priority, unix_signal_process, unix_signal_process_group, UnixSignal};
151#[cfg(windows)]
152pub(crate) use windows::{
153 assign_child_to_windows_kill_on_close_job_impl, windows_creation_flags, CapturePipeHandles,
154 WindowsJobHandle,
155};
156
157#[macro_export]
158macro_rules! rp_rust_debug_scope {
160 ($label:expr) => {
161 let _running_process_rust_debug_scope =
162 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
163 };
164}
165
166#[derive(Default)]
167struct QueueState {
168 stdout_queue: VecDeque<Vec<u8>>,
169 stderr_queue: VecDeque<Vec<u8>>,
170 combined_queue: VecDeque<StreamEvent>,
171 stdout_history: VecDeque<Vec<u8>>,
172 stderr_history: VecDeque<Vec<u8>>,
173 combined_history: VecDeque<StreamEvent>,
174 stdout_raw: Vec<u8>,
175 stderr_raw: Vec<u8>,
176 stdout_history_bytes: usize,
177 stderr_history_bytes: usize,
178 combined_history_bytes: usize,
179 stdout_closed: bool,
180 stderr_closed: bool,
181}
182
183const RETURNCODE_NOT_SET: i64 = i64::MIN;
185
186struct SharedState {
187 queues: Mutex<QueueState>,
188 condvar: Condvar,
189 returncode: AtomicI64,
192 observer: Option<ObserverEmitter>,
197 observer_exit_emitted: AtomicBool,
200}
201
202struct ChildState {
203 child: Child,
204 #[cfg(windows)]
205 _job: WindowsJobHandle,
206}
207
208#[cfg(unix)]
209#[derive(Default)]
210struct UnixCaptureWakers {
211 stdout: Option<UnixStream>,
212 stderr: Option<UnixStream>,
213}
214
215#[cfg(unix)]
216struct UnixCancelableReader<R> {
217 reader: R,
218 wake_reader: UnixStream,
219}
220
221#[cfg(any(test, unix))]
222#[derive(Debug, Eq, PartialEq)]
223enum CapturePollAction {
224 Wait,
225 Read,
226 Cancel,
227}
228
229#[cfg(any(test, unix))]
230fn capture_poll_action(capture_revents: i16, wake_revents: i16) -> CapturePollAction {
231 if wake_revents != 0 {
232 CapturePollAction::Cancel
233 } else if capture_revents != 0 {
234 CapturePollAction::Read
235 } else {
236 CapturePollAction::Wait
237 }
238}
239
240#[cfg(unix)]
241impl<R: Read + AsRawFd> Read for UnixCancelableReader<R> {
242 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
243 if buf.is_empty() {
244 return Ok(0);
245 }
246 loop {
247 let mut poll_fds = [
248 libc::pollfd {
249 fd: self.reader.as_raw_fd(),
250 events: libc::POLLIN | libc::POLLHUP | libc::POLLERR,
251 revents: 0,
252 },
253 libc::pollfd {
254 fd: self.wake_reader.as_raw_fd(),
255 events: libc::POLLIN | libc::POLLHUP | libc::POLLERR,
256 revents: 0,
257 },
258 ];
259 let polled = unsafe { libc::poll(poll_fds.as_mut_ptr(), poll_fds.len() as _, -1) };
260 if polled < 0 {
261 let error = std::io::Error::last_os_error();
262 if error.kind() == std::io::ErrorKind::Interrupted {
263 continue;
264 }
265 return Err(error);
266 }
267 match capture_poll_action(poll_fds[0].revents, poll_fds[1].revents) {
268 CapturePollAction::Cancel => {
269 return Err(std::io::Error::new(
270 std::io::ErrorKind::Interrupted,
271 "capture reader cancelled",
272 ));
273 }
274 CapturePollAction::Read => match self.reader.read(buf) {
275 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => continue,
276 result => return result,
277 },
278 CapturePollAction::Wait => {}
279 }
280 }
281 }
282}
283
284#[cfg(unix)]
285fn set_nonblocking(fd: RawFd) -> std::io::Result<()> {
286 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
287 if flags < 0 {
288 return Err(std::io::Error::last_os_error());
289 }
290 if unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0 {
291 return Err(std::io::Error::last_os_error());
292 }
293 Ok(())
294}
295
296#[cfg(unix)]
297fn cleanup_child_after_start_error(mut child: Child) {
298 let _ = child.kill();
299 thread::spawn(move || {
302 let _ = child.wait();
303 });
304}
305
306impl SharedState {
307 fn new(capture: bool) -> Self {
308 Self::with_observer(capture, None)
309 }
310
311 fn with_observer(capture: bool, observer: Option<ObserverEmitter>) -> Self {
312 let queues = QueueState {
313 stdout_closed: !capture,
314 stderr_closed: !capture,
315 ..QueueState::default()
316 };
317 Self {
318 queues: Mutex::new(queues),
319 condvar: Condvar::new(),
320 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
321 observer,
322 observer_exit_emitted: AtomicBool::new(false),
323 }
324 }
325
326 fn emit_exited(&self, pid: u32, exit_code: i32) {
329 let Some(emitter) = self.observer.as_ref() else {
330 return;
331 };
332 if self
333 .observer_exit_emitted
334 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
335 .is_ok()
336 {
337 emitter.emit_exited(pid, exit_code);
338 }
339 }
340}
341
342pub struct NativeProcess {
349 config: ProcessConfig,
350 child: Arc<Mutex<Option<ChildState>>>,
351 stdin: Mutex<Option<ChildStdin>>,
352 shared: Arc<SharedState>,
353 #[cfg(test)]
354 stdin_write_active: AtomicBool,
355 #[cfg(windows)]
356 capture_pipe_handles: Arc<Mutex<CapturePipeHandles>>,
357 #[cfg(unix)]
358 capture_wakers: Arc<Mutex<UnixCaptureWakers>>,
359}
360
361impl NativeProcess {
362 pub fn new(config: ProcessConfig) -> Self {
368 Self::new_with_observer(config, None)
369 }
370
371 pub fn with_observer(
384 config: ProcessConfig,
385 observer: crate::observer::ObserverConfig,
386 ) -> (Self, ObserverSubscriber) {
387 let (emitter, subscriber) = ObserverEmitter::new(observer);
388 let process = Self::new_with_observer(config, Some(emitter));
389 (process, subscriber)
390 }
391
392 fn new_with_observer(config: ProcessConfig, observer: Option<ObserverEmitter>) -> Self {
393 let shared = match observer {
394 None => SharedState::new(config.capture),
396 Some(emitter) => SharedState::with_observer(config.capture, Some(emitter)),
397 };
398 Self {
399 shared: Arc::new(shared),
400 child: Arc::new(Mutex::new(None)),
401 stdin: Mutex::new(None),
402 #[cfg(test)]
403 stdin_write_active: AtomicBool::new(false),
404 config,
405 #[cfg(windows)]
406 capture_pipe_handles: Arc::new(Mutex::new(CapturePipeHandles::default())),
407 #[cfg(unix)]
408 capture_wakers: Arc::new(Mutex::new(UnixCaptureWakers::default())),
409 }
410 }
411
412 #[inline(never)]
414 pub fn start(&self) -> Result<(), ProcessError> {
419 public_symbols::rp_native_process_start_public(self)
420 }
421
422 fn start_impl(&self) -> Result<(), ProcessError> {
423 crate::rp_rust_debug_scope!("running_process::NativeProcess::start");
424 let mut guard = self.child.lock().expect("child mutex poisoned");
425 if guard.is_some() {
426 return Err(ProcessError::AlreadyStarted);
427 }
428
429 let mut command = self.build_command();
430 match self.config.stdin_mode {
431 StdinMode::Inherit => {}
432 StdinMode::Piped => {
433 command.stdin(Stdio::piped());
434 }
435 StdinMode::Null => {
436 command.stdin(Stdio::null());
437 }
438 }
439 if self.config.capture {
440 command.stdout(Stdio::piped());
441 command.stderr(Stdio::piped());
442 }
443
444 let mut child = command.spawn().map_err(ProcessError::Spawn)?;
445 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
446 if let Some(emitter) = self.shared.observer.as_ref() {
449 emitter.emit_started(child.id());
450 }
451 #[cfg(windows)]
456 let job = {
457 let descendant_sink = self
458 .shared
459 .observer
460 .as_ref()
461 .and_then(|e| e.descendant_sink());
462 let direct_pid = child.id();
463 public_symbols::rp_assign_child_to_windows_kill_on_close_job_with_observer_public(
464 &child,
465 descendant_sink,
466 direct_pid,
467 )
468 .map_err(ProcessError::Spawn)?
469 };
470 #[cfg(target_os = "linux")]
474 {
475 if let Some(emitter) = self.shared.observer.as_ref() {
476 if let Some((sink, stop)) = emitter.descendant_pump() {
477 crate::observer::descendants_linux::enable_subreaper();
478 crate::observer::descendants_linux::spawn_pump(child.id(), sink, stop);
479 }
480 }
481 }
482 #[cfg(target_os = "macos")]
486 {
487 if let Some(emitter) = self.shared.observer.as_ref() {
488 if let Some((sink, stop)) = emitter.descendant_pump() {
489 crate::observer::descendants_macos::spawn_pump(child.id(), sink, stop);
490 }
491 }
492 }
493 if self.config.capture {
494 let stdout = child.stdout.take().expect("stdout pipe missing");
495 let stderr = child.stderr.take().expect("stderr pipe missing");
496 #[cfg(windows)]
497 {
498 use std::os::windows::io::AsRawHandle;
499 let mut handles = self
500 .capture_pipe_handles
501 .lock()
502 .expect("capture pipe handles mutex poisoned");
503 handles.stdout = Some(stdout.as_raw_handle() as usize);
504 handles.stderr = Some(stderr.as_raw_handle() as usize);
505 }
506 #[cfg(unix)]
507 let ((stdout, stdout_waker), (stderr, stderr_waker)) =
508 match Self::prepare_unix_capture_reader(stdout).and_then(|stdout| {
509 Self::prepare_unix_capture_reader(stderr).map(|stderr| (stdout, stderr))
510 }) {
511 Ok(readers) => readers,
512 Err(error) => {
513 cleanup_child_after_start_error(child);
514 return Err(ProcessError::Spawn(error));
515 }
516 };
517 #[cfg(unix)]
518 {
519 let mut wakers = self
520 .capture_wakers
521 .lock()
522 .expect("capture wakers mutex poisoned");
523 wakers.stdout = Some(stdout_waker);
524 wakers.stderr = Some(stderr_waker);
525 }
526 self.spawn_reader(
527 stdout,
528 StreamKind::Stdout,
529 StreamKind::Stdout,
530 self.pipe_done_callback(StreamKind::Stdout),
531 );
532 self.spawn_reader(
533 stderr,
534 StreamKind::Stderr,
535 match self.config.stderr_mode {
536 StderrMode::Stdout => StreamKind::Stdout,
537 StderrMode::Pipe => StreamKind::Stderr,
538 },
539 self.pipe_done_callback(StreamKind::Stderr),
540 );
541 }
542 *self.stdin.lock().expect("stdin mutex poisoned") = child.stdin.take();
543 *guard = Some(ChildState {
544 child,
545 #[cfg(windows)]
546 _job: job,
547 });
548 drop(guard);
549 self.spawn_exit_waiter();
550 Ok(())
551 }
552
553 fn spawn_exit_waiter(&self) {
556 let child = Arc::clone(&self.child);
557 let shared = Arc::clone(&self.shared);
558 let capture = self.config.capture;
559 #[cfg(windows)]
560 let capture_pipe_handles = Arc::clone(&self.capture_pipe_handles);
561 #[cfg(unix)]
562 let capture_wakers = Arc::clone(&self.capture_wakers);
563 thread::spawn(move || {
564 loop {
565 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
566 return;
567 }
568 let exited = {
569 let mut guard = child.lock().expect("child mutex poisoned");
570 if let Some(child_state) = guard.as_mut() {
571 let pid = child_state.child.id();
572 match child_state.child.try_wait() {
573 Ok(Some(status)) => {
574 let code = exit_code(status);
575 shared.returncode.store(code as i64, Ordering::Release);
576 shared.emit_exited(pid, code);
580 shared.condvar.notify_all();
581 true
582 }
583 Ok(None) => false,
584 Err(_error) => {
585 #[cfg(unix)]
586 if child_try_wait_error_is_retryable(&_error) {
587 false
588 } else {
589 return;
590 }
591 #[cfg(windows)]
592 return;
593 }
594 }
595 } else {
596 return;
597 }
598 };
599 if exited {
600 if capture {
615 let drained = finalize_capture_completion(&shared, kill_drain_deadline());
616 #[cfg(windows)]
617 if !drained {
618 cancel_capture_pipe_io(&capture_pipe_handles);
619 }
620 #[cfg(unix)]
621 if !drained {
622 cancel_capture_pipe_io(&capture_wakers);
623 }
624 #[cfg(not(any(windows, unix)))]
625 let _ = drained;
626 }
627 return;
628 }
629 thread::sleep(Duration::from_millis(10));
635 }
636 });
637 }
638
639 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
641 if self.child.lock().expect("child mutex poisoned").is_none() {
642 return Err(ProcessError::NotRunning);
643 }
644 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
645 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
646 use std::io::Write;
647 #[cfg(test)]
648 self.stdin_write_active.store(true, Ordering::Release);
649 let write_result = stdin.write_all(data);
650 #[cfg(test)]
651 self.stdin_write_active.store(false, Ordering::Release);
652 write_result.map_err(ProcessError::Io)?;
653 stdin.flush().map_err(ProcessError::Io)?;
654 drop(guard.take());
655 Ok(())
656 }
657
658 pub fn write_stdin_streaming(&self, data: &[u8]) -> Result<(), ProcessError> {
663 if self.child.lock().expect("child mutex poisoned").is_none() {
664 return Err(ProcessError::NotRunning);
665 }
666 let mut guard = self.stdin.lock().expect("stdin mutex poisoned");
667 let stdin = guard.as_mut().ok_or(ProcessError::StdinUnavailable)?;
668 use std::io::Write;
669 #[cfg(test)]
670 self.stdin_write_active.store(true, Ordering::Release);
671 let write_result = stdin.write_all(data);
672 #[cfg(test)]
673 self.stdin_write_active.store(false, Ordering::Release);
674 write_result.map_err(ProcessError::Io)?;
675 stdin.flush().map_err(ProcessError::Io)?;
676 Ok(())
677 }
678
679 pub fn close_stdin(&self) -> Result<(), ProcessError> {
682 if self.child.lock().expect("child mutex poisoned").is_none() {
683 return Err(ProcessError::NotRunning);
684 }
685 drop(self.stdin.lock().expect("stdin mutex poisoned").take());
686 Ok(())
687 }
688
689 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
693 if let Some(code) = self.returncode() {
695 return Ok(Some(code));
696 }
697 let mut guard = self.child.lock().expect("child mutex poisoned");
698 let Some(child_state) = guard.as_mut() else {
699 return Ok(self.returncode());
700 };
701 let pid = child_state.child.id();
702 let child = &mut child_state.child;
703 let status = child.try_wait().map_err(ProcessError::Io)?;
704 if let Some(status) = status {
705 let code = exit_code(status);
706 self.set_returncode(code);
707 self.shared.emit_exited(pid, code);
708 return Ok(Some(code));
709 }
710 Ok(None)
711 }
712
713 #[inline(never)]
715 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
720 public_symbols::rp_native_process_wait_public(self, timeout)
721 }
722
723 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
724 crate::rp_rust_debug_scope!("running_process::NativeProcess::wait");
725 if self.child.lock().expect("child mutex poisoned").is_none() {
726 return self.returncode().ok_or(ProcessError::NotRunning);
727 }
728 if let Some(code) = self.returncode() {
730 self.finish_capture_drain();
731 return Ok(code);
732 }
733 let start = Instant::now();
734 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
735 loop {
736 let rc = self.shared.returncode.load(Ordering::Acquire);
738 if rc != RETURNCODE_NOT_SET {
739 drop(guard);
740 let code = rc as i32;
741 self.finish_capture_drain();
742 return Ok(code);
743 }
744 if let Some(limit) = timeout {
745 let elapsed = start.elapsed();
746 if elapsed >= limit {
747 return Err(ProcessError::Timeout);
748 }
749 let remaining = limit - elapsed;
750 let wait_time = remaining.min(Duration::from_millis(50));
752 guard = self
753 .shared
754 .condvar
755 .wait_timeout(guard, wait_time)
756 .expect("queue mutex poisoned")
757 .0;
758 } else {
759 guard = self
761 .shared
762 .condvar
763 .wait_timeout(guard, Duration::from_millis(50))
764 .expect("queue mutex poisoned")
765 .0;
766 }
767 }
768 }
769
770 #[inline(never)]
772 pub fn kill(&self) -> Result<(), ProcessError> {
774 public_symbols::rp_native_process_kill_public(self)
775 }
776
777 fn kill_impl(&self) -> Result<(), ProcessError> {
778 crate::rp_rust_debug_scope!("running_process::NativeProcess::kill");
779 #[cfg(windows)]
780 {
781 let mut guard = self.child.lock().expect("child mutex poisoned");
782 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
783 let pid = child.id();
784 child.kill().map_err(ProcessError::Io)?;
785 let status = child.wait().map_err(ProcessError::Io)?;
786 let code = exit_code(status);
787 self.set_returncode(code);
788 self.shared.emit_exited(pid, code);
791 }
792 #[cfg(unix)]
793 {
794 let deadline = kill_drain_deadline();
795 let (pid, already_reaped) = with_child_lock_for_signal(&self.child, |state| {
796 let child = &mut state.as_mut().ok_or(ProcessError::NotRunning)?.child;
797 let pid = child.id();
798 match child_signal_disposition(child.try_wait()).map_err(ProcessError::Io)? {
799 ChildSignalDisposition::AlreadyExited(status) => Ok((pid, Some(status))),
800 ChildSignalDisposition::Signal => {
801 let group_signaled = self.config.create_process_group
802 && unix_signal_process_group(pid as i32, UnixSignal::Kill).is_ok();
803 if !group_signaled {
804 child.kill().map_err(ProcessError::Io)?;
805 }
806 Ok((pid, None))
807 }
808 }
809 })?;
810
811 self.cancel_capture_io();
815 let reaped = already_reaped.or_else(|| {
816 let reap_result =
817 poll_mutex_until(&self.child, deadline, Duration::from_millis(10), |state| {
818 match state.as_mut() {
819 Some(child) => child.child.try_wait(),
820 None => Ok(None),
821 }
822 });
823 completed_reap_after_signal(reap_result)
824 });
825 if let Some(status) = reaped {
826 let code = exit_code(status);
827 self.set_returncode(code);
828 self.shared.emit_exited(pid, code);
829 }
830 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
831 self, deadline,
832 );
833 Ok(())
834 }
835 #[cfg(windows)]
836 {
837 #[cfg(any(windows, unix))]
845 self.cancel_capture_io();
846 public_symbols::rp_native_process_wait_for_capture_completion_with_deadline_public(
857 self,
858 kill_drain_deadline(),
859 );
860 Ok(())
861 }
862 }
863
864 pub fn terminate(&self) -> Result<(), ProcessError> {
868 self.kill()
869 }
870
871 pub fn terminate_group_soft(&self) -> Result<(), ProcessError> {
886 #[cfg(unix)]
887 {
888 if !self.config.create_process_group {
889 return Ok(());
890 }
891 let pid = match self.pid() {
892 Some(p) => p as i32,
893 None => return Err(ProcessError::NotRunning),
894 };
895 let result = unsafe { libc::kill(-pid, libc::SIGTERM) };
896 if result != 0 {
897 let err = std::io::Error::last_os_error();
898 if err.raw_os_error() != Some(libc::ESRCH) {
899 return Err(ProcessError::Io(err));
900 }
901 }
902 Ok(())
903 }
904 #[cfg(windows)]
905 {
906 if !self.config.create_process_group {
907 return Ok(());
912 }
913 let pid = match self.pid() {
914 Some(p) => p,
915 None => return Err(ProcessError::NotRunning),
916 };
917 let ok = unsafe {
921 winapi::um::wincon::GenerateConsoleCtrlEvent(
922 winapi::um::wincon::CTRL_BREAK_EVENT,
923 pid,
924 )
925 };
926 if ok == 0 {
927 let err = std::io::Error::last_os_error();
928 if err.raw_os_error() != Some(6) {
934 return Err(ProcessError::Io(err));
935 }
936 }
937 Ok(())
938 }
939 }
940
941 #[inline(never)]
943 pub fn close(&self) -> Result<(), ProcessError> {
945 public_symbols::rp_native_process_close_public(self)
946 }
947
948 fn close_impl(&self) -> Result<(), ProcessError> {
949 crate::rp_rust_debug_scope!("running_process::NativeProcess::close");
950 if self.child.lock().expect("child mutex poisoned").is_none() {
951 return Ok(());
952 }
953 if self.poll()?.is_none() {
954 self.kill()?;
955 } else {
956 self.finish_capture_drain();
957 }
958 Ok(())
959 }
960
961 pub fn pid(&self) -> Option<u32> {
963 self.child
964 .lock()
965 .expect("child mutex poisoned")
966 .as_ref()
967 .map(|state| state.child.id())
968 }
969
970 pub fn returncode(&self) -> Option<i32> {
972 let v = self.shared.returncode.load(Ordering::Acquire);
973 if v == RETURNCODE_NOT_SET {
974 None
975 } else {
976 Some(v as i32)
977 }
978 }
979
980 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
982 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
983 return false;
984 }
985 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
986 match stream {
987 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
988 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
989 }
990 }
991
992 pub fn has_pending_combined(&self) -> bool {
994 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
995 !guard.combined_queue.is_empty()
996 }
997
998 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
1000 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1001 return Vec::new();
1002 }
1003 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1004 let queue = match stream {
1005 StreamKind::Stdout => &mut guard.stdout_queue,
1006 StreamKind::Stderr => &mut guard.stderr_queue,
1007 };
1008 queue.drain(..).collect()
1009 }
1010
1011 pub fn drain_combined(&self) -> Vec<StreamEvent> {
1013 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1014 guard.combined_queue.drain(..).collect()
1015 }
1016
1017 pub fn read_stream(
1022 &self,
1023 stream: StreamKind,
1024 timeout: Option<Duration>,
1025 ) -> ReadStatus<Vec<u8>> {
1026 let deadline = timeout.map(|limit| Instant::now() + limit);
1027 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1028
1029 loop {
1030 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1031 return ReadStatus::Eof;
1032 }
1033
1034 let queue = match stream {
1035 StreamKind::Stdout => &mut guard.stdout_queue,
1036 StreamKind::Stderr => &mut guard.stderr_queue,
1037 };
1038 if let Some(line) = queue.pop_front() {
1039 return ReadStatus::Line(line);
1040 }
1041
1042 let closed = match stream {
1043 StreamKind::Stdout => {
1044 if self.config.stderr_mode == StderrMode::Stdout {
1045 guard.stdout_closed && guard.stderr_closed
1046 } else {
1047 guard.stdout_closed
1048 }
1049 }
1050 StreamKind::Stderr => guard.stderr_closed,
1051 };
1052 if closed {
1053 return ReadStatus::Eof;
1054 }
1055
1056 match deadline {
1057 Some(deadline) => {
1058 let now = Instant::now();
1059 if now >= deadline {
1060 return ReadStatus::Timeout;
1061 }
1062 let wait = deadline.saturating_duration_since(now);
1063 let result = self
1064 .shared
1065 .condvar
1066 .wait_timeout(guard, wait)
1067 .expect("queue mutex poisoned");
1068 guard = result.0;
1069 if result.1.timed_out() {
1070 return ReadStatus::Timeout;
1071 }
1072 }
1073 None => {
1074 guard = self
1075 .shared
1076 .condvar
1077 .wait(guard)
1078 .expect("queue mutex poisoned");
1079 }
1080 }
1081 }
1082 }
1083
1084 #[inline(never)]
1086 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1088 public_symbols::rp_native_process_read_combined_public(self, timeout)
1089 }
1090
1091 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
1092 crate::rp_rust_debug_scope!("running_process::NativeProcess::read_combined");
1093 let deadline = timeout.map(|limit| Instant::now() + limit);
1094 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1095
1096 loop {
1097 if let Some(event) = guard.combined_queue.pop_front() {
1098 return ReadStatus::Line(event);
1099 }
1100 if guard.stdout_closed && guard.stderr_closed {
1101 return ReadStatus::Eof;
1102 }
1103
1104 match deadline {
1105 Some(deadline) => {
1106 let now = Instant::now();
1107 if now >= deadline {
1108 return ReadStatus::Timeout;
1109 }
1110 let wait = deadline.saturating_duration_since(now);
1111 let result = self
1112 .shared
1113 .condvar
1114 .wait_timeout(guard, wait)
1115 .expect("queue mutex poisoned");
1116 guard = result.0;
1117 if result.1.timed_out() {
1118 return ReadStatus::Timeout;
1119 }
1120 }
1121 None => {
1122 guard = self
1123 .shared
1124 .condvar
1125 .wait(guard)
1126 .expect("queue mutex poisoned");
1127 }
1128 }
1129 }
1130 }
1131
1132 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
1134 self.shared
1135 .queues
1136 .lock()
1137 .expect("queue mutex poisoned")
1138 .stdout_history
1139 .clone()
1140 .into_iter()
1141 .collect()
1142 }
1143
1144 fn captured_stdout_raw(&self) -> Vec<u8> {
1145 self.shared
1146 .queues
1147 .lock()
1148 .expect("queue mutex poisoned")
1149 .stdout_raw
1150 .clone()
1151 }
1152
1153 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
1155 if self.config.stderr_mode == StderrMode::Stdout {
1156 return Vec::new();
1157 }
1158 self.shared
1159 .queues
1160 .lock()
1161 .expect("queue mutex poisoned")
1162 .stderr_history
1163 .clone()
1164 .into_iter()
1165 .collect()
1166 }
1167
1168 fn captured_stderr_raw(&self) -> Vec<u8> {
1169 if self.config.stderr_mode == StderrMode::Stdout {
1170 return Vec::new();
1171 }
1172 self.shared
1173 .queues
1174 .lock()
1175 .expect("queue mutex poisoned")
1176 .stderr_raw
1177 .clone()
1178 }
1179
1180 pub fn captured_combined(&self) -> Vec<StreamEvent> {
1182 self.shared
1183 .queues
1184 .lock()
1185 .expect("queue mutex poisoned")
1186 .combined_history
1187 .clone()
1188 .into_iter()
1189 .collect()
1190 }
1191
1192 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
1194 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1195 return 0;
1196 }
1197 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
1198 match stream {
1199 StreamKind::Stdout => guard.stdout_history_bytes,
1200 StreamKind::Stderr => guard.stderr_history_bytes,
1201 }
1202 }
1203
1204 pub fn captured_combined_bytes(&self) -> usize {
1206 self.shared
1207 .queues
1208 .lock()
1209 .expect("queue mutex poisoned")
1210 .combined_history_bytes
1211 }
1212
1213 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
1215 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
1216 return 0;
1217 }
1218 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1219 match stream {
1220 StreamKind::Stdout => {
1221 let released = guard.stdout_history_bytes;
1222 guard.stdout_history.clear();
1223 guard.stdout_raw.clear();
1224 guard.stdout_history_bytes = 0;
1225 released
1226 }
1227 StreamKind::Stderr => {
1228 let released = guard.stderr_history_bytes;
1229 guard.stderr_history.clear();
1230 guard.stderr_raw.clear();
1231 guard.stderr_history_bytes = 0;
1232 released
1233 }
1234 }
1235 }
1236
1237 pub fn clear_captured_combined(&self) -> usize {
1239 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
1240 let released = guard.combined_history_bytes;
1241 guard.combined_history.clear();
1242 guard.combined_history_bytes = 0;
1243 released
1244 }
1245
1246 fn build_command(&self) -> Command {
1247 let mut command = match &self.config.command {
1248 CommandSpec::Shell(command) => shell_command(command),
1249 CommandSpec::Argv(argv) => {
1250 let mut command = Command::new(&argv[0]);
1251 if argv.len() > 1 {
1252 command.args(&argv[1..]);
1253 }
1254 command
1255 }
1256 };
1257 if let Some(cwd) = &self.config.cwd {
1258 command.current_dir(cwd);
1259 }
1260 if let Some(env) = &self.config.env {
1261 command.env_clear();
1262 command.envs(env.iter().map(|(k, v)| (k, v)));
1263 }
1264 #[cfg(windows)]
1265 {
1266 use std::os::windows::process::CommandExt;
1267
1268 let flags = windows_creation_flags(
1276 self.config.creationflags,
1277 self.config.create_process_group,
1278 self.config.nice,
1279 crate::windows::parent_has_console(),
1280 );
1281 if flags != 0 {
1282 command.creation_flags(flags);
1283 }
1284 }
1285 #[cfg(unix)]
1286 {
1287 let create_process_group = self.config.create_process_group;
1288 let nice = self.config.nice;
1289
1290 if create_process_group || nice.is_some() {
1291 use std::os::unix::process::CommandExt;
1292
1293 unsafe {
1294 command.pre_exec(move || {
1295 if create_process_group && libc::setpgid(0, 0) == -1 {
1296 return Err(std::io::Error::last_os_error());
1297 }
1298 if let Some(nice) = nice {
1299 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
1300 if result == -1 {
1301 return Err(std::io::Error::last_os_error());
1302 }
1303 }
1304 Ok(())
1305 });
1306 }
1307 }
1308 }
1309 command
1310 }
1311
1312 fn spawn_reader<R>(
1313 &self,
1314 pipe: R,
1315 source_stream: StreamKind,
1316 visible_stream: StreamKind,
1317 on_pipe_done: Box<dyn FnOnce() + Send>,
1318 ) where
1319 R: Read + Send + 'static,
1320 {
1321 let shared = Arc::clone(&self.shared);
1322 thread::spawn(move || {
1323 let mut reader = pipe;
1324 let mut chunk = vec![0_u8; 65536];
1325 let mut pending = Vec::new();
1326
1327 loop {
1328 match reader.read(&mut chunk) {
1329 Ok(0) => break,
1330 Ok(n) => {
1331 append_raw(&shared, visible_stream, &chunk[..n]);
1332 let lines = feed_chunk(&mut pending, &chunk[..n]);
1333 emit_lines(&shared, visible_stream, lines);
1334 }
1335 Err(_) => break,
1336 }
1337 }
1338
1339 if !pending.is_empty() {
1340 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
1341 }
1342
1343 on_pipe_done();
1348 drop(reader);
1349
1350 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1351 match source_stream {
1352 StreamKind::Stdout => guard.stdout_closed = true,
1353 StreamKind::Stderr => guard.stderr_closed = true,
1354 }
1355 shared.condvar.notify_all();
1356 });
1357 }
1358
1359 #[cfg(unix)]
1360 fn prepare_unix_capture_reader<R: Read + AsRawFd>(
1361 reader: R,
1362 ) -> std::io::Result<(UnixCancelableReader<R>, UnixStream)> {
1363 set_nonblocking(reader.as_raw_fd())?;
1364 let (wake_reader, wake_writer) = UnixStream::pair()?;
1365 wake_writer.set_nonblocking(true)?;
1366 Ok((
1367 UnixCancelableReader {
1368 reader,
1369 wake_reader,
1370 },
1371 wake_writer,
1372 ))
1373 }
1374
1375 #[cfg(windows)]
1376 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1377 let handles = Arc::clone(&self.capture_pipe_handles);
1378 Box::new(move || {
1379 let mut guard = handles.lock().expect("capture pipe handles mutex poisoned");
1380 match stream {
1381 StreamKind::Stdout => guard.stdout = None,
1382 StreamKind::Stderr => guard.stderr = None,
1383 }
1384 })
1385 }
1386
1387 #[cfg(unix)]
1388 fn pipe_done_callback(&self, stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1389 let wakers = Arc::clone(&self.capture_wakers);
1390 Box::new(move || {
1391 let mut guard = wakers.lock().expect("capture wakers mutex poisoned");
1392 match stream {
1393 StreamKind::Stdout => guard.stdout = None,
1394 StreamKind::Stderr => guard.stderr = None,
1395 }
1396 })
1397 }
1398
1399 #[cfg(not(any(windows, unix)))]
1400 fn pipe_done_callback(&self, _stream: StreamKind) -> Box<dyn FnOnce() + Send> {
1401 Box::new(|| {})
1402 }
1403
1404 #[cfg(windows)]
1408 fn cancel_capture_io(&self) {
1409 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1410 cancel_capture_pipe_io(&self.capture_pipe_handles);
1411 }
1412
1413 #[cfg(unix)]
1414 fn cancel_capture_io(&self) {
1415 crate::rp_rust_debug_scope!("running_process::NativeProcess::cancel_capture_io");
1416 cancel_capture_pipe_io(&self.capture_wakers);
1417 }
1418
1419 fn set_returncode(&self, code: i32) {
1420 self.shared.returncode.store(code as i64, Ordering::Release);
1421 self.shared.condvar.notify_all();
1422 }
1423
1424 fn finish_capture_drain(&self) {
1434 self.finish_capture_drain_with_deadline(kill_drain_deadline());
1435 }
1436
1437 fn finish_capture_drain_with_deadline(&self, deadline: Instant) {
1438 let drained = self.wait_for_capture_completion_with_deadline_impl(deadline);
1439 #[cfg(any(windows, unix))]
1440 if !drained {
1441 self.cancel_capture_io();
1442 }
1443 #[cfg(not(any(windows, unix)))]
1444 let _ = drained;
1445 }
1446
1447 fn wait_for_capture_completion_with_deadline_impl(&self, deadline: Instant) -> bool {
1450 crate::rp_rust_debug_scope!(
1451 "running_process::NativeProcess::wait_for_capture_completion_with_deadline"
1452 );
1453 if !self.config.capture {
1454 return true;
1455 }
1456 finalize_capture_completion(&self.shared, deadline)
1457 }
1458}
1459
1460#[cfg(windows)]
1466fn cancel_capture_pipe_io(handles: &Mutex<CapturePipeHandles>) {
1467 use winapi::shared::ntdef::HANDLE;
1468 use winapi::um::ioapiset::CancelIoEx;
1469 let guard = handles.lock().expect("capture pipe handles mutex poisoned");
1470 if let Some(h) = guard.stdout {
1471 unsafe {
1477 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1478 }
1479 }
1480 if let Some(h) = guard.stderr {
1481 unsafe {
1482 CancelIoEx(h as HANDLE, std::ptr::null_mut());
1483 }
1484 }
1485}
1486
1487#[cfg(unix)]
1488fn cancel_capture_pipe_io(wakers: &Mutex<UnixCaptureWakers>) {
1489 use std::os::fd::AsRawFd;
1490
1491 let guard = wakers.lock().expect("capture wakers mutex poisoned");
1492 let byte = [1_u8; 1];
1493 for writer in [&guard.stdout, &guard.stderr].into_iter().flatten() {
1494 let _ = unsafe { libc::write(writer.as_raw_fd(), byte.as_ptr().cast(), byte.len()) };
1498 }
1499}
1500
1501fn finalize_capture_completion(shared: &SharedState, deadline: Instant) -> bool {
1508 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1509 while !(guard.stdout_closed && guard.stderr_closed) {
1510 let now = Instant::now();
1511 if now >= deadline {
1512 guard.stdout_closed = true;
1513 guard.stderr_closed = true;
1514 shared.condvar.notify_all();
1515 return false;
1516 }
1517 let (next_guard, result) = shared
1518 .condvar
1519 .wait_timeout(guard, deadline - now)
1520 .expect("queue mutex poisoned");
1521 guard = next_guard;
1522 if result.timed_out() && !(guard.stdout_closed && guard.stderr_closed) {
1523 guard.stdout_closed = true;
1524 guard.stderr_closed = true;
1525 shared.condvar.notify_all();
1526 return false;
1527 }
1528 }
1529 true
1530}
1531
1532fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
1533 if lines.is_empty() {
1534 return;
1535 }
1536 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1537 for line in lines {
1538 let line_len = line.len();
1539 match stream {
1540 StreamKind::Stdout => {
1541 guard.stdout_history_bytes += line_len;
1542 guard.stdout_history.push_back(line.clone());
1543 guard.stdout_queue.push_back(line.clone());
1544 }
1545 StreamKind::Stderr => {
1546 guard.stderr_history_bytes += line_len;
1547 guard.stderr_history.push_back(line.clone());
1548 guard.stderr_queue.push_back(line.clone());
1549 }
1550 }
1551 let event = StreamEvent { stream, line };
1552 guard.combined_history_bytes += line_len;
1553 guard.combined_history.push_back(event.clone());
1554 guard.combined_queue.push_back(event);
1555 }
1556 shared.condvar.notify_all();
1557}
1558
1559fn append_raw(shared: &Arc<SharedState>, stream: StreamKind, chunk: &[u8]) {
1560 if chunk.is_empty() {
1561 return;
1562 }
1563 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
1564 match stream {
1565 StreamKind::Stdout => guard.stdout_raw.extend_from_slice(chunk),
1566 StreamKind::Stderr => guard.stderr_raw.extend_from_slice(chunk),
1567 }
1568}
1569
1570pub fn run_command(
1576 mut config: ProcessConfig,
1577 timeout: Option<Duration>,
1578) -> Result<RunOutput, ProcessError> {
1579 config.capture = true;
1580 let process = NativeProcess::new(config);
1581 process.start()?;
1582
1583 let exit_code = match process.wait(timeout) {
1584 Ok(code) => code,
1585 Err(ProcessError::Timeout) => {
1586 match process.kill() {
1587 Ok(()) | Err(ProcessError::NotRunning) => {}
1588 Err(error) => return Err(error),
1589 }
1590 return Err(ProcessError::Timeout);
1591 }
1592 Err(error) => return Err(error),
1593 };
1594
1595 Ok(RunOutput {
1596 stdout: process.captured_stdout_raw(),
1597 stderr: process.captured_stderr_raw(),
1598 exit_code,
1599 })
1600}
1601
1602pub(crate) fn shell_command(command: &str) -> Command {
1603 #[cfg(windows)]
1604 {
1605 use std::os::windows::process::CommandExt;
1606
1607 let mut cmd = Command::new("cmd");
1608 cmd.raw_arg("/D /S /C \"");
1609 cmd.raw_arg(command);
1610 cmd.raw_arg("\"");
1611 cmd
1612 }
1613 #[cfg(not(windows))]
1614 {
1615 let mut cmd = Command::new("sh");
1616 cmd.arg("-lc").arg(command);
1617 cmd
1618 }
1619}
1620
1621#[cfg(test)]
1622mod tests;