Skip to content

Commit 4dcb7af

Browse files
authored
Rollup merge of rust-lang#58454 - pitdicker:windows_stdio, r=alexcrichton
Refactor Windows stdio and remove stdin double buffering I was looking for something nice and small to work on, tried to tackle a few FIXME's in Windows stdio, and things grew from there. This part of the standard library contains some tricky code, and has changed over the years to handle more corner cases. It could use some refactoring and extra comments. Changes/fixes: - Made `StderrRaw` `pub(crate)`, to remove the `Write` implementations on `sys::Stderr` (used unsynchronised for panic output). - Remove the unused `Read` implementation on `sys::windows::stdin` - The `windows::stdio::Output` enum made sense when we cached the handles, but we can use simple functions like `is_console` now that we get the handle on every read/write - `write` can now calculate the number of written bytes as UTF-8 when we can't write all `u16`s. - If `write` could only write one half of a surrogate pair, attempt another write for the other because user code can't reslice in any way that would allow us to write it otherwise. - Removed the double buffering on stdin. Documentation on the unexposed `StdinRaw` says: 'This handle is not synchronized or buffered in any fashion'; which is now true. - `sys::windows::Stdin` now always only partially fills its buffer, so we can guarantee any arbitrary UTF-16 can be re-encoded without losing any data. - `sys::windows::STDIN_BUF_SIZE` is slightly larger to compensate. There should be no real change in the number of syscalls the buffered `Stdin` does. This buffer is a little larger, while the extra buffer on Stdin is gone. - `sys::windows::Stdin` now attempts to handle unpaired surrogates at its buffer boundary. - `sys::windows::Stdin` no langer allocates for its buffer, but the UTF-16 decoding still does. ### Testing I did some manual testing of reading and writing to console. The console does support UTF-16 in some sense, but doesn't supporting displaying characters outside the BMP. - compile stage 1 stdlib with a tiny value for `MAX_BUFFER_SIZE` to make it easier to catch corner cases - run a simple test program that reads on stdin, and echo's to stdout - write some lines with plenty of ASCII and emoji in a text editor - copy and paste in console to stdin - return with `\r\n\` or CTRL-Z - copy and paste in text editor - check it round-trips ----- Fixes rust-lang#23344. All but one of the suggestions in that issue are now implemented. the missing one is: > * When reading data, we require the entire set of input to be valid UTF-16. We should instead attempt to read as much of the input as possible as valid UTF-16, only returning an error for the actual invalid elements. For example if we read 10 elements, 5 of which are valid UTF-16, the 6th is bad, and then the remaining are all valid UTF-16, we should probably return the first 5 on a call to `read`, then return an error, then return the remaining on the next call to `read`. Stdin in Console mode is dealing with text directly input by a user. In my opinion getting an unpaired surrogate is quite unlikely in that case, and a valid reason to error on the entire line of input (which is probably short). Dealing with it is incompatible with an unbuffered stdin, which seems the more interesting guarantee to me.
2 parents b78e9f4 + 1a944b0 commit 4dcb7af

File tree

7 files changed

+273
-241
lines changed

7 files changed

+273
-241
lines changed

src/libstd/sys/cloudabi/stdio.rs

+11-18
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ impl Stdin {
99
pub fn new() -> io::Result<Stdin> {
1010
Ok(Stdin(()))
1111
}
12+
}
1213

13-
pub fn read(&self, _: &mut [u8]) -> io::Result<usize> {
14+
impl io::Read for Stdin {
15+
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
1416
Ok(0)
1517
}
1618
}
@@ -19,15 +21,17 @@ impl Stdout {
1921
pub fn new() -> io::Result<Stdout> {
2022
Ok(Stdout(()))
2123
}
24+
}
2225

23-
pub fn write(&self, _: &[u8]) -> io::Result<usize> {
26+
impl io::Write for Stdout {
27+
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
2428
Err(io::Error::new(
2529
io::ErrorKind::BrokenPipe,
2630
"Stdout is not connected to any output in this environment",
2731
))
2832
}
2933

30-
pub fn flush(&self) -> io::Result<()> {
34+
fn flush(&mut self) -> io::Result<()> {
3135
Ok(())
3236
}
3337
}
@@ -36,29 +40,18 @@ impl Stderr {
3640
pub fn new() -> io::Result<Stderr> {
3741
Ok(Stderr(()))
3842
}
43+
}
3944

40-
pub fn write(&self, _: &[u8]) -> io::Result<usize> {
45+
impl io::Write for Stderr {
46+
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
4147
Err(io::Error::new(
4248
io::ErrorKind::BrokenPipe,
4349
"Stderr is not connected to any output in this environment",
4450
))
4551
}
4652

47-
pub fn flush(&self) -> io::Result<()> {
48-
Ok(())
49-
}
50-
}
51-
52-
// FIXME: right now this raw stderr handle is used in a few places because
53-
// std::io::stderr_raw isn't exposed, but once that's exposed this impl
54-
// should go away
55-
impl io::Write for Stderr {
56-
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
57-
Stderr::write(self, data)
58-
}
59-
6053
fn flush(&mut self) -> io::Result<()> {
61-
Stderr::flush(self)
54+
Ok(())
6255
}
6356
}
6457

src/libstd/sys/redox/stdio.rs

+14-21
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,48 @@ pub struct Stderr(());
88

99
impl Stdin {
1010
pub fn new() -> io::Result<Stdin> { Ok(Stdin(())) }
11+
}
1112

12-
pub fn read(&self, data: &mut [u8]) -> io::Result<usize> {
13+
impl io::Read for Stdin {
14+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1315
let fd = FileDesc::new(0);
14-
let ret = fd.read(data);
16+
let ret = fd.read(buf);
1517
fd.into_raw();
1618
ret
1719
}
1820
}
1921

2022
impl Stdout {
2123
pub fn new() -> io::Result<Stdout> { Ok(Stdout(())) }
24+
}
2225

23-
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
26+
impl io::Write for Stdout {
27+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
2428
let fd = FileDesc::new(1);
25-
let ret = fd.write(data);
29+
let ret = fd.write(buf);
2630
fd.into_raw();
2731
ret
2832
}
2933

30-
pub fn flush(&self) -> io::Result<()> {
34+
fn flush(&mut self) -> io::Result<()> {
3135
cvt(syscall::fsync(1)).and(Ok(()))
3236
}
3337
}
3438

3539
impl Stderr {
3640
pub fn new() -> io::Result<Stderr> { Ok(Stderr(())) }
41+
}
3742

38-
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
43+
impl io::Write for Stderr {
44+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3945
let fd = FileDesc::new(2);
40-
let ret = fd.write(data);
46+
let ret = fd.write(buf);
4147
fd.into_raw();
4248
ret
4349
}
4450

45-
pub fn flush(&self) -> io::Result<()> {
46-
cvt(syscall::fsync(2)).and(Ok(()))
47-
}
48-
}
49-
50-
// FIXME: right now this raw stderr handle is used in a few places because
51-
// std::io::stderr_raw isn't exposed, but once that's exposed this impl
52-
// should go away
53-
impl io::Write for Stderr {
54-
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
55-
Stderr::write(self, data)
56-
}
57-
5851
fn flush(&mut self) -> io::Result<()> {
59-
Stderr::flush(self)
52+
cvt(syscall::fsync(2)).and(Ok(()))
6053
}
6154
}
6255

src/libstd/sys/sgx/stdio.rs

+12-19
Original file line numberDiff line numberDiff line change
@@ -16,46 +16,39 @@ fn with_std_fd<F: FnOnce(&FileDesc) -> R, R>(fd: abi::Fd, f: F) -> R {
1616

1717
impl Stdin {
1818
pub fn new() -> io::Result<Stdin> { Ok(Stdin(())) }
19+
}
1920

20-
pub fn read(&self, data: &mut [u8]) -> io::Result<usize> {
21-
with_std_fd(abi::FD_STDIN, |fd| fd.read(data))
21+
impl io::Read for Stdin {
22+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
23+
with_std_fd(abi::FD_STDIN, |fd| fd.read(buf))
2224
}
2325
}
2426

2527
impl Stdout {
2628
pub fn new() -> io::Result<Stdout> { Ok(Stdout(())) }
29+
}
2730

28-
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
29-
with_std_fd(abi::FD_STDOUT, |fd| fd.write(data))
31+
impl io::Write for Stdout {
32+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
33+
with_std_fd(abi::FD_STDOUT, |fd| fd.write(buf))
3034
}
3135

32-
pub fn flush(&self) -> io::Result<()> {
36+
fn flush(&mut self) -> io::Result<()> {
3337
with_std_fd(abi::FD_STDOUT, |fd| fd.flush())
3438
}
3539
}
3640

3741
impl Stderr {
3842
pub fn new() -> io::Result<Stderr> { Ok(Stderr(())) }
39-
40-
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
41-
with_std_fd(abi::FD_STDERR, |fd| fd.write(data))
42-
}
43-
44-
pub fn flush(&self) -> io::Result<()> {
45-
with_std_fd(abi::FD_STDERR, |fd| fd.flush())
46-
}
4743
}
4844

49-
// FIXME: right now this raw stderr handle is used in a few places because
50-
// std::io::stderr_raw isn't exposed, but once that's exposed this impl
51-
// should go away
5245
impl io::Write for Stderr {
53-
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
54-
Stderr::write(self, data)
46+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
47+
with_std_fd(abi::FD_STDERR, |fd| fd.write(buf))
5548
}
5649

5750
fn flush(&mut self) -> io::Result<()> {
58-
Stderr::flush(self)
51+
with_std_fd(abi::FD_STDERR, |fd| fd.flush())
5952
}
6053
}
6154

src/libstd/sys/unix/stdio.rs

+14-21
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,48 @@ pub struct Stderr(());
88

99
impl Stdin {
1010
pub fn new() -> io::Result<Stdin> { Ok(Stdin(())) }
11+
}
1112

12-
pub fn read(&self, data: &mut [u8]) -> io::Result<usize> {
13+
impl io::Read for Stdin {
14+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1315
let fd = FileDesc::new(libc::STDIN_FILENO);
14-
let ret = fd.read(data);
16+
let ret = fd.read(buf);
1517
fd.into_raw(); // do not close this FD
1618
ret
1719
}
1820
}
1921

2022
impl Stdout {
2123
pub fn new() -> io::Result<Stdout> { Ok(Stdout(())) }
24+
}
2225

23-
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
26+
impl io::Write for Stdout {
27+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
2428
let fd = FileDesc::new(libc::STDOUT_FILENO);
25-
let ret = fd.write(data);
29+
let ret = fd.write(buf);
2630
fd.into_raw(); // do not close this FD
2731
ret
2832
}
2933

30-
pub fn flush(&self) -> io::Result<()> {
34+
fn flush(&mut self) -> io::Result<()> {
3135
Ok(())
3236
}
3337
}
3438

3539
impl Stderr {
3640
pub fn new() -> io::Result<Stderr> { Ok(Stderr(())) }
41+
}
3742

38-
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
43+
impl io::Write for Stderr {
44+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3945
let fd = FileDesc::new(libc::STDERR_FILENO);
40-
let ret = fd.write(data);
46+
let ret = fd.write(buf);
4147
fd.into_raw(); // do not close this FD
4248
ret
4349
}
4450

45-
pub fn flush(&self) -> io::Result<()> {
46-
Ok(())
47-
}
48-
}
49-
50-
// FIXME: right now this raw stderr handle is used in a few places because
51-
// std::io::stderr_raw isn't exposed, but once that's exposed this impl
52-
// should go away
53-
impl io::Write for Stderr {
54-
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
55-
Stderr::write(self, data)
56-
}
57-
5851
fn flush(&mut self) -> io::Result<()> {
59-
Stderr::flush(self)
52+
Ok(())
6053
}
6154
}
6255

src/libstd/sys/wasm/stdio.rs

+15-18
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,27 @@ impl Stdin {
99
pub fn new() -> io::Result<Stdin> {
1010
Ok(Stdin)
1111
}
12+
}
1213

13-
pub fn read(&self, data: &mut [u8]) -> io::Result<usize> {
14-
Ok(ReadSysCall::perform(0, data))
14+
impl io::Read for Stdin {
15+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
16+
Ok(ReadSysCall::perform(0, buf))
1517
}
1618
}
1719

1820
impl Stdout {
1921
pub fn new() -> io::Result<Stdout> {
2022
Ok(Stdout)
2123
}
24+
}
2225

23-
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
24-
WriteSysCall::perform(1, data);
25-
Ok(data.len())
26+
impl io::Write for Stdout {
27+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
28+
WriteSysCall::perform(1, buf);
29+
Ok(buf.len())
2630
}
2731

28-
pub fn flush(&self) -> io::Result<()> {
32+
fn flush(&mut self) -> io::Result<()> {
2933
Ok(())
3034
}
3135
}
@@ -34,23 +38,16 @@ impl Stderr {
3438
pub fn new() -> io::Result<Stderr> {
3539
Ok(Stderr)
3640
}
37-
38-
pub fn write(&self, data: &[u8]) -> io::Result<usize> {
39-
WriteSysCall::perform(2, data);
40-
Ok(data.len())
41-
}
42-
43-
pub fn flush(&self) -> io::Result<()> {
44-
Ok(())
45-
}
4641
}
4742

4843
impl io::Write for Stderr {
49-
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
50-
(&*self).write(data)
44+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
45+
WriteSysCall::perform(2, buf);
46+
Ok(buf.len())
5147
}
48+
5249
fn flush(&mut self) -> io::Result<()> {
53-
(&*self).flush()
50+
Ok(())
5451
}
5552
}
5653

src/libstd/sys/windows/process.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,9 @@ impl Stdio {
252252
// should still be unavailable so propagate the
253253
// INVALID_HANDLE_VALUE.
254254
Stdio::Inherit => {
255-
match stdio::get(stdio_id) {
255+
match stdio::get_handle(stdio_id) {
256256
Ok(io) => {
257-
let io = Handle::new(io.handle());
257+
let io = Handle::new(io);
258258
let ret = io.duplicate(0, true,
259259
c::DUPLICATE_SAME_ACCESS);
260260
io.into_raw();

0 commit comments

Comments
 (0)