Skip to content

Commit 2a721e2

Browse files
authored
Make rustix::io::stdout a safe function, in std mode. (#519)
* Make `rustix::io::stdout` a safe function, in std mode. Revisit the safety considerations of `rustix::io::stdout` and friends. The Rust standard library assumes that the stdin, stdout, and stderr file descriptors are always open, to the point where it's even willing to hand out a [`'static` borrow for them]. Consequently, rustix doesn't need to treat these as unsafe when std is in use. [`'static` borrow for them]: https://doc.rust-lang.org/stable/std/io/struct.Stdin.html#method.lock * Fix an unused-unsafe warning in an example.
1 parent d78fac6 commit 2a721e2

File tree

4 files changed

+134
-70
lines changed

4 files changed

+134
-70
lines changed

examples/hello.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,8 @@ fn main() -> std::io::Result<()> {
1111
// need the ability to compute substrings at arbitrary byte offsets.
1212
let mut bytes = message.as_bytes();
1313

14-
// Safety: See [here] for the safety conditions for calling `stdout`. In
15-
// this example, the code is inside `main` itself so we know how `stdout`
16-
// is being used and we know that it's not dropped.
17-
//
18-
// [here]: https://docs.rs/rustix/*/rustix/io/fn.stdout.html#safety
19-
let stdout = unsafe { rustix::io::stdout() };
14+
// In a std-using configuration, `stdout` is always open.
15+
let stdout = rustix::io::stdout();
2016

2117
while !bytes.is_empty() {
2218
match rustix::io::write(&stdout, bytes) {

examples/stdio.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustix::termios::ttyname;
1919

2020
#[cfg(not(windows))]
2121
fn main() -> io::Result<()> {
22-
let (stdin, stdout, stderr) = unsafe { (stdin(), stdout(), stderr()) };
22+
let (stdin, stdout, stderr) = (stdin(), stdout(), stderr());
2323

2424
println!("Stdin:");
2525
show(&stdin)?;

src/io/stdio.rs

Lines changed: 119 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,57 @@ use backend::fd::{BorrowedFd, FromRawFd, RawFd};
1414

1515
/// `STDIN_FILENO`—Standard input, borrowed.
1616
///
17-
/// # Safety
17+
/// In `std`-using configurations, this is a safe function, because the
18+
/// standard library already assumes that the stdin file descriptor is always
19+
/// valid. In `no_std` configurations, it is `unsafe`.
1820
///
19-
/// This function must be called from code which knows how the process'
20-
/// standard input is being used. Often, this will be the `main` function or
21-
/// code that knows its relationship with the `main` function.
21+
/// # Warning
2222
///
23-
/// The stdin file descriptor can be closed, potentially on other threads, in
24-
/// which case the file descriptor index value could be dynamically reused for
25-
/// other purposes, potentially on different threads.
23+
/// This function allows reading directly from stdin without coordinating
24+
/// with the buffering performed by [`std::io::Stdin`], so it could cause
25+
/// corrupted input.
2626
///
27-
/// # Other hazards
27+
/// # References
28+
/// - [POSIX]
29+
/// - [Linux]
30+
///
31+
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdin.html
32+
/// [Linux]: https://man7.org/linux/man-pages/man3/stdin.3.html
33+
#[cfg(feature = "std")]
34+
#[doc(alias = "STDIN_FILENO")]
35+
#[inline]
36+
pub const fn stdin() -> BorrowedFd<'static> {
37+
// Safety: When "std" is enabled, the standard library assumes that the stdio
38+
// file descriptors are all valid.
39+
unsafe { BorrowedFd::borrow_raw(backend::io::types::STDIN_FILENO as RawFd) }
40+
}
41+
42+
/// `STDIN_FILENO`—Standard input, borrowed.
43+
///
44+
/// In `std`-using configurations, this is a safe function, because the
45+
/// standard library already assumes that the stdin file descriptor is always
46+
/// valid. In `no_std` configurations, it is `unsafe`.
47+
///
48+
/// # Safety
49+
///
50+
/// In `no_std` configurations, the stdin file descriptor can be closed,
51+
/// potentially on other threads, in which case the file descriptor index
52+
/// value could be dynamically reused for other purposes, potentially on
53+
/// different threads.
2854
///
29-
/// Stdin could be redirected from arbitrary input sources, and unless one
30-
/// knows how the process' standard input is being used, one could consume
31-
/// bytes that are expected to be consumed by other parts of the process.
55+
/// # Warning
56+
///
57+
/// This function allows reading directly from stdin without coordinating
58+
/// with the buffering performed by [`std::io::Stdin`], so it could cause
59+
/// corrupted input.
3260
///
3361
/// # References
3462
/// - [POSIX]
3563
/// - [Linux]
3664
///
3765
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdin.html
3866
/// [Linux]: https://man7.org/linux/man-pages/man3/stdin.3.html
67+
#[cfg(not(feature = "std"))]
3968
#[doc(alias = "STDIN_FILENO")]
4069
#[inline]
4170
pub const unsafe fn stdin() -> BorrowedFd<'static> {
@@ -49,17 +78,14 @@ pub const unsafe fn stdin() -> BorrowedFd<'static> {
4978
///
5079
/// # Safety
5180
///
52-
/// This is unsafe for the same reasons as [`stdin`].
81+
/// Safe `std`-using Rust code is permitted to assume that the stdin file
82+
/// descriptor is always valid. This function returns an `OwnedFd` which will
83+
/// close the stdin file descriptor when dropped.
5384
///
54-
/// # Other hazards
85+
/// # Warning
5586
///
5687
/// This has the same hazards as [`stdin`].
5788
///
58-
/// And, when the `OwnedFd` is dropped, subsequent newly created file
59-
/// descriptors may unknowingly reuse the stdin file descriptor number, which
60-
/// may break common assumptions, so it should typically only be dropped at the
61-
/// end of a program when no more file descriptors will be created.
62-
///
6389
/// # References
6490
/// - [POSIX]
6591
/// - [Linux]
@@ -74,29 +100,57 @@ pub unsafe fn take_stdin() -> OwnedFd {
74100

75101
/// `STDOUT_FILENO`—Standard output, borrowed.
76102
///
77-
/// # Safety
103+
/// In `std`-using configurations, this is a safe function, because the
104+
/// standard library already assumes that the stdout file descriptor is always
105+
/// valid. In `no_std` configurations, it is `unsafe`.
78106
///
79-
/// This function must be called from code which knows how the process'
80-
/// standard output is being used. Often, this will be the `main` function or
81-
/// code that knows its relationship with the `main` function.
107+
/// # Warning
82108
///
83-
/// The stdout file descriptor can be closed, potentially on other threads, in
84-
/// which case the file descriptor index value could be dynamically reused for
85-
/// other purposes, potentially on different threads.
109+
/// This function allows reading directly from stdout without coordinating
110+
/// with the buffering performed by [`std::io::Stdout`], so it could cause
111+
/// corrupted input.
86112
///
87-
/// # Other hazards
113+
/// # References
114+
/// - [POSIX]
115+
/// - [Linux]
116+
///
117+
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdout.html
118+
/// [Linux]: https://man7.org/linux/man-pages/man3/stdout.3.html
119+
#[cfg(feature = "std")]
120+
#[doc(alias = "STDOUT_FILENO")]
121+
#[inline]
122+
pub const fn stdout() -> BorrowedFd<'static> {
123+
// Safety: When "std" is enabled, the standard library assumes that the stdio
124+
// file descriptors are all valid.
125+
unsafe { BorrowedFd::borrow_raw(backend::io::types::STDOUT_FILENO as RawFd) }
126+
}
127+
128+
/// `STDOUT_FILENO`—Standard output, borrowed.
88129
///
89-
/// Stdout could be redirected to arbitrary output sinks, and unless one
90-
/// knows how the process' standard output is being used, one could
91-
/// unexpectedly inject bytes into a stream being written by another part of
92-
/// the process.
130+
/// In `std`-using configurations, this is a safe function, because the
131+
/// standard library already assumes that the stdin file descriptor is always
132+
/// valid. In `no_std` configurations, it is `unsafe`.
133+
///
134+
/// # Safety
135+
///
136+
/// In `no_std` configurations, the stdout file descriptor can be closed,
137+
/// potentially on other threads, in which case the file descriptor index
138+
/// value could be dynamically reused for other purposes, potentially on
139+
/// different threads.
140+
///
141+
/// # Warning
142+
///
143+
/// This function allows reading directly from stdout without coordinating
144+
/// with the buffering performed by [`std::io::Stdout`], so it could cause
145+
/// corrupted input.
93146
///
94147
/// # References
95148
/// - [POSIX]
96149
/// - [Linux]
97150
///
98151
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdout.html
99152
/// [Linux]: https://man7.org/linux/man-pages/man3/stdout.3.html
153+
#[cfg(not(feature = "std"))]
100154
#[doc(alias = "STDOUT_FILENO")]
101155
#[inline]
102156
pub const unsafe fn stdout() -> BorrowedFd<'static> {
@@ -110,17 +164,14 @@ pub const unsafe fn stdout() -> BorrowedFd<'static> {
110164
///
111165
/// # Safety
112166
///
113-
/// This is unsafe for the same reasons as [`stdout`].
167+
/// Safe `std`-using Rust code is permitted to assume that the stdout file
168+
/// descriptor is always valid. This function returns an `OwnedFd` which will
169+
/// close the stdout file descriptor when dropped.
114170
///
115-
/// # Other hazards
171+
/// # Warning
116172
///
117173
/// This has the same hazards as [`stdout`].
118174
///
119-
/// And, when the `OwnedFd` is dropped, subsequent newly created file
120-
/// descriptors may unknowingly reuse the stdout file descriptor number, which
121-
/// may break common assumptions, so it should typically only be dropped at the
122-
/// end of a program when no more file descriptors will be created.
123-
///
124175
/// # References
125176
/// - [POSIX]
126177
/// - [Linux]
@@ -135,28 +186,45 @@ pub unsafe fn take_stdout() -> OwnedFd {
135186

136187
/// `STDERR_FILENO`—Standard error, borrowed.
137188
///
138-
/// # Safety
189+
/// In `std`-using configurations, this is a safe function, because the
190+
/// standard library already assumes that the stderr file descriptor is always
191+
/// valid. In `no_std` configurations, it is `unsafe`.
139192
///
140-
/// This function must be called from code which knows how the process'
141-
/// standard error is being used. Often, this will be the `main` function or
142-
/// code that knows its relationship with the `main` function.
193+
/// # References
194+
/// - [POSIX]
195+
/// - [Linux]
143196
///
144-
/// The stderr file descriptor can be closed, potentially on other threads, in
145-
/// which case the file descriptor index value could be dynamically reused for
146-
/// other purposes, potentially on different threads.
197+
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stderr.html
198+
/// [Linux]: https://man7.org/linux/man-pages/man3/stderr.3.html
199+
#[cfg(feature = "std")]
200+
#[doc(alias = "STDERR_FILENO")]
201+
#[inline]
202+
pub const fn stderr() -> BorrowedFd<'static> {
203+
// Safety: When "std" is enabled, the standard library assumes that the stdio
204+
// file descriptors are all valid.
205+
unsafe { BorrowedFd::borrow_raw(backend::io::types::STDERR_FILENO as RawFd) }
206+
}
207+
208+
/// `STDERR_FILENO`—Standard error, borrowed.
147209
///
148-
/// # Other hazards
210+
/// In `std`-using configurations, this is a safe function, because the
211+
/// standard library already assumes that the stderr file descriptor is always
212+
/// valid. In `no_std` configurations, it is `unsafe`.
213+
///
214+
/// # Safety
149215
///
150-
/// Stderr could be redirected to arbitrary output sinks, and unless one
151-
/// knows how the process' standard error is being used, one could unexpectedly
152-
/// inject bytes into a stream being written by another part of the process.
216+
/// In `no_std` configurations, the stderr file descriptor can be closed,
217+
/// potentially on other threads, in which case the file descriptor index
218+
/// value could be dynamically reused for other purposes, potentially on
219+
/// different threads.
153220
///
154221
/// # References
155222
/// - [POSIX]
156223
/// - [Linux]
157224
///
158225
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stderr.html
159226
/// [Linux]: https://man7.org/linux/man-pages/man3/stderr.3.html
227+
#[cfg(not(feature = "std"))]
160228
#[doc(alias = "STDERR_FILENO")]
161229
#[inline]
162230
pub const unsafe fn stderr() -> BorrowedFd<'static> {
@@ -170,7 +238,9 @@ pub const unsafe fn stderr() -> BorrowedFd<'static> {
170238
///
171239
/// # Safety
172240
///
173-
/// This is unsafe for the same reasons as [`stderr`].
241+
/// Safe std-using Rust code is permitted to assume that the stderr file
242+
/// descriptor is always valid. This function returns an `OwnedFd` which will
243+
/// close the stderr file descriptor when dropped.
174244
///
175245
/// # Other hazards
176246
///

tests/termios/isatty.rs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,16 @@ fn stdio_descriptors() {
5252
#[cfg(target_os = "wasi")]
5353
use std::os::wasi::io::AsRawFd;
5454

55-
unsafe {
56-
assert_eq!(
57-
rustix::io::stdin().as_raw_fd(),
58-
std::io::stdin().as_raw_fd()
59-
);
60-
assert_eq!(
61-
rustix::io::stdout().as_raw_fd(),
62-
std::io::stdout().as_raw_fd()
63-
);
64-
assert_eq!(
65-
rustix::io::stderr().as_raw_fd(),
66-
std::io::stderr().as_raw_fd()
67-
);
68-
}
55+
assert_eq!(
56+
rustix::io::stdin().as_raw_fd(),
57+
std::io::stdin().as_raw_fd()
58+
);
59+
assert_eq!(
60+
rustix::io::stdout().as_raw_fd(),
61+
std::io::stdout().as_raw_fd()
62+
);
63+
assert_eq!(
64+
rustix::io::stderr().as_raw_fd(),
65+
std::io::stderr().as_raw_fd()
66+
);
6967
}

0 commit comments

Comments
 (0)