char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somrearrange libs into lib and extensions into extsf4a0f5b

main
14.8 KiB459 linesraw
1use std::ffi::CString;
2use std::io;
3use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
4use std::os::unix::ffi::OsStrExt;
5use std::os::unix::fs::PermissionsExt;
6use std::os::unix::process::ExitStatusExt;
7use std::path::PathBuf;
8use std::pin::Pin;
9use std::process::ExitStatus;
10use std::task::{Context, Poll};
11
12use crate::Launch;
13use tokio::io::unix::AsyncFd;
14use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
15
16#[cfg(target_os = "linux")]
17#[path = "linux.rs"]
18mod os;
19#[cfg(target_os = "macos")]
20#[path = "macos.rs"]
21mod os;
22
23/// A descriptor handed to the child at its declared number.
24pub type Endpoint = OwnedFd;
25/// The terminal master, kept for resizing.
26pub type Window = OwnedFd;
27/// Leaves room above every requested child descriptor for the spawn
28/// handshake's own descriptors.
29pub const MAX_DESCRIPTOR: u32 = (i32::MAX - 32) as u32;
30
31use crate::signals::signal_number;
32pub use crate::signals::{exit_signal, known_signal, validate_signal};
33
34fn reserve(fd: RawFd, minimum: RawFd) -> io::Result<OwnedFd> {
35    let result = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, minimum) };
36    if result < 0 {
37        Err(io::Error::last_os_error())
38    } else {
39        Ok(unsafe { OwnedFd::from_raw_fd(result) })
40    }
41}
42
43/// Returns the parent's end and the child's end of a new pipe.
44pub fn pipe(parent_writes: bool) -> io::Result<(Pipe, Endpoint)> {
45    let (read, write) = os::pipe()?;
46    Ok(if parent_writes {
47        (Pipe::new(write)?, read)
48    } else {
49        (Pipe::new(read)?, write)
50    })
51}
52
53pub fn null(read: bool) -> io::Result<Endpoint> {
54    Ok(std::fs::OpenOptions::new()
55        .read(read)
56        .write(!read)
57        .open("/dev/null")?
58        .into())
59}
60
61pub fn inherit(fd: u32) -> io::Result<Endpoint> {
62    reserve(fd as RawFd, 3)
63}
64
65pub fn duplicate(endpoint: &Endpoint) -> io::Result<Endpoint> {
66    reserve(endpoint.as_raw_fd(), 3)
67}
68
69pub struct Pty {
70    pub reader: Pipe,
71    pub writer: Pipe,
72    pub endpoints: [Endpoint; 3],
73    pub window: Window,
74}
75
76pub fn pty(columns: u16, rows: u16) -> io::Result<Pty> {
77    let (master, slave) = os::pty()?;
78    resize(&master, columns, rows)?;
79    Ok(Pty {
80        reader: Pipe::new(reserve(master.as_raw_fd(), 3)?)?,
81        writer: Pipe::new(reserve(master.as_raw_fd(), 3)?)?,
82        endpoints: [
83            reserve(slave.as_raw_fd(), 3)?,
84            reserve(slave.as_raw_fd(), 3)?,
85            slave,
86        ],
87        window: master,
88    })
89}
90
91pub fn resize(fd: &Window, columns: u16, rows: u16) -> io::Result<()> {
92    let size = libc::winsize {
93        ws_row: rows,
94        ws_col: columns,
95        ws_xpixel: 0,
96        ws_ypixel: 0,
97    };
98    if unsafe { libc::ioctl(fd.as_raw_fd(), libc::TIOCSWINSZ, &size) } < 0 {
99        Err(io::Error::last_os_error())
100    } else {
101        Ok(())
102    }
103}
104
105pub struct Pipe(AsyncFd<OwnedFd>);
106impl Pipe {
107    fn new(fd: OwnedFd) -> io::Result<Self> {
108        let flags = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETFL) };
109        if flags < 0
110            || unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0
111        {
112            return Err(io::Error::last_os_error());
113        }
114        Ok(Self(AsyncFd::new(fd)?))
115    }
116}
117impl AsyncRead for Pipe {
118    fn poll_read(
119        self: Pin<&mut Self>,
120        cx: &mut Context<'_>,
121        buffer: &mut ReadBuf<'_>,
122    ) -> Poll<io::Result<()>> {
123        loop {
124            let mut ready = std::task::ready!(self.0.poll_read_ready(cx))?;
125            match ready.try_io(|inner| {
126                let bytes = buffer.initialize_unfilled();
127                let count = unsafe {
128                    libc::read(
129                        inner.get_ref().as_raw_fd(),
130                        bytes.as_mut_ptr().cast(),
131                        bytes.len(),
132                    )
133                };
134                if count < 0 {
135                    let error = io::Error::last_os_error();
136                    // A PTY reports EIO after its last slave closes.
137                    if error.raw_os_error() == Some(libc::EIO) {
138                        return Ok(0);
139                    }
140                    Err(error)
141                } else {
142                    Ok(count as usize)
143                }
144            }) {
145                Ok(Ok(count)) => {
146                    buffer.advance(count);
147                    return Poll::Ready(Ok(()));
148                }
149                Ok(Err(error)) if error.kind() == io::ErrorKind::Interrupted => continue,
150                Ok(Err(error)) => return Poll::Ready(Err(error)),
151                Err(_) => continue,
152            }
153        }
154    }
155}
156impl AsyncWrite for Pipe {
157    fn poll_write(
158        self: Pin<&mut Self>,
159        cx: &mut Context<'_>,
160        bytes: &[u8],
161    ) -> Poll<io::Result<usize>> {
162        loop {
163            let mut ready = std::task::ready!(self.0.poll_write_ready(cx))?;
164            match ready.try_io(|inner| {
165                let count = unsafe {
166                    libc::write(
167                        inner.get_ref().as_raw_fd(),
168                        bytes.as_ptr().cast(),
169                        bytes.len(),
170                    )
171                };
172                if count < 0 {
173                    Err(io::Error::last_os_error())
174                } else {
175                    Ok(count as usize)
176                }
177            }) {
178                Ok(Err(error)) if error.kind() == io::ErrorKind::Interrupted => continue,
179                Ok(result) => return Poll::Ready(result),
180                Err(_) => continue,
181            }
182        }
183    }
184    fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
185        Poll::Ready(Ok(()))
186    }
187    fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
188        Poll::Ready(Ok(()))
189    }
190}
191
192pub fn is_executable(metadata: &std::fs::Metadata) -> bool {
193    metadata.is_file() && metadata.permissions().mode() & 0o111 != 0
194}
195
196/// Unix runs exactly the named entry.
197pub fn expand(path: PathBuf) -> Vec<PathBuf> {
198    vec![path]
199}
200
201pub use os::Executable;
202
203/// Whether this process can use a terminal: it has a controlling terminal
204/// and leads its foreground group.
205pub fn controlling_foreground() -> bool {
206    let path = b"/dev/tty\0";
207    let fd = unsafe { libc::open(path.as_ptr().cast(), libc::O_RDONLY | libc::O_CLOEXEC) };
208    if fd < 0 {
209        return false;
210    }
211    let foreground = unsafe { libc::tcgetpgrp(fd) };
212    unsafe {
213        libc::close(fd);
214    }
215    foreground >= 0 && foreground == unsafe { libc::getpgrp() }
216}
217
218struct Exec {
219    executable: Executable,
220    /// argv[0] is the authorized path.
221    _strings: Vec<CString>,
222    argv: Vec<usize>,
223    env: Vec<usize>,
224}
225impl Exec {
226    fn execute(&self) -> io::Error {
227        self.executable
228            .execute(self.argv.as_ptr().cast(), self.env.as_ptr().cast())
229    }
230}
231
232pub struct Child {
233    pid: u32,
234    group: bool,
235    reaped: bool,
236}
237impl Child {
238    pub fn id(&self) -> u32 {
239        self.pid
240    }
241    pub fn wait(&mut self) -> io::Result<ExitStatus> {
242        loop {
243            let mut status = 0;
244            if unsafe { libc::waitpid(self.pid as i32, &mut status, 0) } >= 0 {
245                self.reaped = true;
246                return Ok(ExitStatus::from_raw(status));
247            }
248            let error = io::Error::last_os_error();
249            if error.kind() != io::ErrorKind::Interrupted {
250                return Err(error);
251            }
252        }
253    }
254    /// Observes exit without reaping, so the PID (and with it the process
255    /// group) cannot be reused before group cleanup.
256    pub fn observe_exit(&self) -> io::Result<Option<ExitStatus>> {
257        let mut info = std::mem::MaybeUninit::<libc::siginfo_t>::zeroed();
258        let result = unsafe {
259            libc::waitid(
260                libc::P_PID,
261                self.pid as libc::id_t,
262                info.as_mut_ptr(),
263                libc::WEXITED | libc::WNOHANG | libc::WNOWAIT,
264            )
265        };
266        if result < 0 {
267            return Err(io::Error::last_os_error());
268        }
269        let info = unsafe { info.assume_init() };
270        if unsafe { info.si_pid() } == 0 {
271            return Ok(None);
272        }
273        let status = unsafe { info.si_status() };
274        // Darwin's waitid has reported stopped children as waitable even
275        // without WSTOPPED (golang/go#19314).
276        Ok(match info.si_code {
277            libc::CLD_EXITED => Some(ExitStatus::from_raw(status << 8)),
278            libc::CLD_KILLED => Some(ExitStatus::from_raw(status)),
279            libc::CLD_DUMPED => Some(ExitStatus::from_raw(status | 0x80)),
280            _ => None,
281        })
282    }
283    pub fn signal(&self, group: bool, name: &str) -> io::Result<bool> {
284        self.send(group, signal_number(name)?)
285    }
286    fn send(&self, group: bool, number: libc::c_int) -> io::Result<bool> {
287        if self.reaped {
288            return Ok(false);
289        }
290        crate::signals::send(self.pid, group, number)
291    }
292}
293impl Drop for Child {
294    fn drop(&mut self) {
295        // Only reachable when the spawn handshake failed; the normal path reaps
296        // in supervision. Reap blocking so a failed spawn never leaves a zombie.
297        if !self.reaped {
298            let _ = self.send(false, libc::SIGKILL);
299            if self.group {
300                let _ = self.send(true, libc::SIGKILL);
301            }
302            let mut status = 0;
303            loop {
304                if unsafe { libc::waitpid(self.pid as i32, &mut status, 0) } >= 0 {
305                    break;
306                }
307                if io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
308                    break;
309                }
310            }
311        }
312    }
313}
314
315pub(crate) fn spawn(launch: Launch) -> io::Result<Child> {
316    use std::io::Read;
317    let Launch {
318        executable,
319        program,
320        args,
321        env,
322        cwd,
323        group,
324        join,
325        terminal,
326        endpoints,
327    } = launch;
328    let pgid = join.map(|group| group.pgid()).transpose()?.unwrap_or(0);
329    // Every descriptor the handshake holds sits above all requested targets.
330    let minimum = endpoints
331        .iter()
332        .map(|(fd, _)| *fd as RawFd + 16)
333        .max()
334        .unwrap_or(0)
335        .max(64);
336    let pointers = |strings: &[CString]| {
337        strings
338            .iter()
339            .map(|value| value.as_ptr() as usize)
340            .chain([0])
341            .collect()
342    };
343    let argv = [CString::new(program.as_os_str().as_bytes())]
344        .into_iter()
345        .chain(args.iter().map(|arg| CString::new(arg.as_bytes())))
346        .collect::<Result<Vec<_>, _>>()?;
347    let env = env
348        .iter()
349        .map(|(name, value)| CString::new([name.as_bytes(), b"=", value.as_bytes()].concat()))
350        .collect::<Result<Vec<_>, _>>()?;
351    let exec = Exec {
352        executable: executable.reserve(minimum)?,
353        argv: pointers(&argv),
354        env: pointers(&env),
355        _strings: argv.into_iter().chain(env).collect(),
356    };
357    let entries = endpoints
358        .into_iter()
359        .map(|(target, fd)| Ok((reserve(fd.as_raw_fd(), minimum)?, target as RawFd)))
360        .collect::<io::Result<Vec<_>>>()?;
361    let cwd = CString::new(cwd.as_os_str().as_bytes())?;
362    let (read_end, original_writer) = os::pipe()?;
363    // The exec-error pipe must not collide with a requested child descriptor.
364    let write_end = reserve(original_writer.as_raw_fd(), minimum)?;
365    drop(original_writer);
366    let mut descriptors = os::Descriptors::new()?;
367    let owner = os::Owner::new();
368    let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
369    action.sa_sigaction = libc::SIG_DFL;
370    let mut mask: libc::sigset_t = unsafe { std::mem::zeroed() };
371    unsafe {
372        libc::sigemptyset(&mut mask);
373    }
374    let pid = unsafe { libc::fork() };
375    if pid < 0 {
376        return Err(io::Error::last_os_error());
377    }
378    if pid == 0 {
379        // All allocation and CString pointer construction precedes fork.
380        let result = (|| -> io::Result<()> {
381            unsafe {
382                libc::close(read_end.as_raw_fd());
383                if libc::sigaction(libc::SIGPIPE, &action, std::ptr::null_mut()) < 0
384                    || libc::sigprocmask(libc::SIG_SETMASK, &mask, std::ptr::null_mut()) < 0
385                    || libc::chdir(cwd.as_ptr()) < 0
386                {
387                    return Err(io::Error::last_os_error());
388                }
389                // Caught handlers reset on exec but ignores persist:
390                // re-apply the startup ignores the supervisor or listener
391                // feeds may have taken over, so children keep the fate
392                // they were launched with. Reads the snapshot only, so no
393                // allocation or locking happens after fork.
394                for number in [
395                    libc::SIGINT,
396                    libc::SIGTERM,
397                    libc::SIGHUP,
398                    libc::SIGUSR1,
399                    libc::SIGUSR2,
400                    libc::SIGWINCH,
401                ] {
402                    if crate::signals::originally_ignored(number)
403                        && libc::signal(number, libc::SIG_IGN) == libc::SIG_ERR
404                    {
405                        return Err(io::Error::last_os_error());
406                    }
407                }
408                if terminal {
409                    if libc::setsid() < 0 {
410                        return Err(io::Error::last_os_error());
411                    }
412                } else if (group || join.is_some()) && libc::setpgid(0, pgid as i32) < 0 {
413                    return Err(io::Error::last_os_error());
414                }
415                owner.bind()?;
416                for (source, target) in &entries {
417                    if libc::dup2(source.as_raw_fd(), *target) < 0 {
418                        return Err(io::Error::last_os_error());
419                    }
420                }
421                descriptors.restrict()?;
422                for (_, target) in &entries {
423                    if *target >= 3 && libc::fcntl(*target, libc::F_SETFD, 0) < 0 {
424                        return Err(io::Error::last_os_error());
425                    }
426                }
427                if terminal && libc::ioctl(0, libc::TIOCSCTTY as _, 0) < 0 {
428                    return Err(io::Error::last_os_error());
429                }
430            }
431            Err(exec.execute())
432        })();
433        let error = result.unwrap_err().raw_os_error().unwrap_or(libc::EIO);
434        unsafe {
435            libc::write(
436                write_end.as_raw_fd(),
437                (&error as *const i32).cast(),
438                std::mem::size_of::<i32>(),
439            );
440            libc::_exit(127);
441        }
442    }
443    drop(write_end);
444    let mut child = Child {
445        pid: pid as u32,
446        group,
447        reaped: false,
448    };
449    let mut read_end = std::fs::File::from(read_end);
450    let mut error = [0; 4];
451    match read_end.read(&mut error)? {
452        0 => Ok(child),
453        4 => {
454            child.wait()?;
455            Err(io::Error::from_raw_os_error(i32::from_ne_bytes(error)))
456        }
457        _ => Err(io::Error::other("Invalid subprocess exec handshake")),
458    }
459}