1use anyhow::Context as _;
6#[cfg(target_os = "fuchsia")]
7use fidl_fuchsia_io as fio;
8#[cfg(not(target_os = "fuchsia"))]
9use fidl_fuchsia_io_common as fio;
10use futures::future::Either;
11use futures::stream::StreamExt as _;
12use futures::{AsyncReadExt as _, AsyncWriteExt as _};
13use std::io::StdoutLock;
14use termion::raw::IntoRawMode as _;
15
16pub enum Stdout<'a> {
19 Raw(termion::raw::RawTerminal<StdoutLock<'a>>),
22 Buffered,
24}
25
26impl std::io::Write for Stdout<'_> {
27 fn flush(&mut self) -> Result<(), std::io::Error> {
28 match self {
29 Self::Raw(r) => r.flush(),
30 Self::Buffered => std::io::stdout().flush(),
31 }
32 }
33 fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
34 match self {
35 Self::Raw(r) => r.write(buf),
36 Self::Buffered => std::io::stdout().write(buf),
37 }
38 }
39}
40
41impl Stdout<'_> {
42 pub fn raw() -> anyhow::Result<Self> {
43 let stdout = std::io::stdout();
44
45 if !termion::is_tty(&stdout) {
46 anyhow::bail!("interactive mode does not support piping");
47 }
48
49 let term_out =
52 stdout.lock().into_raw_mode().context("could not set raw mode on terminal")?;
53
54 Ok(Self::Raw(term_out))
55 }
56
57 pub fn buffered() -> Self {
58 Self::Buffered
59 }
60}
61
62#[cfg(target_os = "fuchsia")]
67pub async fn connect_socket_to_stdio(
68 socket: fidl::Socket,
69 stdout: Stdout<'_>,
70) -> anyhow::Result<()> {
71 connect_socket_to_stdio_impl(
72 fidl::AsyncSocket::from_socket(socket),
73 || std::io::stdin().lock(),
74 stdout,
75 )?
76 .await
77}
78
79pub async fn connect_fdomain_socket_to_stdio(
81 socket: fdomain_client::Socket,
82 stdout: Stdout<'_>,
83) -> anyhow::Result<()> {
84 connect_socket_to_stdio_impl(socket, || std::io::stdin().lock(), stdout)?.await
85}
86
87fn connect_socket_to_stdio_impl<R>(
88 socket: impl futures::AsyncRead + futures::AsyncWrite,
89 stdin: impl FnOnce() -> R + Send + 'static,
90 mut stdout: impl std::io::Write,
91) -> anyhow::Result<impl futures::Future<Output = anyhow::Result<()>>>
92where
93 R: std::io::Read,
94{
95 let (stdin_send, mut stdin_recv) = futures::channel::mpsc::unbounded();
97 let _: std::thread::JoinHandle<_> = std::thread::Builder::new()
98 .name("connect_socket_to_stdio stdin thread".into())
99 .spawn(move || {
100 let mut stdin = stdin();
101 let mut buf = [0u8; fio::MAX_BUF as usize];
102 loop {
103 let bytes_read = stdin.read(&mut buf)?;
104 if bytes_read == 0 {
105 return Ok::<(), anyhow::Error>(());
106 }
107 let () = stdin_send.unbounded_send(buf[..bytes_read].to_vec())?;
108 }
109 })
110 .context("spawning stdin thread")?;
111
112 let (mut socket_in, mut socket_out) = socket.split();
113
114 let stdin_to_socket = async move {
115 while let Some(stdin) = stdin_recv.next().await {
116 socket_out.write_all(&stdin).await.context("writing to socket")?;
117 socket_out.flush().await.context("flushing socket")?;
118 }
119 Ok::<(), anyhow::Error>(())
120 };
121
122 let socket_to_stdout = async move {
123 let mut buf = vec![0u8; fio::MAX_BUF as usize];
124 loop {
125 let bytes_read = socket_in.read(&mut buf).await.context("reading from socket")?;
126 if bytes_read == 0 {
127 break;
128 }
129 stdout.write_all(&buf[..bytes_read]).context("writing to stdout")?;
130 stdout.flush().context("flushing stdout")?;
131 }
132 Ok::<(), anyhow::Error>(())
133 };
134
135 Ok(async move {
136 futures::pin_mut!(stdin_to_socket);
137 futures::pin_mut!(socket_to_stdout);
138 Ok(match futures::future::select(stdin_to_socket, socket_to_stdout).await {
139 Either::Left((stdin_to_socket, socket_to_stdout)) => {
140 let () = stdin_to_socket?;
141 let () = socket_to_stdout.await?;
145 }
146 Either::Right((socket_to_stdout, _)) => {
147 let () = socket_to_stdout?;
148 }
151 })
152 })
153}
154
155#[cfg(all(test, target_os = "fuchsia"))]
156mod tests {
157 use super::*;
158
159 #[fuchsia::test]
160 async fn stdin_to_socket() {
161 let (socket, socket_remote) = fidl::Socket::create_stream();
162 let socket_remote = fidl::AsyncSocket::from_socket(socket_remote);
163
164 let connect_fut =
165 connect_socket_to_stdio_impl(socket_remote, || &b"test input"[..], vec![]).unwrap();
166
167 let (connect_res, bytes_from_socket) = futures::join!(connect_fut, async move {
168 let mut socket = fidl::AsyncSocket::from_socket(socket);
169 let mut out = vec![0u8; 100];
170 let bytes_read = socket.read(&mut out).await.unwrap();
171 drop(socket);
172 out.resize(bytes_read, 0);
173 out
174 });
175 let () = connect_res.unwrap();
176
177 assert_eq!(bytes_from_socket, &b"test input"[..]);
178 }
179
180 #[fuchsia::test]
181 async fn socket_to_stdout() {
182 let (socket, socket_remote) = fidl::Socket::create_stream();
183 assert_eq!(socket.write(&b"test input"[..]).unwrap(), 10);
184 drop(socket);
185 let mut stdout = vec![];
186 let (unblocker, block_until) = std::sync::mpsc::channel();
187
188 let socket_remote = fidl::AsyncSocket::from_socket(socket_remote);
189 let () = connect_socket_to_stdio_impl(
190 socket_remote,
191 move || {
192 let () = block_until.recv().unwrap();
193 &[][..]
194 },
195 &mut stdout,
196 )
197 .unwrap()
198 .await
199 .unwrap();
200
201 unblocker.send(()).unwrap();
203
204 assert_eq!(&stdout[..], &b"test input"[..]);
205 }
206}