1#[cfg(all(feature = "smol", not(feature = "tokio")))]
5use smol::io::AsyncReadExt;
6use std::io;
7use std::os::fd::OwnedFd;
8use std::process::{Command, Stdio, exit};
9#[cfg(feature = "tokio")]
10use tokio::io::AsyncReadExt;
11
12async fn read_from_pipe(read: OwnedFd) -> Option<u32> {
13 #[cfg(feature = "tokio")]
14 {
15 let mut read = tokio::net::unix::pipe::Receiver::from_owned_fd(read).unwrap();
16 return read.read_u32().await.ok();
17 }
18
19 #[cfg(all(feature = "smol", not(feature = "tokio")))]
20 {
21 let mut read = smol::Async::new(std::fs::File::from(read)).unwrap();
22 let mut bytes = [0; 4];
23 read.read_exact(&mut bytes).await.ok()?;
24 return Some(u32::from_be_bytes(bytes));
25 }
26
27 #[cfg(not(any(feature = "tokio", feature = "smol")))]
28 {
29 use rustix::fd::AsFd;
30 let mut bytes = [0u8; 4];
31 rustix::io::read(&read, &mut bytes).ok()?;
32 return Some(u32::from_be_bytes(bytes));
33 }
34}
35
36#[cold]
38pub async fn spawn(mut command: Command) -> Option<u32> {
39 command
41 .stdin(Stdio::null())
42 .stdout(Stdio::null())
43 .stderr(Stdio::null());
44
45 #[cfg(all(unix, not(target_os = "macos")))]
47 let Ok((read, write)) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC) else {
48 return None;
49 };
50
51 #[cfg(target_os = "macos")]
53 let Ok((read, write)) = rustix::pipe::pipe() else {
54 return None;
55 };
56
57 match unsafe { libc::fork() } {
58 1.. => {
60 drop(write);
62 let pid = read_from_pipe(read).await;
63 _ = rustix::process::wait(rustix::process::WaitOptions::empty());
65 pid
66 }
67
68 0 => {
70 let _res = rustix::process::setsid();
71 if let Ok(child) = command.spawn() {
72 let _ = rustix::io::write(write, &child.id().to_be_bytes());
74 }
75
76 exit(0)
77 }
78
79 ..=-1 => {
80 println!(
81 "failed to fork and spawn command: {}",
82 io::Error::last_os_error()
83 );
84
85 None
86 }
87 }
88}