openssh/native_mux_impl/
stdio.rs

1use crate::{stdio::StdioImpl, Error, Stdio};
2
3use std::{
4    fs::{File, OpenOptions},
5    io,
6    os::unix::io::{AsRawFd, OwnedFd, RawFd},
7};
8
9use libc::{c_int, fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
10use once_cell::sync::OnceCell;
11use tokio::net::unix::pipe::{pipe, Receiver as PipeReader, Sender as PipeWriter};
12
13fn create_pipe() -> Result<(PipeReader, PipeWriter), Error> {
14    pipe().map_err(Error::ChildIo).map(|(w, r)| (r, w))
15}
16
17/// Open "/dev/null" with RW.
18fn get_null_fd() -> Result<RawFd, Error> {
19    static NULL_FD: OnceCell<File> = OnceCell::new();
20    let res = NULL_FD.get_or_try_init(|| {
21        OpenOptions::new()
22            .read(true)
23            .write(true)
24            .open("/dev/null")
25            .map_err(Error::ChildIo)
26    });
27
28    res.map(AsRawFd::as_raw_fd)
29}
30
31pub(crate) enum Fd {
32    Owned(OwnedFd),
33    Borrowed(RawFd),
34    Null,
35}
36
37fn cvt(ret: c_int) -> io::Result<c_int> {
38    if ret == -1 {
39        Err(io::Error::last_os_error())
40    } else {
41        Ok(ret)
42    }
43}
44
45fn set_blocking_inner(fd: RawFd) -> io::Result<()> {
46    let flags = cvt(unsafe { fcntl(fd, F_GETFL) })?;
47    cvt(unsafe { fcntl(fd, F_SETFL, flags & (!O_NONBLOCK)) })?;
48
49    Ok(())
50}
51
52fn set_blocking(fd: RawFd) -> Result<(), Error> {
53    set_blocking_inner(fd).map_err(Error::ChildIo)
54}
55
56impl Fd {
57    pub(crate) fn as_raw_fd_or_null_fd(&self) -> Result<RawFd, Error> {
58        use Fd::*;
59
60        match self {
61            Owned(owned_fd) => Ok(owned_fd.as_raw_fd()),
62            Borrowed(rawfd) => Ok(*rawfd),
63            Null => get_null_fd(),
64        }
65    }
66}
67
68impl TryFrom<PipeReader> for Fd {
69    type Error = Error;
70
71    fn try_from(pipe_reader: PipeReader) -> Result<Self, Error> {
72        pipe_reader
73            .into_blocking_fd()
74            .map_err(Error::ChildIo)
75            .map(Fd::Owned)
76    }
77}
78
79impl TryFrom<PipeWriter> for Fd {
80    type Error = Error;
81
82    fn try_from(pipe_writer: PipeWriter) -> Result<Self, Error> {
83        pipe_writer
84            .into_blocking_fd()
85            .map_err(Error::ChildIo)
86            .map(Fd::Owned)
87    }
88}
89
90impl Stdio {
91    pub(crate) fn to_stdin(&self) -> Result<(Fd, Option<ChildStdin>), Error> {
92        match &self.0 {
93            StdioImpl::Inherit => Ok((Fd::Borrowed(io::stdin().as_raw_fd()), None)),
94            StdioImpl::Null => Ok((Fd::Null, None)),
95            StdioImpl::Pipe => {
96                let (read, write) = create_pipe()?;
97                Ok((read.try_into()?, Some(write)))
98            }
99            StdioImpl::Fd(fd) => {
100                let raw_fd = fd.as_raw_fd();
101                set_blocking(raw_fd)?;
102                Ok((Fd::Borrowed(raw_fd), None))
103            }
104        }
105    }
106
107    fn to_output(
108        &self,
109        get_inherit_rawfd: fn() -> RawFd,
110    ) -> Result<(Fd, Option<PipeReader>), Error> {
111        match &self.0 {
112            StdioImpl::Inherit => Ok((Fd::Borrowed(get_inherit_rawfd()), None)),
113            StdioImpl::Null => Ok((Fd::Null, None)),
114            StdioImpl::Pipe => {
115                let (read, write) = create_pipe()?;
116                Ok((write.try_into()?, Some(read)))
117            }
118            StdioImpl::Fd(fd) => {
119                let raw_fd = fd.as_raw_fd();
120                set_blocking(raw_fd)?;
121                Ok((Fd::Borrowed(raw_fd), None))
122            }
123        }
124    }
125
126    pub(crate) fn to_stdout(&self) -> Result<(Fd, Option<PipeReader>), Error> {
127        self.to_output(|| io::stdout().as_raw_fd())
128    }
129
130    pub(crate) fn to_stderr(&self) -> Result<(Fd, Option<PipeReader>), Error> {
131        self.to_output(|| io::stderr().as_raw_fd())
132    }
133}
134
135pub(crate) type ChildStdin = PipeWriter;
136pub(crate) type ChildStdout = PipeReader;
137pub(crate) type ChildStderr = PipeReader;