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, 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/// 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        1.. => {
60            // Drop copy of write end, then read PID from pipe
61            drop(write);
62            let pid = read_from_pipe(read).await;
63            // wait to prevent zombie
64            _ = rustix::process::wait(rustix::process::WaitOptions::empty());
65            pid
66        }
67
68        // Child process
69        0 => {
70            let _res = rustix::process::setsid();
71            if let Ok(child) = command.spawn() {
72                // Write PID to pipe
73                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}