1use std::collections::VecDeque;
2use std::fs::OpenOptions;
3use std::io::Read;
4use std::io::Write;
5use std::path::PathBuf;
6use std::process::{Child, Command, Stdio};
7use std::sync::atomic::{AtomicI64, Ordering};
8use std::sync::{Arc, Condvar, Mutex};
9use std::thread;
10use std::time::{Duration, Instant};
11
12use thiserror::Error;
13
14pub mod console_detect;
15pub mod containment;
16#[cfg(feature = "originator-scan")]
17pub mod originator;
18pub mod pty;
19mod public_symbols;
20mod rust_debug;
21pub mod spawn;
22
23pub use console_detect::{monitor_console_windows, ConsoleWindowInfo};
24pub use containment::{ContainedProcessGroup, ORIGINATOR_ENV_VAR};
25#[cfg(feature = "originator-scan")]
26pub use originator::{find_processes_by_originator, OriginatorProcessInfo};
27pub use rust_debug::{render_rust_debug_traces, RustDebugScopeGuard};
28pub use spawn::{
29 spawn, spawn_daemon, spawn_daemon_with_clear_env, DaemonChild, SpawnStdio, SpawnedChild,
30 StdioSource,
31};
32
33#[macro_export]
34macro_rules! rp_rust_debug_scope {
35 ($label:expr) => {
36 let _running_process_rust_debug_scope =
37 $crate::RustDebugScopeGuard::enter($label, file!(), line!());
38 };
39}
40
41const CHILD_PID_LOG_PATH_ENV: &str = "RUNNING_PROCESS_CHILD_PID_LOG_PATH";
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum StreamKind {
45 Stdout,
46 Stderr,
47}
48
49impl StreamKind {
50 pub fn as_str(self) -> &'static str {
51 match self {
52 Self::Stdout => "stdout",
53 Self::Stderr => "stderr",
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct StreamEvent {
60 pub stream: StreamKind,
61 pub line: Vec<u8>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum ReadStatus<T> {
66 Line(T),
67 Timeout,
68 Eof,
69}
70
71#[derive(Debug, Error)]
72pub enum ProcessError {
73 #[error("process already started")]
74 AlreadyStarted,
75 #[error("process is not running")]
76 NotRunning,
77 #[error("process stdin is not available")]
78 StdinUnavailable,
79 #[error("failed to spawn process: {0}")]
80 Spawn(std::io::Error),
81 #[error("failed to read process output: {0}")]
82 Io(std::io::Error),
83 #[error("process timed out")]
84 Timeout,
85}
86
87#[derive(Debug, Clone)]
88pub enum CommandSpec {
89 Shell(String),
90 Argv(Vec<String>),
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum StdinMode {
95 Inherit,
96 Piped,
97 Null,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum StderrMode {
102 Stdout,
103 Pipe,
104}
105
106#[cfg(unix)]
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum UnixSignal {
109 Interrupt,
110 Terminate,
111 Kill,
112}
113
114#[derive(Debug, Clone)]
115pub struct ProcessConfig {
116 pub command: CommandSpec,
117 pub cwd: Option<PathBuf>,
118 pub env: Option<Vec<(String, String)>>,
119 pub capture: bool,
120 pub stderr_mode: StderrMode,
121 pub creationflags: Option<u32>,
122 pub create_process_group: bool,
123 pub stdin_mode: StdinMode,
124 pub nice: Option<i32>,
125}
126
127#[derive(Default)]
128struct QueueState {
129 stdout_queue: VecDeque<Vec<u8>>,
130 stderr_queue: VecDeque<Vec<u8>>,
131 combined_queue: VecDeque<StreamEvent>,
132 stdout_history: VecDeque<Vec<u8>>,
133 stderr_history: VecDeque<Vec<u8>>,
134 combined_history: VecDeque<StreamEvent>,
135 stdout_history_bytes: usize,
136 stderr_history_bytes: usize,
137 combined_history_bytes: usize,
138 stdout_closed: bool,
139 stderr_closed: bool,
140}
141
142const RETURNCODE_NOT_SET: i64 = i64::MIN;
144
145struct SharedState {
146 queues: Mutex<QueueState>,
147 condvar: Condvar,
148 returncode: AtomicI64,
151}
152
153#[cfg(windows)]
154struct WindowsJobHandle(usize);
155
156#[cfg(windows)]
157impl Drop for WindowsJobHandle {
158 fn drop(&mut self) {
159 unsafe {
160 winapi::um::handleapi::CloseHandle(self.0 as winapi::shared::ntdef::HANDLE);
161 }
162 }
163}
164
165struct ChildState {
166 child: Child,
167 #[cfg(windows)]
168 _job: WindowsJobHandle,
169}
170
171impl SharedState {
172 fn new(capture: bool) -> Self {
173 let queues = QueueState {
174 stdout_closed: !capture,
175 stderr_closed: !capture,
176 ..QueueState::default()
177 };
178 Self {
179 queues: Mutex::new(queues),
180 condvar: Condvar::new(),
181 returncode: AtomicI64::new(RETURNCODE_NOT_SET),
182 }
183 }
184}
185
186pub struct NativeProcess {
187 config: ProcessConfig,
188 child: Arc<Mutex<Option<ChildState>>>,
189 shared: Arc<SharedState>,
190}
191
192impl NativeProcess {
193 pub fn new(config: ProcessConfig) -> Self {
194 Self {
195 shared: Arc::new(SharedState::new(config.capture)),
196 child: Arc::new(Mutex::new(None)),
197 config,
198 }
199 }
200
201 #[inline(never)]
203 pub fn start(&self) -> Result<(), ProcessError> {
204 public_symbols::rp_native_process_start_public(self)
205 }
206
207 fn start_impl(&self) -> Result<(), ProcessError> {
208 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::start");
209 let mut guard = self.child.lock().expect("child mutex poisoned");
210 if guard.is_some() {
211 return Err(ProcessError::AlreadyStarted);
212 }
213
214 let mut command = self.build_command();
215 match self.config.stdin_mode {
216 StdinMode::Inherit => {}
217 StdinMode::Piped => {
218 command.stdin(Stdio::piped());
219 }
220 StdinMode::Null => {
221 command.stdin(Stdio::null());
222 }
223 }
224 if self.config.capture {
225 command.stdout(Stdio::piped());
226 command.stderr(Stdio::piped());
227 }
228
229 let mut child = command.spawn().map_err(ProcessError::Spawn)?;
230 log_spawned_child_pid(child.id()).map_err(ProcessError::Spawn)?;
231 #[cfg(windows)]
232 let job = public_symbols::rp_assign_child_to_windows_kill_on_close_job_public(&child)
233 .map_err(ProcessError::Spawn)?;
234 if self.config.capture {
235 let stdout = child.stdout.take().expect("stdout pipe missing");
236 let stderr = child.stderr.take().expect("stderr pipe missing");
237 self.spawn_reader(stdout, StreamKind::Stdout, StreamKind::Stdout);
238 self.spawn_reader(
239 stderr,
240 StreamKind::Stderr,
241 match self.config.stderr_mode {
242 StderrMode::Stdout => StreamKind::Stdout,
243 StderrMode::Pipe => StreamKind::Stderr,
244 },
245 );
246 }
247 *guard = Some(ChildState {
248 child,
249 #[cfg(windows)]
250 _job: job,
251 });
252 drop(guard);
253 self.spawn_exit_waiter();
254 Ok(())
255 }
256
257 fn spawn_exit_waiter(&self) {
260 let child = Arc::clone(&self.child);
261 let shared = Arc::clone(&self.shared);
262 thread::spawn(move || loop {
263 if shared.returncode.load(Ordering::Acquire) != RETURNCODE_NOT_SET {
264 return;
265 }
266 {
267 let mut guard = child.lock().expect("child mutex poisoned");
268 if let Some(child_state) = guard.as_mut() {
269 match child_state.child.try_wait() {
270 Ok(Some(status)) => {
271 let code = exit_code(status);
272 shared.returncode.store(code as i64, Ordering::Release);
273 shared.condvar.notify_all();
274 return;
275 }
276 Ok(None) => {}
277 Err(_) => return,
278 }
279 } else {
280 return;
281 }
282 }
283 thread::sleep(Duration::from_millis(10));
284 });
285 }
286
287 pub fn write_stdin(&self, data: &[u8]) -> Result<(), ProcessError> {
288 let mut guard = self.child.lock().expect("child mutex poisoned");
289 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
290 let stdin = child.stdin.as_mut().ok_or(ProcessError::StdinUnavailable)?;
291 use std::io::Write;
292 stdin.write_all(data).map_err(ProcessError::Io)?;
293 stdin.flush().map_err(ProcessError::Io)?;
294 drop(child.stdin.take());
295 Ok(())
296 }
297
298 pub fn poll(&self) -> Result<Option<i32>, ProcessError> {
299 if let Some(code) = self.returncode() {
301 return Ok(Some(code));
302 }
303 let mut guard = self.child.lock().expect("child mutex poisoned");
304 let Some(child_state) = guard.as_mut() else {
305 return Ok(self.returncode());
306 };
307 let child = &mut child_state.child;
308 let status = child.try_wait().map_err(ProcessError::Io)?;
309 if let Some(status) = status {
310 let code = exit_code(status);
311 self.set_returncode(code);
312 return Ok(Some(code));
313 }
314 Ok(None)
315 }
316
317 #[inline(never)]
319 pub fn wait(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
320 public_symbols::rp_native_process_wait_public(self, timeout)
321 }
322
323 fn wait_impl(&self, timeout: Option<Duration>) -> Result<i32, ProcessError> {
324 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::wait");
325 if self.child.lock().expect("child mutex poisoned").is_none() {
326 return self.returncode().ok_or(ProcessError::NotRunning);
327 }
328 if let Some(code) = self.returncode() {
330 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
331 return Ok(code);
332 }
333 let start = Instant::now();
334 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
335 loop {
336 let rc = self.shared.returncode.load(Ordering::Acquire);
338 if rc != RETURNCODE_NOT_SET {
339 drop(guard);
340 let code = rc as i32;
341 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
342 return Ok(code);
343 }
344 if let Some(limit) = timeout {
345 let elapsed = start.elapsed();
346 if elapsed >= limit {
347 return Err(ProcessError::Timeout);
348 }
349 let remaining = limit - elapsed;
350 let wait_time = remaining.min(Duration::from_millis(50));
352 guard = self
353 .shared
354 .condvar
355 .wait_timeout(guard, wait_time)
356 .expect("queue mutex poisoned")
357 .0;
358 } else {
359 guard = self
361 .shared
362 .condvar
363 .wait_timeout(guard, Duration::from_millis(50))
364 .expect("queue mutex poisoned")
365 .0;
366 }
367 }
368 }
369
370 #[inline(never)]
372 pub fn kill(&self) -> Result<(), ProcessError> {
373 public_symbols::rp_native_process_kill_public(self)
374 }
375
376 fn kill_impl(&self) -> Result<(), ProcessError> {
377 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::kill");
378 let mut guard = self.child.lock().expect("child mutex poisoned");
379 let child = &mut guard.as_mut().ok_or(ProcessError::NotRunning)?.child;
380 child.kill().map_err(ProcessError::Io)?;
381 let status = child.wait().map_err(ProcessError::Io)?;
382 self.set_returncode(exit_code(status));
383 Ok(())
384 }
385
386 pub fn terminate(&self) -> Result<(), ProcessError> {
387 self.kill()
388 }
389
390 #[inline(never)]
392 pub fn close(&self) -> Result<(), ProcessError> {
393 public_symbols::rp_native_process_close_public(self)
394 }
395
396 fn close_impl(&self) -> Result<(), ProcessError> {
397 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::close");
398 if self.child.lock().expect("child mutex poisoned").is_none() {
399 return Ok(());
400 }
401 if self.poll()?.is_none() {
402 self.kill()?;
403 } else {
404 public_symbols::rp_native_process_wait_for_capture_completion_public(self);
405 }
406 Ok(())
407 }
408
409 pub fn pid(&self) -> Option<u32> {
410 self.child
411 .lock()
412 .expect("child mutex poisoned")
413 .as_ref()
414 .map(|state| state.child.id())
415 }
416
417 pub fn returncode(&self) -> Option<i32> {
418 let v = self.shared.returncode.load(Ordering::Acquire);
419 if v == RETURNCODE_NOT_SET {
420 None
421 } else {
422 Some(v as i32)
423 }
424 }
425
426 pub fn has_pending_stream(&self, stream: StreamKind) -> bool {
427 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
428 return false;
429 }
430 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
431 match stream {
432 StreamKind::Stdout => !guard.stdout_queue.is_empty(),
433 StreamKind::Stderr => !guard.stderr_queue.is_empty(),
434 }
435 }
436
437 pub fn has_pending_combined(&self) -> bool {
438 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
439 !guard.combined_queue.is_empty()
440 }
441
442 pub fn drain_stream(&self, stream: StreamKind) -> Vec<Vec<u8>> {
443 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
444 return Vec::new();
445 }
446 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
447 let queue = match stream {
448 StreamKind::Stdout => &mut guard.stdout_queue,
449 StreamKind::Stderr => &mut guard.stderr_queue,
450 };
451 queue.drain(..).collect()
452 }
453
454 pub fn drain_combined(&self) -> Vec<StreamEvent> {
455 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
456 guard.combined_queue.drain(..).collect()
457 }
458
459 pub fn read_stream(
460 &self,
461 stream: StreamKind,
462 timeout: Option<Duration>,
463 ) -> ReadStatus<Vec<u8>> {
464 let deadline = timeout.map(|limit| Instant::now() + limit);
465 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
466
467 loop {
468 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
469 return ReadStatus::Eof;
470 }
471
472 let queue = match stream {
473 StreamKind::Stdout => &mut guard.stdout_queue,
474 StreamKind::Stderr => &mut guard.stderr_queue,
475 };
476 if let Some(line) = queue.pop_front() {
477 return ReadStatus::Line(line);
478 }
479
480 let closed = match stream {
481 StreamKind::Stdout => {
482 if self.config.stderr_mode == StderrMode::Stdout {
483 guard.stdout_closed && guard.stderr_closed
484 } else {
485 guard.stdout_closed
486 }
487 }
488 StreamKind::Stderr => guard.stderr_closed,
489 };
490 if closed {
491 return ReadStatus::Eof;
492 }
493
494 match deadline {
495 Some(deadline) => {
496 let now = Instant::now();
497 if now >= deadline {
498 return ReadStatus::Timeout;
499 }
500 let wait = deadline.saturating_duration_since(now);
501 let result = self
502 .shared
503 .condvar
504 .wait_timeout(guard, wait)
505 .expect("queue mutex poisoned");
506 guard = result.0;
507 if result.1.timed_out() {
508 return ReadStatus::Timeout;
509 }
510 }
511 None => {
512 guard = self
513 .shared
514 .condvar
515 .wait(guard)
516 .expect("queue mutex poisoned");
517 }
518 }
519 }
520 }
521
522 #[inline(never)]
524 pub fn read_combined(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
525 public_symbols::rp_native_process_read_combined_public(self, timeout)
526 }
527
528 fn read_combined_impl(&self, timeout: Option<Duration>) -> ReadStatus<StreamEvent> {
529 crate::rp_rust_debug_scope!("running_process_core::NativeProcess::read_combined");
530 let deadline = timeout.map(|limit| Instant::now() + limit);
531 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
532
533 loop {
534 if let Some(event) = guard.combined_queue.pop_front() {
535 return ReadStatus::Line(event);
536 }
537 if guard.stdout_closed && guard.stderr_closed {
538 return ReadStatus::Eof;
539 }
540
541 match deadline {
542 Some(deadline) => {
543 let now = Instant::now();
544 if now >= deadline {
545 return ReadStatus::Timeout;
546 }
547 let wait = deadline.saturating_duration_since(now);
548 let result = self
549 .shared
550 .condvar
551 .wait_timeout(guard, wait)
552 .expect("queue mutex poisoned");
553 guard = result.0;
554 if result.1.timed_out() {
555 return ReadStatus::Timeout;
556 }
557 }
558 None => {
559 guard = self
560 .shared
561 .condvar
562 .wait(guard)
563 .expect("queue mutex poisoned");
564 }
565 }
566 }
567 }
568
569 pub fn captured_stdout(&self) -> Vec<Vec<u8>> {
570 self.shared
571 .queues
572 .lock()
573 .expect("queue mutex poisoned")
574 .stdout_history
575 .clone()
576 .into_iter()
577 .collect()
578 }
579
580 pub fn captured_stderr(&self) -> Vec<Vec<u8>> {
581 if self.config.stderr_mode == StderrMode::Stdout {
582 return Vec::new();
583 }
584 self.shared
585 .queues
586 .lock()
587 .expect("queue mutex poisoned")
588 .stderr_history
589 .clone()
590 .into_iter()
591 .collect()
592 }
593
594 pub fn captured_combined(&self) -> Vec<StreamEvent> {
595 self.shared
596 .queues
597 .lock()
598 .expect("queue mutex poisoned")
599 .combined_history
600 .clone()
601 .into_iter()
602 .collect()
603 }
604
605 pub fn captured_stream_bytes(&self, stream: StreamKind) -> usize {
606 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
607 return 0;
608 }
609 let guard = self.shared.queues.lock().expect("queue mutex poisoned");
610 match stream {
611 StreamKind::Stdout => guard.stdout_history_bytes,
612 StreamKind::Stderr => guard.stderr_history_bytes,
613 }
614 }
615
616 pub fn captured_combined_bytes(&self) -> usize {
617 self.shared
618 .queues
619 .lock()
620 .expect("queue mutex poisoned")
621 .combined_history_bytes
622 }
623
624 pub fn clear_captured_stream(&self, stream: StreamKind) -> usize {
625 if stream == StreamKind::Stderr && self.config.stderr_mode == StderrMode::Stdout {
626 return 0;
627 }
628 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
629 match stream {
630 StreamKind::Stdout => {
631 let released = guard.stdout_history_bytes;
632 guard.stdout_history.clear();
633 guard.stdout_history_bytes = 0;
634 released
635 }
636 StreamKind::Stderr => {
637 let released = guard.stderr_history_bytes;
638 guard.stderr_history.clear();
639 guard.stderr_history_bytes = 0;
640 released
641 }
642 }
643 }
644
645 pub fn clear_captured_combined(&self) -> usize {
646 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
647 let released = guard.combined_history_bytes;
648 guard.combined_history.clear();
649 guard.combined_history_bytes = 0;
650 released
651 }
652
653 fn build_command(&self) -> Command {
654 let mut command = match &self.config.command {
655 CommandSpec::Shell(command) => shell_command(command),
656 CommandSpec::Argv(argv) => {
657 let mut command = Command::new(&argv[0]);
658 if argv.len() > 1 {
659 command.args(&argv[1..]);
660 }
661 command
662 }
663 };
664 if let Some(cwd) = &self.config.cwd {
665 command.current_dir(cwd);
666 }
667 if let Some(env) = &self.config.env {
668 command.env_clear();
669 command.envs(env.iter().map(|(k, v)| (k, v)));
670 }
671 #[cfg(windows)]
672 {
673 use std::os::windows::process::CommandExt;
674
675 let flags =
676 self.config.creationflags.unwrap_or(0) | windows_priority_flags(self.config.nice);
677 if flags != 0 {
678 command.creation_flags(flags);
679 }
680 }
681 #[cfg(unix)]
682 {
683 let create_process_group = self.config.create_process_group;
684 let nice = self.config.nice;
685
686 if create_process_group || nice.is_some() {
687 use std::os::unix::process::CommandExt;
688
689 unsafe {
690 command.pre_exec(move || {
691 if create_process_group && libc::setpgid(0, 0) == -1 {
692 return Err(std::io::Error::last_os_error());
693 }
694 if let Some(nice) = nice {
695 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
696 if result == -1 {
697 return Err(std::io::Error::last_os_error());
698 }
699 }
700 Ok(())
701 });
702 }
703 }
704 }
705 command
706 }
707
708 fn spawn_reader<R>(&self, pipe: R, source_stream: StreamKind, visible_stream: StreamKind)
709 where
710 R: Read + Send + 'static,
711 {
712 let shared = Arc::clone(&self.shared);
713 thread::spawn(move || {
714 let mut reader = pipe;
715 let mut chunk = vec![0_u8; 65536];
716 let mut pending = Vec::new();
717
718 loop {
719 match reader.read(&mut chunk) {
720 Ok(0) => break,
721 Ok(n) => {
722 let lines = feed_chunk(&mut pending, &chunk[..n]);
723 emit_lines(&shared, visible_stream, lines);
724 }
725 Err(_) => break,
726 }
727 }
728
729 if !pending.is_empty() {
730 emit_lines(&shared, visible_stream, vec![std::mem::take(&mut pending)]);
731 }
732
733 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
734 match source_stream {
735 StreamKind::Stdout => guard.stdout_closed = true,
736 StreamKind::Stderr => guard.stderr_closed = true,
737 }
738 shared.condvar.notify_all();
739 });
740 }
741
742 fn set_returncode(&self, code: i32) {
743 self.shared.returncode.store(code as i64, Ordering::Release);
744 self.shared.condvar.notify_all();
745 }
746
747 fn wait_for_capture_completion_impl(&self) {
748 crate::rp_rust_debug_scope!(
749 "running_process_core::NativeProcess::wait_for_capture_completion"
750 );
751 if !self.config.capture {
752 return;
753 }
754
755 let mut guard = self.shared.queues.lock().expect("queue mutex poisoned");
756 while !(guard.stdout_closed && guard.stderr_closed) {
757 guard = self
758 .shared
759 .condvar
760 .wait(guard)
761 .expect("queue mutex poisoned");
762 }
763 }
764}
765
766#[cfg(unix)]
767pub fn unix_set_priority(pid: u32, nice: i32) -> Result<(), std::io::Error> {
768 let result = unsafe { libc::setpriority(libc::PRIO_PROCESS, pid, nice) };
769 if result == -1 {
770 return Err(std::io::Error::last_os_error());
771 }
772 Ok(())
773}
774
775#[cfg(unix)]
776pub fn unix_signal_process(pid: u32, signal: UnixSignal) -> Result<(), std::io::Error> {
777 let result = unsafe { libc::kill(pid as i32, unix_signal_raw(signal)) };
778 if result == -1 {
779 return Err(std::io::Error::last_os_error());
780 }
781 Ok(())
782}
783
784#[cfg(unix)]
785pub fn unix_signal_process_group(pid: i32, signal: UnixSignal) -> Result<(), std::io::Error> {
786 let result = unsafe { libc::killpg(pid, unix_signal_raw(signal)) };
787 if result == -1 {
788 return Err(std::io::Error::last_os_error());
789 }
790 Ok(())
791}
792
793fn log_spawned_child_pid(pid: u32) -> Result<(), std::io::Error> {
794 let Some(path) = std::env::var_os(CHILD_PID_LOG_PATH_ENV) else {
795 return Ok(());
796 };
797
798 let mut file = OpenOptions::new().create(true).append(true).open(path)?;
799 file.write_all(format!("{pid}\n").as_bytes())?;
800 file.flush()?;
801 Ok(())
802}
803
804#[cfg(windows)]
805fn assign_child_to_windows_kill_on_close_job_impl(
806 child: &Child,
807) -> Result<WindowsJobHandle, std::io::Error> {
808 crate::rp_rust_debug_scope!("running_process_core::assign_child_to_windows_kill_on_close_job");
809 use std::mem::zeroed;
810 use std::os::windows::io::AsRawHandle;
811
812 use winapi::shared::minwindef::FALSE;
813 use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
814 use winapi::um::jobapi2::{
815 AssignProcessToJobObject, CreateJobObjectW, SetInformationJobObject,
816 };
817 use winapi::um::winnt::{
818 JobObjectExtendedLimitInformation, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
819 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
820 };
821
822 let handle = child.as_raw_handle();
823 let job = unsafe { CreateJobObjectW(std::ptr::null_mut(), std::ptr::null()) };
824 if job.is_null() || job == INVALID_HANDLE_VALUE {
825 return Err(std::io::Error::last_os_error());
826 }
827
828 let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() };
829 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
830 let ok = unsafe {
831 SetInformationJobObject(
832 job,
833 JobObjectExtendedLimitInformation,
834 (&mut info as *mut JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(),
835 std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
836 )
837 };
838 if ok == FALSE {
839 let err = std::io::Error::last_os_error();
840 unsafe { CloseHandle(job) };
841 return Err(err);
842 }
843
844 let ok = unsafe { AssignProcessToJobObject(job, handle.cast()) };
845 if ok == FALSE {
846 let err = std::io::Error::last_os_error();
847 unsafe { CloseHandle(job) };
848 return Err(err);
849 }
850
851 Ok(WindowsJobHandle(job as usize))
852}
853
854fn feed_chunk(pending: &mut Vec<u8>, chunk: &[u8]) -> Vec<Vec<u8>> {
855 let mut lines = Vec::new();
856 let mut start = 0;
857 let mut index = 0;
858
859 while index < chunk.len() {
860 if chunk[index] == b'\n' {
861 let end = if index > start && chunk[index - 1] == b'\r' {
862 index - 1
863 } else {
864 index
865 };
866 pending.extend_from_slice(&chunk[start..end]);
867 if !pending.is_empty() {
868 lines.push(std::mem::take(pending));
869 }
870 start = index + 1;
871 }
872 index += 1;
873 }
874
875 pending.extend_from_slice(&chunk[start..]);
876 lines
877}
878
879fn emit_lines(shared: &Arc<SharedState>, stream: StreamKind, lines: Vec<Vec<u8>>) {
880 if lines.is_empty() {
881 return;
882 }
883 let mut guard = shared.queues.lock().expect("queue mutex poisoned");
884 for line in lines {
885 let line_len = line.len();
886 match stream {
887 StreamKind::Stdout => {
888 guard.stdout_history_bytes += line_len;
889 guard.stdout_history.push_back(line.clone());
890 guard.stdout_queue.push_back(line.clone());
891 }
892 StreamKind::Stderr => {
893 guard.stderr_history_bytes += line_len;
894 guard.stderr_history.push_back(line.clone());
895 guard.stderr_queue.push_back(line.clone());
896 }
897 }
898 let event = StreamEvent { stream, line };
899 guard.combined_history_bytes += line_len;
900 guard.combined_history.push_back(event.clone());
901 guard.combined_queue.push_back(event);
902 }
903 shared.condvar.notify_all();
904}
905
906fn shell_command(command: &str) -> Command {
907 #[cfg(windows)]
908 {
909 use std::os::windows::process::CommandExt;
910
911 let mut cmd = Command::new("cmd");
912 cmd.raw_arg("/D /S /C \"");
913 cmd.raw_arg(command);
914 cmd.raw_arg("\"");
915 cmd
916 }
917 #[cfg(not(windows))]
918 {
919 let mut cmd = Command::new("sh");
920 cmd.arg("-lc").arg(command);
921 cmd
922 }
923}
924
925fn exit_code(status: std::process::ExitStatus) -> i32 {
926 #[cfg(unix)]
927 {
928 use std::os::unix::process::ExitStatusExt;
929 status
930 .code()
931 .unwrap_or_else(|| -status.signal().unwrap_or(1))
932 }
933 #[cfg(not(unix))]
934 {
935 status.code().unwrap_or(1)
936 }
937}
938
939#[cfg(unix)]
940fn unix_signal_raw(signal: UnixSignal) -> i32 {
941 match signal {
942 UnixSignal::Interrupt => libc::SIGINT,
943 UnixSignal::Terminate => libc::SIGTERM,
944 UnixSignal::Kill => libc::SIGKILL,
945 }
946}
947
948#[cfg(windows)]
949fn windows_priority_flags(nice: Option<i32>) -> u32 {
950 const IDLE_PRIORITY_CLASS: u32 = 0x0000_0040;
951 const BELOW_NORMAL_PRIORITY_CLASS: u32 = 0x0000_4000;
952 const ABOVE_NORMAL_PRIORITY_CLASS: u32 = 0x0000_8000;
953 const HIGH_PRIORITY_CLASS: u32 = 0x0000_0080;
954
955 match nice {
956 Some(value) if value >= 15 => IDLE_PRIORITY_CLASS,
957 Some(value) if value >= 1 => BELOW_NORMAL_PRIORITY_CLASS,
958 Some(value) if value <= -15 => HIGH_PRIORITY_CLASS,
959 Some(value) if value <= -1 => ABOVE_NORMAL_PRIORITY_CLASS,
960 _ => 0,
961 }
962}
963#[cfg(test)]
964mod tests;