Skip to main content

cosmic/
process.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4#[cfg(all(feature = "smol", not(feature = "tokio")))]
5use smol::io::AsyncReadExt;
6use std::io;
7use std::os::fd::OwnedFd;
8use std::process::{Command, Stdio};
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/// Performs a double fork with setsid to spawn and detach a command.
37#[cold]
38pub async fn spawn(mut command: Command) -> Option<u32> {
39    // NOTE: Windows platform is not supported
40    command
41        .stdin(Stdio::null())
42        .stdout(Stdio::null())
43        .stderr(Stdio::null());
44
45    // Handle Linux
46    #[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    // Handle macOS
52    #[cfg(target_os = "macos")]
53    let Ok((read, write)) = rustix::pipe::pipe() else {
54        return None;
55    };
56
57    match unsafe { libc::fork() } {
58        // Parent process
59        fork_pid @ 1..=i32::MAX => {
60            // Drop copy of write end, then read PID from pipe
61            drop(write);
62            let pid = read_from_pipe(read).await;
63            _ = rustix::process::waitpid(
64                rustix::process::Pid::from_raw(fork_pid),
65                rustix::process::WaitOptions::empty(),
66            );
67            pid
68        }
69
70        // Child process
71        0 => {
72            let _res = rustix::process::setsid();
73            let exit_status = if let Ok(child) = command.spawn() {
74                // Write PID to pipe
75                let _ = rustix::io::write(write, &child.id().to_be_bytes());
76                0
77            } else {
78                1
79            };
80
81            // # Safety
82            // Required for child fork to exit without affecting the parent.
83            unsafe {
84                libc::_exit(exit_status);
85            }
86        }
87
88        ..=-1 => {
89            println!(
90                "failed to fork and spawn command: {}",
91                io::Error::last_os_error()
92            );
93
94            None
95        }
96    }
97}