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}