Skip to content

Commit 5d29650

Browse files
committed
Merge #677
677: PtyMaster::drop should panic on EBADF r=asomers Fixes #659
2 parents f84a008 + 5fb4ceb commit 5d29650

File tree

5 files changed

+86
-4
lines changed

5 files changed

+86
-4
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ test = true
4848
name = "test-mount"
4949
path = "test/test_mount.rs"
5050
harness = false
51+
52+
[[test]]
53+
name = "test-ptymaster-drop"
54+
path = "test/test_ptymaster_drop.rs"

src/pty.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,17 @@ impl IntoRawFd for PtyMaster {
4545

4646
impl Drop for PtyMaster {
4747
fn drop(&mut self) {
48-
// Errors when closing are ignored because we don't actually know if the file descriptor
49-
// was closed. If we retried, it's possible that descriptor was reallocated in the mean
50-
// time and the wrong file descriptor could be closed.
51-
let _ = ::unistd::close(self.0);
48+
// On drop, we ignore errors like EINTR and EIO because there's no clear
49+
// way to handle them, we can't return anything, and (on FreeBSD at
50+
// least) the file descriptor is deallocated in these cases. However,
51+
// we must panic on EBADF, because it is always an error to close an
52+
// invalid file descriptor. That frequently indicates a double-close
53+
// condition, which can cause confusing errors for future I/O
54+
// operations.
55+
let e = ::unistd::close(self.0);
56+
if e == Err(Error::Sys(Errno::EBADF)) {
57+
panic!("Closing an invalid file descriptor!");
58+
};
5259
}
5360
}
5461

src/unistd.rs

+34
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,40 @@ pub fn gethostname<'a>(buffer: &'a mut [u8]) -> Result<&'a CStr> {
670670
})
671671
}
672672

673+
/// Close a raw file descriptor
674+
///
675+
/// Be aware that many Rust types implicitly close-on-drop, including
676+
/// `std::fs::File`. Explicitly closing them with this method too can result in
677+
/// a double-close condition, which can cause confusing `EBADF` errors in
678+
/// seemingly unrelated code. Caveat programmer.
679+
///
680+
/// # Examples
681+
///
682+
/// ```no_run
683+
/// extern crate tempfile;
684+
/// extern crate nix;
685+
///
686+
/// use std::os::unix::io::AsRawFd;
687+
/// use nix::unistd::close;
688+
///
689+
/// fn main() {
690+
/// let mut f = tempfile::tempfile().unwrap();
691+
/// close(f.as_raw_fd()).unwrap(); // Bad! f will also close on drop!
692+
/// }
693+
/// ```
694+
///
695+
/// ```rust
696+
/// extern crate tempfile;
697+
/// extern crate nix;
698+
///
699+
/// use std::os::unix::io::IntoRawFd;
700+
/// use nix::unistd::close;
701+
///
702+
/// fn main() {
703+
/// let mut f = tempfile::tempfile().unwrap();
704+
/// close(f.into_raw_fd()).unwrap(); // Good. into_raw_fd consumes f
705+
/// }
706+
/// ```
673707
pub fn close(fd: RawFd) -> Result<()> {
674708
let res = unsafe { libc::close(fd) };
675709
Errno::result(res).map(drop)

test/test_pty.rs

+16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1+
use std::io::Write;
12
use std::path::Path;
23
use std::os::unix::prelude::*;
4+
use tempfile::tempfile;
35

46
use nix::fcntl::{O_RDWR, open};
57
use nix::pty::*;
68
use nix::sys::stat;
79
use nix::sys::termios::*;
810
use nix::unistd::{write, close};
911

12+
/// Regression test for Issue #659
13+
/// This is the correct way to explicitly close a PtyMaster
14+
#[test]
15+
fn test_explicit_close() {
16+
let mut f = {
17+
let m = posix_openpt(O_RDWR).unwrap();
18+
close(m.into_raw_fd()).unwrap();
19+
tempfile().unwrap()
20+
};
21+
// This should work. But if there's been a double close, then it will
22+
// return EBADF
23+
f.write(b"whatever").unwrap();
24+
}
25+
1026
/// Test equivalence of `ptsname` and `ptsname_r`
1127
#[test]
1228
#[cfg(any(target_os = "android", target_os = "linux"))]

test/test_ptymaster_drop.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
extern crate nix;
2+
3+
use nix::fcntl::O_RDWR;
4+
use nix::pty::*;
5+
use nix::unistd::close;
6+
use std::os::unix::io::AsRawFd;
7+
8+
/// Regression test for Issue #659
9+
/// PtyMaster should panic rather than double close the file descriptor
10+
/// This must run in its own test process because it deliberately creates a race
11+
/// condition.
12+
#[test]
13+
#[should_panic(expected = "Closing an invalid file descriptor!")]
14+
// In Travis on i686-unknown-linux-musl, this test gets SIGABRT. I don't know
15+
// why. It doesn't happen on any other target, and it doesn't happen on my PC.
16+
#[cfg_attr(all(target_env = "musl", target_arch = "x86"), ignore)]
17+
fn test_double_close() {
18+
let m = posix_openpt(O_RDWR).unwrap();
19+
close(m.as_raw_fd()).unwrap();
20+
drop(m); // should panic here
21+
}

0 commit comments

Comments
 (0)