openssh/
child.rs

1use super::{ChildStderr, ChildStdin, ChildStdout, Error};
2
3use std::io;
4use std::process::{ExitStatus, Output};
5
6use tokio::io::AsyncReadExt;
7use tokio::try_join;
8
9#[derive(Debug)]
10pub(crate) enum RemoteChildImp {
11    #[cfg(feature = "process-mux")]
12    ProcessImpl(super::process_impl::RemoteChild),
13
14    #[cfg(feature = "native-mux")]
15    NativeMuxImpl(super::native_mux_impl::RemoteChild),
16}
17#[cfg(feature = "process-mux")]
18impl From<super::process_impl::RemoteChild> for RemoteChildImp {
19    fn from(imp: super::process_impl::RemoteChild) -> Self {
20        RemoteChildImp::ProcessImpl(imp)
21    }
22}
23
24#[cfg(feature = "native-mux")]
25impl From<super::native_mux_impl::RemoteChild> for RemoteChildImp {
26    fn from(imp: super::native_mux_impl::RemoteChild) -> Self {
27        RemoteChildImp::NativeMuxImpl(imp)
28    }
29}
30
31macro_rules! delegate {
32    ($impl:expr, $var:ident, $then:block) => {{
33        match $impl {
34            #[cfg(feature = "process-mux")]
35            RemoteChildImp::ProcessImpl($var) => $then,
36
37            #[cfg(feature = "native-mux")]
38            RemoteChildImp::NativeMuxImpl($var) => $then,
39        }
40    }};
41}
42
43/// Representation of a running or exited remote child process.
44///
45/// This structure is used to represent and manage remote child
46/// processes. A remote child process is created via the
47/// [`OwningCommand`](crate::OwningCommand) struct through
48/// [`Session::command`](crate::Session::command) or one of its
49/// variants, which configures the spawning process and can itself be
50/// constructed using a builder-style interface.
51///
52/// Calling [`wait`](Child::wait) (or other functions that wrap around it) will make the
53/// parent process wait until the child has actually exited before continuing.
54///
55/// Unlike [`std::process::Child`], `Child` *does* implement [`Drop`], and will terminate the
56/// local `ssh` process corresponding to the remote process when it goes out of scope. Note that
57/// this does _not_ terminate the remote process. If you want to do that, you will need to kill it
58/// yourself by executing a remote command like `pkill` to kill it on the remote side.
59///
60/// As a result, `Child` cannot expose `stdin`, `stdout`, and `stderr` as fields for
61/// split-borrows like [`std::process::Child`] does. Instead, it exposes
62/// [`stdin`](Child::stdin), [`stdout`](Child::stdout),
63/// and [`stderr`](Child::stderr) as methods. Callers can call `.take()` to get the same
64/// effect as a split borrow and use multiple streams concurrently. Note that for the streams to be
65/// available,`Stdio::piped()` should be passed to the corresponding method on
66/// [`OwningCommand`](crate::OwningCommand).
67///
68/// NOTE that once `Child` is dropped, any data written to `stdin` will not be sent to the
69/// remote process and `stdout` and `stderr` will yield EOF immediately.
70///
71/// ```rust,no_run
72/// # async fn foo() {
73/// # let child: openssh::RemoteChild<'static> = unimplemented!();
74/// let stdin = child.stdin().take().unwrap();
75/// let stdout = child.stdout().take().unwrap();
76/// tokio::io::copy(&mut stdout, &mut stdin).await;
77/// # }
78/// ```
79#[derive(Debug)]
80pub struct Child<S> {
81    session: S,
82    imp: RemoteChildImp,
83
84    stdin: Option<ChildStdin>,
85    stdout: Option<ChildStdout>,
86    stderr: Option<ChildStderr>,
87}
88
89impl<S> Child<S> {
90    pub(crate) fn new(
91        session: S,
92        (imp, stdin, stdout, stderr): (
93            RemoteChildImp,
94            Option<ChildStdin>,
95            Option<ChildStdout>,
96            Option<ChildStderr>,
97        ),
98    ) -> Self {
99        Self {
100            session,
101            stdin,
102            stdout,
103            stderr,
104            imp,
105        }
106    }
107
108    /// Disconnect from this given remote child process.
109    ///
110    /// Note that disconnecting does _not_ kill the remote process, it merely kills the local
111    /// handle to that remote process.
112    pub async fn disconnect(self) -> io::Result<()> {
113        delegate!(self.imp, imp, { imp.disconnect().await })
114    }
115
116    /// Waits for the remote child to exit completely, returning the status that it exited with.
117    ///
118    /// This function will continue to have the same return value after it has been called at least
119    /// once.
120    ///
121    /// The stdin handle to the child process, if any, will be closed before waiting. This helps
122    /// avoid deadlock: it ensures that the child does not block waiting for input from the parent,
123    /// while the parent waits for the child to exit.
124    pub async fn wait(mut self) -> Result<ExitStatus, Error> {
125        // Close stdin so that if the remote process is reading stdin,
126        // it would return EOF and the remote process can exit.
127        self.stdin().take();
128
129        delegate!(self.imp, imp, { imp.wait().await })
130    }
131
132    /// Simultaneously waits for the remote child to exit and collect all remaining output on the
133    /// stdout/stderr handles, returning an `Output` instance.
134    ///
135    /// The stdin handle to the child process, if any, will be closed before waiting. This helps
136    /// avoid deadlock: it ensures that the child does not block waiting for input from the parent,
137    /// while the parent waits for the child to exit.
138    ///
139    /// By default, stdin, stdout and stderr are inherited from the parent. In order to capture the
140    /// output into this `Result<Output>` it is necessary to create new pipes between parent and
141    /// child. Use `stdout(Stdio::piped())` or `stderr(Stdio::piped())`, respectively.
142    pub async fn wait_with_output(mut self) -> Result<Output, Error> {
143        self.stdin().take();
144
145        let child_stdout = self.stdout.take();
146        let stdout_read = async move {
147            let mut stdout = Vec::new();
148
149            if let Some(mut child_stdout) = child_stdout {
150                child_stdout
151                    .read_to_end(&mut stdout)
152                    .await
153                    .map_err(Error::ChildIo)?;
154            }
155
156            Ok::<_, Error>(stdout)
157        };
158
159        let child_stderr = self.stderr.take();
160        let stderr_read = async move {
161            let mut stderr = Vec::new();
162
163            if let Some(mut child_stderr) = child_stderr {
164                child_stderr
165                    .read_to_end(&mut stderr)
166                    .await
167                    .map_err(Error::ChildIo)?;
168            }
169
170            Ok::<_, Error>(stderr)
171        };
172
173        // Execute them concurrently to avoid the pipe buffer being filled up
174        // and cause the remote process to block forever.
175        let (stdout, stderr) = try_join!(stdout_read, stderr_read)?;
176        Ok(Output {
177            // The self.wait() future terminates the stdout and stderr futures
178            // when it resolves, even if there may still be more data arriving
179            // from the server.
180            //
181            // Therefore, we wait for them first, and only once they're complete
182            // do we wait for the process to have terminated.
183            status: self.wait().await?,
184            stdout,
185            stderr,
186        })
187    }
188
189    /// Access the handle for reading from the remote child's standard input (stdin), if requested.
190    pub fn stdin(&mut self) -> &mut Option<ChildStdin> {
191        &mut self.stdin
192    }
193
194    /// Access the handle for reading from the remote child's standard output (stdout), if
195    /// requested.
196    pub fn stdout(&mut self) -> &mut Option<ChildStdout> {
197        &mut self.stdout
198    }
199
200    /// Access the handle for reading from the remote child's standard error (stderr), if requested.
201    pub fn stderr(&mut self) -> &mut Option<ChildStderr> {
202        &mut self.stderr
203    }
204}
205
206impl<S: Clone> Child<S> {
207    /// Access the SSH session that this remote process was spawned from.
208    pub fn session(&self) -> S {
209        self.session.clone()
210    }
211}