From a414b61f9279b2806ab92ec17cdd19cf57d59e2f Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Wed, 20 Jan 2016 21:24:23 -0800 Subject: [PATCH] Add File::try_clone --- src/libstd/fs.rs | 34 +++++++++++++++++++++++++++++ src/libstd/sys/unix/fd.rs | 42 ++++++++++++++++++++++++++++++++++++ src/libstd/sys/unix/fs.rs | 4 ++++ src/libstd/sys/unix/net.rs | 40 +--------------------------------- src/libstd/sys/windows/fs.rs | 6 ++++++ 5 files changed, 87 insertions(+), 39 deletions(-) diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index 414a0ebd11fa2..6c57fd6a4c9f2 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -309,6 +309,18 @@ impl File { pub fn metadata(&self) -> io::Result { self.inner.file_attr().map(Metadata) } + + /// Creates a new independently owned handle to the underlying file. + /// + /// The returned `File` is a reference to the same state that this object + /// references. Both handles will read and write with the same cursor + /// position. + #[unstable(feature = "file_try_clone", reason = "newly added", issue = "31405")] + pub fn try_clone(&self) -> io::Result { + Ok(File { + inner: try!(self.inner.duplicate()) + }) + } } impl AsInner for File { @@ -2283,6 +2295,28 @@ mod tests { assert!(v == &bytes[..]); } + #[test] + fn file_try_clone() { + let tmpdir = tmpdir(); + + let mut f1 = check!(OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&tmpdir.join("test"))); + let mut f2 = check!(f1.try_clone()); + + check!(f1.write_all(b"hello world")); + check!(f1.seek(SeekFrom::Start(2))); + + let mut buf = vec![]; + check!(f2.read_to_end(&mut buf)); + assert_eq!(buf, b"llo world"); + drop(f2); + + check!(f1.write_all(b"!")); + } + #[test] #[cfg(not(windows))] fn unlink_readonly() { diff --git a/src/libstd/sys/unix/fd.rs b/src/libstd/sys/unix/fd.rs index e09f4ca27bca6..1492613c53c63 100644 --- a/src/libstd/sys/unix/fd.rs +++ b/src/libstd/sys/unix/fd.rs @@ -13,6 +13,7 @@ use libc::{self, c_int, size_t, c_void}; use mem; use sys::cvt; use sys_common::AsInner; +use sync::atomic::{AtomicBool, Ordering}; pub struct FileDesc { fd: c_int, @@ -65,6 +66,47 @@ impl FileDesc { debug_assert_eq!(ret, 0); } } + + pub fn duplicate(&self) -> io::Result { + // We want to atomically duplicate this file descriptor and set the + // CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This + // flag, however, isn't supported on older Linux kernels (earlier than + // 2.6.24). + // + // To detect this and ensure that CLOEXEC is still set, we + // follow a strategy similar to musl [1] where if passing + // F_DUPFD_CLOEXEC causes `fcntl` to return EINVAL it means it's not + // supported (the third parameter, 0, is always valid), so we stop + // trying that. We also *still* call the `set_cloexec` method as + // apparently some kernel at some point stopped setting CLOEXEC even + // though it reported doing so on F_DUPFD_CLOEXEC. + // + // Also note that Android doesn't have F_DUPFD_CLOEXEC, but get it to + // resolve so we at least compile this. + // + // [1]: http://comments.gmane.org/gmane.linux.lib.musl.general/2963 + #[cfg(target_os = "android")] + use libc::F_DUPFD as F_DUPFD_CLOEXEC; + #[cfg(not(target_os = "android"))] + use libc::F_DUPFD_CLOEXEC; + + let make_filedesc = |fd| { + let fd = FileDesc::new(fd); + fd.set_cloexec(); + fd + }; + static TRY_CLOEXEC: AtomicBool = AtomicBool::new(true); + let fd = self.raw(); + if !cfg!(target_os = "android") && TRY_CLOEXEC.load(Ordering::Relaxed) { + match cvt(unsafe { libc::fcntl(fd, F_DUPFD_CLOEXEC, 0) }) { + Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => { + TRY_CLOEXEC.store(false, Ordering::Relaxed); + } + res => return res.map(make_filedesc), + } + } + cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD, 0) }).map(make_filedesc) + } } impl AsInner for FileDesc { diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs index d2d2ce35d84a0..e0212ca533542 100644 --- a/src/libstd/sys/unix/fs.rs +++ b/src/libstd/sys/unix/fs.rs @@ -374,6 +374,10 @@ impl File { Ok(n as u64) } + pub fn duplicate(&self) -> io::Result { + self.0.duplicate().map(File) + } + pub fn fd(&self) -> &FileDesc { &self.0 } pub fn into_fd(self) -> FileDesc { self.0 } diff --git a/src/libstd/sys/unix/net.rs b/src/libstd/sys/unix/net.rs index bcda1d651d69d..0aa43803048f3 100644 --- a/src/libstd/sys/unix/net.rs +++ b/src/libstd/sys/unix/net.rs @@ -15,7 +15,6 @@ use io; use libc::{self, c_int, size_t}; use net::{SocketAddr, Shutdown}; use str; -use sync::atomic::{AtomicBool, Ordering}; use sys::fd::FileDesc; use sys_common::{AsInner, FromInner, IntoInner}; use sys_common::net::{getsockopt, setsockopt}; @@ -67,44 +66,7 @@ impl Socket { } pub fn duplicate(&self) -> io::Result { - // We want to atomically duplicate this file descriptor and set the - // CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This - // flag, however, isn't supported on older Linux kernels (earlier than - // 2.6.24). - // - // To detect this and ensure that CLOEXEC is still set, we - // follow a strategy similar to musl [1] where if passing - // F_DUPFD_CLOEXEC causes `fcntl` to return EINVAL it means it's not - // supported (the third parameter, 0, is always valid), so we stop - // trying that. We also *still* call the `set_cloexec` method as - // apparently some kernel at some point stopped setting CLOEXEC even - // though it reported doing so on F_DUPFD_CLOEXEC. - // - // Also note that Android doesn't have F_DUPFD_CLOEXEC, but get it to - // resolve so we at least compile this. - // - // [1]: http://comments.gmane.org/gmane.linux.lib.musl.general/2963 - #[cfg(target_os = "android")] - use libc::F_DUPFD as F_DUPFD_CLOEXEC; - #[cfg(not(target_os = "android"))] - use libc::F_DUPFD_CLOEXEC; - - let make_socket = |fd| { - let fd = FileDesc::new(fd); - fd.set_cloexec(); - Socket(fd) - }; - static TRY_CLOEXEC: AtomicBool = AtomicBool::new(true); - let fd = self.0.raw(); - if !cfg!(target_os = "android") && TRY_CLOEXEC.load(Ordering::Relaxed) { - match cvt(unsafe { libc::fcntl(fd, F_DUPFD_CLOEXEC, 0) }) { - Err(ref e) if e.raw_os_error() == Some(libc::EINVAL) => { - TRY_CLOEXEC.store(false, Ordering::Relaxed); - } - res => return res.map(make_socket), - } - } - cvt(unsafe { libc::fcntl(fd, libc::F_DUPFD, 0) }).map(make_socket) + self.0.duplicate().map(Socket) } pub fn read(&self, buf: &mut [u8]) -> io::Result { diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs index 60e3f7c22bd58..f5981baf14187 100644 --- a/src/libstd/sys/windows/fs.rs +++ b/src/libstd/sys/windows/fs.rs @@ -338,6 +338,12 @@ impl File { Ok(newpos as u64) } + pub fn duplicate(&self) -> io::Result { + Ok(File { + handle: try!(self.handle.duplicate(0, true, c::DUPLICATE_SAME_ACCESS)), + }) + } + pub fn handle(&self) -> &Handle { &self.handle } pub fn into_handle(self) -> Handle { self.handle }