Skip to content

Commit 7b9d6d3

Browse files
committed
Auto merge of #31069 - sfackler:file-try-clone, r=alexcrichton
I have it set as stable right now under the rationale that it's extending an existing, stable API to another type in the "obvious" way. r? @alexcrichton cc @reem
2 parents 9c30f12 + a414b61 commit 7b9d6d3

File tree

5 files changed

+87
-39
lines changed

5 files changed

+87
-39
lines changed

src/libstd/fs.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,18 @@ impl File {
309309
pub fn metadata(&self) -> io::Result<Metadata> {
310310
self.inner.file_attr().map(Metadata)
311311
}
312+
313+
/// Creates a new independently owned handle to the underlying file.
314+
///
315+
/// The returned `File` is a reference to the same state that this object
316+
/// references. Both handles will read and write with the same cursor
317+
/// position.
318+
#[unstable(feature = "file_try_clone", reason = "newly added", issue = "31405")]
319+
pub fn try_clone(&self) -> io::Result<File> {
320+
Ok(File {
321+
inner: try!(self.inner.duplicate())
322+
})
323+
}
312324
}
313325

314326
impl AsInner<fs_imp::File> for File {
@@ -2283,6 +2295,28 @@ mod tests {
22832295
assert!(v == &bytes[..]);
22842296
}
22852297

2298+
#[test]
2299+
fn file_try_clone() {
2300+
let tmpdir = tmpdir();
2301+
2302+
let mut f1 = check!(OpenOptions::new()
2303+
.read(true)
2304+
.write(true)
2305+
.create(true)
2306+
.open(&tmpdir.join("test")));
2307+
let mut f2 = check!(f1.try_clone());
2308+
2309+
check!(f1.write_all(b"hello world"));
2310+
check!(f1.seek(SeekFrom::Start(2)));
2311+
2312+
let mut buf = vec![];
2313+
check!(f2.read_to_end(&mut buf));
2314+
assert_eq!(buf, b"llo world");
2315+
drop(f2);
2316+
2317+
check!(f1.write_all(b"!"));
2318+
}
2319+
22862320
#[test]
22872321
#[cfg(not(windows))]
22882322
fn unlink_readonly() {

src/libstd/sys/unix/fd.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use libc::{self, c_int, size_t, c_void};
1313
use mem;
1414
use sys::cvt;
1515
use sys_common::AsInner;
16+
use sync::atomic::{AtomicBool, Ordering};
1617

1718
pub struct FileDesc {
1819
fd: c_int,
@@ -65,6 +66,47 @@ impl FileDesc {
6566
debug_assert_eq!(ret, 0);
6667
}
6768
}
69+
70+
pub fn duplicate(&self) -> io::Result<FileDesc> {
71+
// We want to atomically duplicate this file descriptor and set the
72+
// CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This
73+
// flag, however, isn't supported on older Linux kernels (earlier than
74+
// 2.6.24).
75+
//
76+
// To detect this and ensure that CLOEXEC is still set, we
77+
// follow a strategy similar to musl [1] where if passing
78+
// F_DUPFD_CLOEXEC causes `fcntl` to return EINVAL it means it's not
79+
// supported (the third parameter, 0, is always valid), so we stop
80+
// trying that. We also *still* call the `set_cloexec` method as
81+
// apparently some kernel at some point stopped setting CLOEXEC even
82+
// though it reported doing so on F_DUPFD_CLOEXEC.
83+
//
84+
// Also note that Android doesn't have F_DUPFD_CLOEXEC, but get it to
85+
// resolve so we at least compile this.
86+
//
87+
// [1]: http://comments.gmane.org/gmane.linux.lib.musl.general/2963
88+
#[cfg(target_os = "android")]
89+
use libc::F_DUPFD as F_DUPFD_CLOEXEC;
90+
#[cfg(not(target_os = "android"))]
91+
use libc::F_DUPFD_CLOEXEC;
92+
93+
let make_filedesc = |fd| {
94+
let fd = FileDesc::new(fd);
95+
fd.set_cloexec();
96+
fd
97+
};
98+
static TRY_CLOEXEC: AtomicBool = AtomicBool::new(true);
99+
let fd = self.raw();
100+
if !cfg!(target_os = "android") && TRY_CLOEXEC.load(Ordering::Relaxed) {
101+
match cvt(unsafe { libc::fcntl(fd, F_DUPFD_CLOEXEC, 0) }) {
102+
Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {
103+
TRY_CLOEXEC.store(false, Ordering::Relaxed);
104+
}
105+
res => return res.map(make_filedesc),
106+
}
107+
}
108+
cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD, 0) }).map(make_filedesc)
109+
}
68110
}
69111

70112
impl AsInner<c_int> for FileDesc {

src/libstd/sys/unix/fs.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,10 @@ impl File {
414414
Ok(n as u64)
415415
}
416416

417+
pub fn duplicate(&self) -> io::Result<File> {
418+
self.0.duplicate().map(File)
419+
}
420+
417421
pub fn fd(&self) -> &FileDesc { &self.0 }
418422

419423
pub fn into_fd(self) -> FileDesc { self.0 }

src/libstd/sys/unix/net.rs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use io;
1515
use libc::{self, c_int, size_t};
1616
use net::{SocketAddr, Shutdown};
1717
use str;
18-
use sync::atomic::{AtomicBool, Ordering};
1918
use sys::fd::FileDesc;
2019
use sys_common::{AsInner, FromInner, IntoInner};
2120
use sys_common::net::{getsockopt, setsockopt};
@@ -67,44 +66,7 @@ impl Socket {
6766
}
6867

6968
pub fn duplicate(&self) -> io::Result<Socket> {
70-
// We want to atomically duplicate this file descriptor and set the
71-
// CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This
72-
// flag, however, isn't supported on older Linux kernels (earlier than
73-
// 2.6.24).
74-
//
75-
// To detect this and ensure that CLOEXEC is still set, we
76-
// follow a strategy similar to musl [1] where if passing
77-
// F_DUPFD_CLOEXEC causes `fcntl` to return EINVAL it means it's not
78-
// supported (the third parameter, 0, is always valid), so we stop
79-
// trying that. We also *still* call the `set_cloexec` method as
80-
// apparently some kernel at some point stopped setting CLOEXEC even
81-
// though it reported doing so on F_DUPFD_CLOEXEC.
82-
//
83-
// Also note that Android doesn't have F_DUPFD_CLOEXEC, but get it to
84-
// resolve so we at least compile this.
85-
//
86-
// [1]: http://comments.gmane.org/gmane.linux.lib.musl.general/2963
87-
#[cfg(target_os = "android")]
88-
use libc::F_DUPFD as F_DUPFD_CLOEXEC;
89-
#[cfg(not(target_os = "android"))]
90-
use libc::F_DUPFD_CLOEXEC;
91-
92-
let make_socket = |fd| {
93-
let fd = FileDesc::new(fd);
94-
fd.set_cloexec();
95-
Socket(fd)
96-
};
97-
static TRY_CLOEXEC: AtomicBool = AtomicBool::new(true);
98-
let fd = self.0.raw();
99-
if !cfg!(target_os = "android") && TRY_CLOEXEC.load(Ordering::Relaxed) {
100-
match cvt(unsafe { libc::fcntl(fd, F_DUPFD_CLOEXEC, 0) }) {
101-
Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => {
102-
TRY_CLOEXEC.store(false, Ordering::Relaxed);
103-
}
104-
res => return res.map(make_socket),
105-
}
106-
}
107-
cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD, 0) }).map(make_socket)
69+
self.0.duplicate().map(Socket)
10870
}
10971

11072
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {

src/libstd/sys/windows/fs.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,12 @@ impl File {
338338
Ok(newpos as u64)
339339
}
340340

341+
pub fn duplicate(&self) -> io::Result<File> {
342+
Ok(File {
343+
handle: try!(self.handle.duplicate(0, true, c::DUPLICATE_SAME_ACCESS)),
344+
})
345+
}
346+
341347
pub fn handle(&self) -> &Handle { &self.handle }
342348

343349
pub fn into_handle(self) -> Handle { self.handle }

0 commit comments

Comments
 (0)