Skip to content

Commit 8f52bc9

Browse files
committed
feat: I/O safety for 'sys/termios' & 'pty'
1 parent 67f7d46 commit 8f52bc9

File tree

9 files changed

+99
-193
lines changed

9 files changed

+99
-193
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1616
([#1862](https://github.com/nix-rust/nix/pull/1862))
1717
- The epoll interface now uses a type.
1818
([#1882](https://github.com/nix-rust/nix/pull/1882))
19+
- With I/O-safe type applied in `pty::OpenptyResult` and `pty::ForkptyResult`,
20+
users no longer need to manually close the file descriptors in these types.
21+
([#1921](https://github.com/nix-rust/nix/pull/1921))
1922

2023
### Fixed
2124
### Removed

Cargo.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,3 @@ path = "test/test_clearenv.rs"
108108
name = "test-mount"
109109
path = "test/test_mount.rs"
110110
harness = false
111-
112-
[[test]]
113-
name = "test-ptymaster-drop"
114-
path = "test/test_ptymaster_drop.rs"

src/pty.rs

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,78 +16,58 @@ use crate::{fcntl, unistd, Result};
1616

1717
/// Representation of a master/slave pty pair
1818
///
19-
/// This is returned by `openpty`. Note that this type does *not* implement `Drop`, so the user
20-
/// must manually close the file descriptors.
21-
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
19+
/// This is returned by [`openpty`].
20+
#[derive(Debug)]
2221
pub struct OpenptyResult {
2322
/// The master port in a virtual pty pair
24-
pub master: RawFd,
23+
pub master: OwnedFd,
2524
/// The slave port in a virtual pty pair
26-
pub slave: RawFd,
25+
pub slave: OwnedFd,
2726
}
2827

2928
feature! {
3029
#![feature = "process"]
3130
/// Representation of a master with a forked pty
3231
///
33-
/// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user
34-
/// must manually close the file descriptors.
35-
#[derive(Clone, Copy, Debug)]
32+
/// This is returned by [`forkpty`].
33+
#[derive(Debug)]
3634
pub struct ForkptyResult {
3735
/// The master port in a virtual pty pair
38-
pub master: RawFd,
36+
pub master: OwnedFd,
3937
/// Metadata about forked process
4038
pub fork_result: ForkResult,
4139
}
4240
}
4341

4442
/// Representation of the Master device in a master/slave pty pair
4543
///
46-
/// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY
47-
/// functions are given the correct file descriptor. Additionally this type implements `Drop`,
48-
/// so that when it's consumed or goes out of scope, it's automatically cleaned-up.
49-
#[derive(Debug, Eq, Hash, PartialEq)]
50-
pub struct PtyMaster(RawFd);
44+
/// While this datatype is a thin wrapper around `OwnedFd`, it enforces that the available PTY
45+
/// functions are given the correct file descriptor.
46+
#[derive(Debug)]
47+
pub struct PtyMaster(OwnedFd);
5148

5249
impl AsRawFd for PtyMaster {
5350
fn as_raw_fd(&self) -> RawFd {
54-
self.0
51+
self.0.as_raw_fd()
5552
}
5653
}
5754

5855
impl IntoRawFd for PtyMaster {
5956
fn into_raw_fd(self) -> RawFd {
6057
let fd = self.0;
61-
mem::forget(self);
62-
fd
63-
}
64-
}
65-
66-
impl Drop for PtyMaster {
67-
fn drop(&mut self) {
68-
// On drop, we ignore errors like EINTR and EIO because there's no clear
69-
// way to handle them, we can't return anything, and (on FreeBSD at
70-
// least) the file descriptor is deallocated in these cases. However,
71-
// we must panic on EBADF, because it is always an error to close an
72-
// invalid file descriptor. That frequently indicates a double-close
73-
// condition, which can cause confusing errors for future I/O
74-
// operations.
75-
let e = unistd::close(self.0);
76-
if e == Err(Errno::EBADF) {
77-
panic!("Closing an invalid file descriptor!");
78-
};
58+
fd.into_raw_fd()
7959
}
8060
}
8161

8262
impl io::Read for PtyMaster {
8363
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
84-
unistd::read(self.0, buf).map_err(io::Error::from)
64+
unistd::read(self.0.as_raw_fd(), buf).map_err(io::Error::from)
8565
}
8666
}
8767

8868
impl io::Write for PtyMaster {
8969
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
90-
unistd::write(self.0, buf).map_err(io::Error::from)
70+
unistd::write(self.0.as_raw_fd(), buf).map_err(io::Error::from)
9171
}
9272
fn flush(&mut self) -> io::Result<()> {
9373
Ok(())
@@ -96,13 +76,13 @@ impl io::Write for PtyMaster {
9676

9777
impl io::Read for &PtyMaster {
9878
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
99-
unistd::read(self.0, buf).map_err(io::Error::from)
79+
unistd::read(self.0.as_raw_fd(), buf).map_err(io::Error::from)
10080
}
10181
}
10282

10383
impl io::Write for &PtyMaster {
10484
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
105-
unistd::write(self.0, buf).map_err(io::Error::from)
85+
unistd::write(self.0.as_raw_fd(), buf).map_err(io::Error::from)
10686
}
10787
fn flush(&mut self) -> io::Result<()> {
10888
Ok(())
@@ -164,7 +144,7 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> {
164144
return Err(Errno::last());
165145
}
166146

167-
Ok(PtyMaster(fd))
147+
Ok(PtyMaster(unsafe { OwnedFd::from_raw_fd(fd) }))
168148
}
169149

170150
/// Get the name of the slave pseudoterminal (see
@@ -308,8 +288,8 @@ pub fn openpty<
308288

309289
unsafe {
310290
Ok(OpenptyResult {
311-
master: master.assume_init(),
312-
slave: slave.assume_init(),
291+
master: OwnedFd::from_raw_fd(master.assume_init()),
292+
slave: OwnedFd::from_raw_fd(slave.assume_init()),
313293
})
314294
}
315295
}
@@ -364,7 +344,7 @@ pub unsafe fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b T
364344
})?;
365345

366346
Ok(ForkptyResult {
367-
master: master.assume_init(),
347+
master: OwnedFd::from_raw_fd(master.assume_init()),
368348
fork_result,
369349
})
370350
}

src/sys/termios.rs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ use libc::{self, c_int, tcflag_t};
222222
use std::cell::{Ref, RefCell};
223223
use std::convert::From;
224224
use std::mem;
225-
use std::os::unix::io::RawFd;
225+
use std::os::unix::io::{AsFd, AsRawFd};
226226

227227
#[cfg(feature = "process")]
228228
use crate::unistd::Pid;
@@ -1143,10 +1143,12 @@ pub fn cfmakesane(termios: &mut Termios) {
11431143
/// `tcgetattr()` returns a `Termios` structure with the current configuration for a port. Modifying
11441144
/// this structure *will not* reconfigure the port, instead the modifications should be done to
11451145
/// the `Termios` structure and then the port should be reconfigured using `tcsetattr()`.
1146-
pub fn tcgetattr(fd: RawFd) -> Result<Termios> {
1146+
pub fn tcgetattr<Fd: AsFd>(fd: &Fd) -> Result<Termios> {
11471147
let mut termios = mem::MaybeUninit::uninit();
11481148

1149-
let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) };
1149+
let res = unsafe {
1150+
libc::tcgetattr(fd.as_fd().as_raw_fd(), termios.as_mut_ptr())
1151+
};
11501152

11511153
Errno::result(res)?;
11521154

@@ -1159,53 +1161,70 @@ pub fn tcgetattr(fd: RawFd) -> Result<Termios> {
11591161
/// `tcsetattr()` reconfigures the given port based on a given `Termios` structure. This change
11601162
/// takes affect at a time specified by `actions`. Note that this function may return success if
11611163
/// *any* of the parameters were successfully set, not only if all were set successfully.
1162-
pub fn tcsetattr(fd: RawFd, actions: SetArg, termios: &Termios) -> Result<()> {
1164+
pub fn tcsetattr<Fd: AsFd>(
1165+
fd: &Fd,
1166+
actions: SetArg,
1167+
termios: &Termios,
1168+
) -> Result<()> {
11631169
let inner_termios = termios.get_libc_termios();
11641170
Errno::result(unsafe {
1165-
libc::tcsetattr(fd, actions as c_int, &*inner_termios)
1171+
libc::tcsetattr(
1172+
fd.as_fd().as_raw_fd(),
1173+
actions as c_int,
1174+
&*inner_termios,
1175+
)
11661176
})
11671177
.map(drop)
11681178
}
11691179

11701180
/// Block until all output data is written (see
11711181
/// [tcdrain(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html)).
1172-
pub fn tcdrain(fd: RawFd) -> Result<()> {
1173-
Errno::result(unsafe { libc::tcdrain(fd) }).map(drop)
1182+
pub fn tcdrain<Fd: AsFd>(fd: &Fd) -> Result<()> {
1183+
Errno::result(unsafe { libc::tcdrain(fd.as_fd().as_raw_fd()) }).map(drop)
11741184
}
11751185

11761186
/// Suspend or resume the transmission or reception of data (see
11771187
/// [tcflow(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html)).
11781188
///
11791189
/// `tcflow()` suspends of resumes the transmission or reception of data for the given port
11801190
/// depending on the value of `action`.
1181-
pub fn tcflow(fd: RawFd, action: FlowArg) -> Result<()> {
1182-
Errno::result(unsafe { libc::tcflow(fd, action as c_int) }).map(drop)
1191+
pub fn tcflow<Fd: AsFd>(fd: &Fd, action: FlowArg) -> Result<()> {
1192+
Errno::result(unsafe {
1193+
libc::tcflow(fd.as_fd().as_raw_fd(), action as c_int)
1194+
})
1195+
.map(drop)
11831196
}
11841197

11851198
/// Discard data in the output or input queue (see
11861199
/// [tcflush(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html)).
11871200
///
11881201
/// `tcflush()` will discard data for a terminal port in the input queue, output queue, or both
11891202
/// depending on the value of `action`.
1190-
pub fn tcflush(fd: RawFd, action: FlushArg) -> Result<()> {
1191-
Errno::result(unsafe { libc::tcflush(fd, action as c_int) }).map(drop)
1203+
pub fn tcflush<Fd: AsFd>(fd: &Fd, action: FlushArg) -> Result<()> {
1204+
Errno::result(unsafe {
1205+
libc::tcflush(fd.as_fd().as_raw_fd(), action as c_int)
1206+
})
1207+
.map(drop)
11921208
}
11931209

11941210
/// Send a break for a specific duration (see
11951211
/// [tcsendbreak(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html)).
11961212
///
11971213
/// When using asynchronous data transmission `tcsendbreak()` will transmit a continuous stream
11981214
/// of zero-valued bits for an implementation-defined duration.
1199-
pub fn tcsendbreak(fd: RawFd, duration: c_int) -> Result<()> {
1200-
Errno::result(unsafe { libc::tcsendbreak(fd, duration) }).map(drop)
1215+
pub fn tcsendbreak<Fd: AsFd>(fd: &Fd, duration: c_int) -> Result<()> {
1216+
Errno::result(unsafe {
1217+
libc::tcsendbreak(fd.as_fd().as_raw_fd(), duration)
1218+
})
1219+
.map(drop)
12011220
}
12021221

12031222
feature! {
12041223
#![feature = "process"]
12051224
/// Get the session controlled by the given terminal (see
12061225
/// [tcgetsid(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetsid.html)).
1207-
pub fn tcgetsid(fd: RawFd) -> Result<Pid> {
1208-
let res = unsafe { libc::tcgetsid(fd) };
1226+
pub fn tcgetsid<Fd: AsFd>(fd: &Fd) -> Result<Pid> {
1227+
let res = unsafe { libc::tcgetsid(fd.as_fd().as_raw_fd()) };
12091228

12101229
Errno::result(res).map(Pid::from_raw)
12111230
}

test/sys/test_termios.rs

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
use std::os::unix::prelude::*;
1+
use std::os::unix::io::{AsFd, AsRawFd};
22
use tempfile::tempfile;
33

44
use nix::errno::Errno;
55
use nix::fcntl;
66
use nix::pty::openpty;
77
use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags};
8-
use nix::unistd::{close, read, write};
8+
use nix::unistd::{read, write};
99

10-
/// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s
11-
fn write_all(f: RawFd, buf: &[u8]) {
10+
/// Helper function analogous to `std::io::Write::write_all`, but for `Fd`s
11+
fn write_all<Fd: AsFd>(f: &Fd, buf: &[u8]) {
1212
let mut len = 0;
1313
while len < buf.len() {
14-
len += write(f, &buf[len..]).unwrap();
14+
len += write(f.as_fd().as_raw_fd(), &buf[len..]).unwrap();
1515
}
1616
}
1717

@@ -22,25 +22,14 @@ fn test_tcgetattr_pty() {
2222
let _m = crate::PTSNAME_MTX.lock();
2323

2424
let pty = openpty(None, None).expect("openpty failed");
25-
termios::tcgetattr(pty.slave).unwrap();
26-
close(pty.master).expect("closing the master failed");
27-
close(pty.slave).expect("closing the slave failed");
25+
termios::tcgetattr(&pty.slave).unwrap();
2826
}
2927

3028
// Test tcgetattr on something that isn't a terminal
3129
#[test]
3230
fn test_tcgetattr_enotty() {
3331
let file = tempfile().unwrap();
34-
assert_eq!(
35-
termios::tcgetattr(file.as_raw_fd()).err(),
36-
Some(Errno::ENOTTY)
37-
);
38-
}
39-
40-
// Test tcgetattr on an invalid file descriptor
41-
#[test]
42-
fn test_tcgetattr_ebadf() {
43-
assert_eq!(termios::tcgetattr(-1).err(), Some(Errno::EBADF));
32+
assert_eq!(termios::tcgetattr(&file).err(), Some(Errno::ENOTTY));
4433
}
4534

4635
// Test modifying output flags
@@ -52,12 +41,7 @@ fn test_output_flags() {
5241
// Open one pty to get attributes for the second one
5342
let mut termios = {
5443
let pty = openpty(None, None).expect("openpty failed");
55-
assert!(pty.master > 0);
56-
assert!(pty.slave > 0);
57-
let termios = tcgetattr(pty.slave).expect("tcgetattr failed");
58-
close(pty.master).unwrap();
59-
close(pty.slave).unwrap();
60-
termios
44+
tcgetattr(&pty.slave).expect("tcgetattr failed")
6145
};
6246

6347
// Make sure postprocessing '\r' isn't specified by default or this test is useless.
@@ -73,19 +57,15 @@ fn test_output_flags() {
7357

7458
// Open a pty
7559
let pty = openpty(None, &termios).unwrap();
76-
assert!(pty.master > 0);
77-
assert!(pty.slave > 0);
7860

7961
// Write into the master
8062
let string = "foofoofoo\r";
81-
write_all(pty.master, string.as_bytes());
63+
write_all(&pty.master, string.as_bytes());
8264

8365
// Read from the slave verifying that the output has been properly transformed
8466
let mut buf = [0u8; 10];
85-
crate::read_exact(pty.slave, &mut buf);
67+
crate::read_exact(&pty.slave, &mut buf);
8668
let transformed_string = "foofoofoo\n";
87-
close(pty.master).unwrap();
88-
close(pty.slave).unwrap();
8969
assert_eq!(&buf, transformed_string.as_bytes());
9070
}
9171

@@ -98,12 +78,7 @@ fn test_local_flags() {
9878
// Open one pty to get attributes for the second one
9979
let mut termios = {
10080
let pty = openpty(None, None).unwrap();
101-
assert!(pty.master > 0);
102-
assert!(pty.slave > 0);
103-
let termios = tcgetattr(pty.slave).unwrap();
104-
close(pty.master).unwrap();
105-
close(pty.slave).unwrap();
106-
termios
81+
tcgetattr(&pty.slave).unwrap()
10782
};
10883

10984
// Make sure echo is specified by default or this test is useless.
@@ -114,23 +89,19 @@ fn test_local_flags() {
11489

11590
// Open a new pty with our modified termios settings
11691
let pty = openpty(None, &termios).unwrap();
117-
assert!(pty.master > 0);
118-
assert!(pty.slave > 0);
11992

12093
// Set the master is in nonblocking mode or reading will never return.
121-
let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap();
94+
let flags = fcntl::fcntl(pty.master.as_raw_fd(), fcntl::F_GETFL).unwrap();
12295
let new_flags =
12396
fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK;
124-
fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap();
97+
fcntl::fcntl(pty.master.as_raw_fd(), fcntl::F_SETFL(new_flags)).unwrap();
12598

12699
// Write into the master
127100
let string = "foofoofoo\r";
128-
write_all(pty.master, string.as_bytes());
101+
write_all(&pty.master, string.as_bytes());
129102

130103
// Try to read from the master, which should not have anything as echoing was disabled.
131104
let mut buf = [0u8; 10];
132-
let read = read(pty.master, &mut buf).unwrap_err();
133-
close(pty.master).unwrap();
134-
close(pty.slave).unwrap();
105+
let read = read(pty.master.as_raw_fd(), &mut buf).unwrap_err();
135106
assert_eq!(read, Errno::EAGAIN);
136107
}

0 commit comments

Comments
 (0)