1use std::io;
3
4pub enum InputLine {
5 Pending,
6 Line(String),
7 Eof,
8}
9
10#[derive(Default)]
11pub struct StdinLines {
12 buffer: Vec<u8>,
13 eof: bool,
14 scanned: usize,
15}
16
17impl StdinLines {
18 pub fn poll_line(&mut self) -> io::Result<InputLine> {
20 if let Some(line) = self.take_line()? {
21 return Ok(InputLine::Line(line));
22 }
23 if self.eof {
24 return Ok(InputLine::Eof);
25 }
26 let mut bytes = [0u8; 4096];
27 for _ in 0..16 {
29 let Some(count) = read_available(&mut bytes)? else {
30 return Ok(InputLine::Pending);
31 };
32 if count == 0 {
33 self.eof = true;
34 } else {
35 self.buffer.extend_from_slice(&bytes[..count]);
36 }
37 if let Some(line) = self.take_line()? {
38 return Ok(InputLine::Line(line));
39 }
40 if self.eof {
41 return Ok(InputLine::Eof);
42 }
43 }
44 Ok(InputLine::Pending)
45 }
46
47 fn take_line(&mut self) -> io::Result<Option<String>> {
48 let length = self.buffer[self.scanned..]
49 .iter()
50 .position(|byte| *byte == b'\n')
51 .map(|index| self.scanned + index + 1)
52 .or_else(|| (self.eof && !self.buffer.is_empty()).then_some(self.buffer.len()));
53 let Some(length) = length else {
54 self.scanned = self.buffer.len();
55 return Ok(None);
56 };
57 self.scanned = 0;
58 let mut line: Vec<u8> = self.buffer.drain(..length).collect();
59 if line.last() == Some(&b'\n') {
60 line.pop();
61 }
62 if line.last() == Some(&b'\r') {
63 line.pop();
64 }
65 String::from_utf8(line)
66 .map(Some)
67 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
68 }
69}
70
71#[cfg(unix)]
72fn read_available(bytes: &mut [u8]) -> io::Result<Option<usize>> {
73 read_fd_available(libc::STDIN_FILENO, bytes)
74}
75
76#[cfg(unix)]
77fn read_fd_available(fd: libc::c_int, bytes: &mut [u8]) -> io::Result<Option<usize>> {
78 let mut descriptor = libc::pollfd {
79 fd,
80 events: libc::POLLIN,
81 revents: 0,
82 };
83 let ready = unsafe { libc::poll(&mut descriptor, 1, 0) };
85 if ready < 0 {
86 return interrupted_or_error();
87 }
88 if ready == 0 {
89 return Ok(None);
90 }
91 if descriptor.revents & libc::POLLNVAL != 0 {
92 return Err(io::Error::from_raw_os_error(libc::EBADF));
93 }
94 let count = unsafe { libc::read(fd, bytes.as_mut_ptr().cast(), bytes.len()) };
97 if count < 0 {
98 interrupted_or_error()
99 } else {
100 Ok(Some(count as usize))
101 }
102}
103
104#[cfg(unix)]
105fn interrupted_or_error() -> io::Result<Option<usize>> {
106 let error = io::Error::last_os_error();
107 if matches!(
108 error.kind(),
109 io::ErrorKind::Interrupted | io::ErrorKind::WouldBlock
110 ) {
111 Ok(None)
112 } else {
113 Err(error)
114 }
115}
116
117#[cfg(windows)]
118fn read_available(bytes: &mut [u8]) -> io::Result<Option<usize>> {
119 use windows_sys::Win32::System::Console::{GetStdHandle, STD_INPUT_HANDLE};
120 read_handle_available(unsafe { GetStdHandle(STD_INPUT_HANDLE) }, bytes)
122}
123
124#[cfg(windows)]
125fn read_handle_available(
126 input: windows_sys::Win32::Foundation::HANDLE,
127 bytes: &mut [u8],
128) -> io::Result<Option<usize>> {
129 use windows_sys::Win32::Foundation::{
130 GetLastError, ERROR_BROKEN_PIPE, ERROR_HANDLE_EOF, INVALID_HANDLE_VALUE,
131 };
132 use windows_sys::Win32::Storage::FileSystem::{
133 GetFileType, ReadFile, FILE_TYPE_DISK, FILE_TYPE_PIPE,
134 };
135 use windows_sys::Win32::System::Pipes::PeekNamedPipe;
136 if input.is_null() || input == INVALID_HANDLE_VALUE {
137 return Err(io::Error::last_os_error());
138 }
139 let kind = unsafe { GetFileType(input) };
140 let capacity = if kind == FILE_TYPE_PIPE {
141 let mut available = 0;
142 let ok = unsafe {
144 PeekNamedPipe(
145 input,
146 std::ptr::null_mut(),
147 0,
148 std::ptr::null_mut(),
149 &mut available,
150 std::ptr::null_mut(),
151 )
152 };
153 if ok == 0 {
154 let error = unsafe { GetLastError() };
155 return if matches!(error, ERROR_BROKEN_PIPE | ERROR_HANDLE_EOF) {
156 Ok(Some(0))
157 } else {
158 Err(io::Error::from_raw_os_error(error as i32))
159 };
160 }
161 if available == 0 {
162 return Ok(None);
163 }
164 available.min(bytes.len() as u32)
165 } else if kind == FILE_TYPE_DISK {
166 bytes.len() as u32
167 } else {
168 return Err(io::Error::new(
169 io::ErrorKind::Unsupported,
170 "stdio server requires piped or file stdin",
171 ));
172 };
173 let mut count = 0;
174 let ok = unsafe {
176 ReadFile(
177 input,
178 bytes.as_mut_ptr(),
179 capacity,
180 &mut count,
181 std::ptr::null_mut(),
182 )
183 };
184 if ok == 0 {
185 let error = unsafe { GetLastError() };
186 return if matches!(error, ERROR_BROKEN_PIPE | ERROR_HANDLE_EOF) {
187 Ok(Some(0))
188 } else {
189 Err(io::Error::from_raw_os_error(error as i32))
190 };
191 }
192 Ok(Some(count as usize))
193}
194
195#[cfg(not(any(unix, windows)))]
196fn read_available(_: &mut [u8]) -> io::Result<Option<usize>> {
197 Err(io::Error::new(
198 io::ErrorKind::Unsupported,
199 "stdio polling is unsupported on this platform",
200 ))
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 #[cfg(windows)]
207 #[test]
208 fn pipe_poll_is_idle_then_readable_then_eof_without_a_reader_thread() {
209 use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
210 use windows_sys::Win32::Storage::FileSystem::WriteFile;
211 use windows_sys::Win32::System::Pipes::CreatePipe;
212 let mut input = std::ptr::null_mut();
213 let mut output = std::ptr::null_mut();
214 assert_ne!(
215 unsafe { CreatePipe(&mut input, &mut output, std::ptr::null(), 0) },
216 0
217 );
218 let input = unsafe { OwnedHandle::from_raw_handle(input) };
219 let output = unsafe { OwnedHandle::from_raw_handle(output) };
220 let mut bytes = [0u8; 16];
221 assert_eq!(
222 read_handle_available(input.as_raw_handle(), &mut bytes).unwrap(),
223 None
224 );
225 let mut written = 0;
226 assert_ne!(
227 unsafe {
228 WriteFile(
229 output.as_raw_handle(),
230 b"abc".as_ptr(),
231 3,
232 &mut written,
233 std::ptr::null_mut(),
234 )
235 },
236 0
237 );
238 assert_eq!(
239 read_handle_available(input.as_raw_handle(), &mut bytes).unwrap(),
240 Some(3)
241 );
242 assert_eq!(&bytes[..3], b"abc");
243 drop(output);
244 assert_eq!(
245 read_handle_available(input.as_raw_handle(), &mut bytes).unwrap(),
246 Some(0)
247 );
248 }
249
250 #[cfg(unix)]
251 #[test]
252 fn pipe_poll_is_idle_then_readable_then_eof_without_a_reader_thread() {
253 use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
254 let mut descriptors = [0; 2];
255 assert_eq!(unsafe { libc::pipe(descriptors.as_mut_ptr()) }, 0);
256 let input = unsafe { OwnedFd::from_raw_fd(descriptors[0]) };
257 let output = unsafe { OwnedFd::from_raw_fd(descriptors[1]) };
258 let mut bytes = [0u8; 16];
259 assert_eq!(
260 read_fd_available(input.as_raw_fd(), &mut bytes).unwrap(),
261 None
262 );
263 assert_eq!(
264 unsafe { libc::write(output.as_raw_fd(), b"abc".as_ptr().cast(), 3) },
265 3
266 );
267 assert_eq!(
268 read_fd_available(input.as_raw_fd(), &mut bytes).unwrap(),
269 Some(3)
270 );
271 assert_eq!(&bytes[..3], b"abc");
272 drop(output);
273 assert_eq!(
274 read_fd_available(input.as_raw_fd(), &mut bytes).unwrap(),
275 Some(0)
276 );
277 }
278
279 #[test]
280 fn retains_partial_utf8_and_final_line_without_newline() {
281 let mut reader = StdinLines::default();
282 reader
283 .buffer
284 .extend_from_slice(&"中文\r\nnext".as_bytes()[..2]);
285 assert!(reader.take_line().unwrap().is_none());
286 reader
287 .buffer
288 .extend_from_slice(&"中文\r\nnext".as_bytes()[2..]);
289 assert_eq!(reader.take_line().unwrap().as_deref(), Some("中文"));
290 assert!(reader.take_line().unwrap().is_none());
291 reader.eof = true;
292 assert_eq!(reader.take_line().unwrap().as_deref(), Some("next"));
293 assert!(reader.take_line().unwrap().is_none());
294 }
295}