]> git.lizzy.rs Git - rust.git/commitdiff
Implement reading and writing atomically at certain offsets
authorTobias Bucher <tobiasbucher5991@gmail.com>
Mon, 15 Aug 2016 23:11:33 +0000 (01:11 +0200)
committerTobias Bucher <tobiasbucher5991@gmail.com>
Sun, 9 Oct 2016 08:48:07 +0000 (10:48 +0200)
These functions allow to read from and write to a file in one atomic
action from multiple threads, avoiding the race between the seek and the
read.

The functions are named `{read,write}_at` on non-Windows (which don't
change the file cursor), and `seek_{read,write}` on Windows (which
change the file cursor).

src/libstd/fs.rs
src/libstd/sys/unix/ext/fs.rs
src/libstd/sys/unix/ext/mod.rs
src/libstd/sys/unix/fd.rs
src/libstd/sys/unix/fs.rs
src/libstd/sys/windows/ext/fs.rs
src/libstd/sys/windows/ext/mod.rs
src/libstd/sys/windows/fs.rs
src/libstd/sys/windows/handle.rs

index 2f2969b110db17a7687b81802ce6409013526277..5bb5183fd6a93b5b3de8fe690e6ca5a3e73c262e 100644 (file)
@@ -1903,6 +1903,130 @@ fn file_test_io_seek_shakedown() {
         check!(fs::remove_file(filename));
     }
 
+    #[test]
+    fn file_test_io_eof() {
+        let tmpdir = tmpdir();
+        let filename = tmpdir.join("file_rt_io_file_test_eof.txt");
+        let mut buf = [0; 256];
+        {
+            let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
+            let mut rw = check!(oo.open(&filename));
+            assert_eq!(check!(rw.read(&mut buf)), 0);
+            assert_eq!(check!(rw.read(&mut buf)), 0);
+        }
+        check!(fs::remove_file(&filename));
+    }
+
+    #[test]
+    #[cfg(unix)]
+    fn file_test_io_read_write_at() {
+        use os::unix::fs::FileExt;
+
+        let tmpdir = tmpdir();
+        let filename = tmpdir.join("file_rt_io_file_test_read_write_at.txt");
+        let mut buf = [0; 256];
+        let write1 = "asdf";
+        let write2 = "qwer-";
+        let write3 = "-zxcv";
+        let content = "qwer-asdf-zxcv";
+        {
+            let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
+            let mut rw = check!(oo.open(&filename));
+            assert_eq!(check!(rw.write_at(write1.as_bytes(), 5)), write1.len());
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
+            assert_eq!(check!(rw.read_at(&mut buf, 5)), write1.len());
+            assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
+            assert_eq!(check!(rw.read_at(&mut buf[..write2.len()], 0)), write2.len());
+            assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok("\0\0\0\0\0"));
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
+            assert_eq!(check!(rw.write(write2.as_bytes())), write2.len());
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
+            assert_eq!(check!(rw.read(&mut buf)), write1.len());
+            assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
+            assert_eq!(check!(rw.read_at(&mut buf[..write2.len()], 0)), write2.len());
+            assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2));
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
+            assert_eq!(check!(rw.write_at(write3.as_bytes(), 9)), write3.len());
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
+        }
+        {
+            let mut read = check!(File::open(&filename));
+            assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
+            assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 0);
+            assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
+            assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
+            assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 9);
+            assert_eq!(check!(read.read(&mut buf)), write3.len());
+            assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3));
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
+            assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
+            assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
+            assert_eq!(check!(read.read_at(&mut buf, 14)), 0);
+            assert_eq!(check!(read.read_at(&mut buf, 15)), 0);
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
+        }
+        check!(fs::remove_file(&filename));
+    }
+
+    #[test]
+    #[cfg(windows)]
+    fn file_test_io_seek_read_write() {
+        use os::windows::fs::FileExt;
+
+        let tmpdir = tmpdir();
+        let filename = tmpdir.join("file_rt_io_file_test_seek_read_write.txt");
+        let mut buf = [0; 256];
+        let write1 = "asdf";
+        let write2 = "qwer-";
+        let write3 = "-zxcv";
+        let content = "qwer-asdf-zxcv";
+        {
+            let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
+            let mut rw = check!(oo.open(&filename));
+            assert_eq!(check!(rw.seek_write(write1.as_bytes(), 5)), write1.len());
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
+            assert_eq!(check!(rw.seek_read(&mut buf, 5)), write1.len());
+            assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
+            assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0);
+            assert_eq!(check!(rw.write(write2.as_bytes())), write2.len());
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
+            assert_eq!(check!(rw.read(&mut buf)), write1.len());
+            assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
+            assert_eq!(check!(rw.seek_read(&mut buf[..write2.len()], 0)), write2.len());
+            assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2));
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
+            assert_eq!(check!(rw.seek_write(write3.as_bytes(), 9)), write3.len());
+            assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 14);
+        }
+        {
+            let mut read = check!(File::open(&filename));
+            assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
+            assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
+            assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
+            assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
+            assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
+            assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
+            assert_eq!(check!(read.read(&mut buf)), write3.len());
+            assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3));
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
+            assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
+            assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
+            assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
+            assert_eq!(check!(read.seek_read(&mut buf, 14)), 0);
+            assert_eq!(check!(read.seek_read(&mut buf, 15)), 0);
+        }
+        check!(fs::remove_file(&filename));
+    }
+
     #[test]
     fn file_test_stat_is_correct_on_is_file() {
         let tmpdir = tmpdir();
@@ -2221,8 +2345,8 @@ fn copy_file_preserves_perm_bits() {
         check!(fs::set_permissions(&out, attr.permissions()));
     }
 
-    #[cfg(windows)]
     #[test]
+    #[cfg(windows)]
     fn copy_file_preserves_streams() {
         let tmp = tmpdir();
         check!(check!(File::create(tmp.join("in.txt:bunny"))).write("carrot".as_bytes()));
index 77587918ac94b0e83d9edb6f912d3227c54f719c..fcfab051588416ce93087ca3456a9fe974c6b5d5 100644 (file)
 use sys_common::{FromInner, AsInner, AsInnerMut};
 use sys::platform::fs::MetadataExt as UnixMetadataExt;
 
+/// Unix-specific extensions to `File`
+#[unstable(feature = "file_offset", issue = "35918")]
+pub trait FileExt {
+    /// Reads a number of bytes starting from a given offset.
+    ///
+    /// Returns the number of bytes read.
+    ///
+    /// The offset is relative to the start of the file and thus independent
+    /// from the current cursor.
+    ///
+    /// The current file cursor is not affected by this function.
+    ///
+    /// Note that similar to `File::read`, it is not an error to return with a
+    /// short read.
+    #[unstable(feature = "file_offset", issue = "35918")]
+    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
+
+    /// Writes a number of bytes starting from a given offset.
+    ///
+    /// Returns the number of bytes written.
+    ///
+    /// The offset is relative to the start of the file and thus independent
+    /// from the current cursor.
+    ///
+    /// The current file cursor is not affected by this function.
+    ///
+    /// When writing beyond the end of the file, the file is appropiately
+    /// extended and the intermediate bytes are initialized with the value 0.
+    ///
+    /// Note that similar to `File::write`, it is not an error to return a
+    /// short write.
+    #[unstable(feature = "file_offset", issue = "35918")]
+    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
+}
+
+#[unstable(feature = "file_offset", issue = "35918")]
+impl FileExt for fs::File {
+    fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+        self.as_inner().read_at(buf, offset)
+    }
+    fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        self.as_inner().write_at(buf, offset)
+    }
+}
+
 /// Unix-specific extensions to `Permissions`
 #[stable(feature = "fs_ext", since = "1.1.0")]
 pub trait PermissionsExt {
index 1be3d75d866dd7123c663604a142fb9f8ffcfeed..b2483f4e209378094a18ea43a57cdd814b4150fd 100644 (file)
@@ -50,6 +50,8 @@ pub mod prelude {
     pub use super::fs::{PermissionsExt, OpenOptionsExt, MetadataExt, FileTypeExt};
     #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
     pub use super::fs::DirEntryExt;
+    #[doc(no_inline)] #[unstable(feature = "file_offset", issue = "35918")]
+    pub use super::fs::FileExt;
     #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
     pub use super::thread::JoinHandleExt;
     #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
index eadf98867a6b7ad78947b1e61b9ad36022539ad3..4f92c3274d2e68e4a78ba4fae8d0b44ae17f34ba 100644 (file)
 use sys_common::AsInner;
 use sys_common::io::read_to_end_uninitialized;
 
+#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
+use libc::{pread64, pwrite64, off64_t};
+#[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))]
+use libc::{pread as pread64, pwrite as pwrite64, off_t as off64_t};
+
 pub struct FileDesc {
     fd: c_int,
 }
@@ -50,6 +55,16 @@ pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
         (&mut me).read_to_end(buf)
     }
 
+    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+        let ret = cvt(unsafe {
+            pread64(self.fd,
+                    buf.as_mut_ptr() as *mut c_void,
+                    buf.len(),
+                    offset as off64_t)
+        })?;
+        Ok(ret as usize)
+    }
+
     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
         let ret = cvt(unsafe {
             libc::write(self.fd,
@@ -59,6 +74,16 @@ pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
         Ok(ret as usize)
     }
 
+    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        let ret = cvt(unsafe {
+            pwrite64(self.fd,
+                     buf.as_ptr() as *const c_void,
+                     buf.len(),
+                     offset as off64_t)
+        })?;
+        Ok(ret as usize)
+    }
+
     #[cfg(not(any(target_env = "newlib",
                   target_os = "solaris",
                   target_os = "emscripten",
index 606e2c2264ad0ac303553654467de26738ba09a0..fe8cbc84215721bcc1cbdb117b750590f13df47f 100644 (file)
@@ -483,10 +483,18 @@ pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
         self.0.read_to_end(buf)
     }
 
+    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+        self.0.read_at(buf, offset)
+    }
+
     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
         self.0.write(buf)
     }
 
+    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        self.0.write_at(buf, offset)
+    }
+
     pub fn flush(&self) -> io::Result<()> { Ok(()) }
 
     pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
index 4388a0bdff2cfb0f7f31920b021cd8221cbae0ea..1e2b8bf38fa719b4300dd59b29f9fc3b7a86a57f 100644 (file)
 
 #![stable(feature = "rust1", since = "1.0.0")]
 
-use fs::{OpenOptions, Metadata};
+use fs::{self, OpenOptions, Metadata};
 use io;
 use path::Path;
 use sys;
 use sys_common::{AsInnerMut, AsInner};
 
+/// Windows-specific extensions to `File`
+#[unstable(feature = "file_offset", issue = "35918")]
+pub trait FileExt {
+    /// Seeks to a given position and reads a number of bytes.
+    ///
+    /// Returns the number of bytes read.
+    ///
+    /// The offset is relative to the start of the file and thus independent
+    /// from the current cursor. The current cursor **is** affected by this
+    /// function, it is set to the end of the read.
+    ///
+    /// Reading beyond the end of the file will always return with a length of
+    /// 0.
+    ///
+    /// Note that similar to `File::read`, it is not an error to return with a
+    /// short read. When returning from such a short read, the file pointer is
+    /// still updated.
+    #[unstable(feature = "file_offset", issue = "35918")]
+    fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
+
+    /// Seeks to a given position and writes a number of bytes.
+    ///
+    /// Returns the number of bytes written.
+    ///
+    /// The offset is relative to the start of the file and thus independent
+    /// from the current cursor. The current cursor **is** affected by this
+    /// function, it is set to the end of the write.
+    ///
+    /// When writing beyond the end of the file, the file is appropiately
+    /// extended and the intermediate bytes are left uninitialized.
+    ///
+    /// Note that similar to `File::write`, it is not an error to return a
+    /// short write. When returning from such a short write, the file pointer
+    /// is still updated.
+    #[unstable(feature = "file_offset", issue = "35918")]
+    fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
+}
+
+#[unstable(feature = "file_offset", issue = "35918")]
+impl FileExt for fs::File {
+    fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+        self.as_inner().read_at(buf, offset)
+    }
+
+    fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        self.as_inner().write_at(buf, offset)
+    }
+}
+
 /// Windows-specific extensions to `OpenOptions`
 #[stable(feature = "open_options_ext", since = "1.10.0")]
 pub trait OpenOptionsExt {
index c3578fdfdb16ba452c68051f3ddb1cb7e2ff14fa..932bb5e95640531fdb26ea1f52140944895d0156 100644 (file)
@@ -36,4 +36,6 @@ pub mod prelude {
     pub use super::ffi::{OsStrExt, OsStringExt};
     #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
     pub use super::fs::{OpenOptionsExt, MetadataExt};
+    #[doc(no_inline)] #[unstable(feature = "file_offset", issue = "35918")]
+    pub use super::fs::FileExt;
 }
index 90a16853d56dd43c60894d2648ffd5818b14bc3a..a927eae020dcf9849f5a85d6e1654b3735bc2dc2 100644 (file)
@@ -311,6 +311,10 @@ pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
         self.handle.read(buf)
     }
 
+    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+        self.handle.read_at(buf, offset)
+    }
+
     pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
         self.handle.read_to_end(buf)
     }
@@ -319,6 +323,10 @@ pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
         self.handle.write(buf)
     }
 
+    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        self.handle.write_at(buf, offset)
+    }
+
     pub fn flush(&self) -> io::Result<()> { Ok(()) }
 
     pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
index 97e746ee345763cc75e3723c5fbfb6f0d7a4deb5..10b86ba44bc793a734e4131e323fd09c261a46e3 100644 (file)
@@ -104,6 +104,23 @@ pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
         }
     }
 
+    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+        let mut read = 0;
+        let len = cmp::min(buf.len(), <c::DWORD>::max_value() as usize) as c::DWORD;
+        let res = unsafe {
+            let mut overlapped: c::OVERLAPPED = mem::zeroed();
+            overlapped.Offset = offset as u32;
+            overlapped.OffsetHigh = (offset >> 32) as u32;
+            cvt(c::ReadFile(self.0, buf.as_mut_ptr() as c::LPVOID,
+                            len, &mut read, &mut overlapped))
+        };
+        match res {
+            Ok(_) => Ok(read as usize),
+            Err(ref e) if e.raw_os_error() == Some(c::ERROR_HANDLE_EOF as i32) => Ok(0),
+            Err(e) => Err(e),
+        }
+    }
+
     pub unsafe fn read_overlapped(&self,
                                   buf: &mut [u8],
                                   overlapped: *mut c::OVERLAPPED)
@@ -174,6 +191,19 @@ pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
         Ok(amt as usize)
     }
 
+    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
+        let mut written = 0;
+        let len = cmp::min(buf.len(), <c::DWORD>::max_value() as usize) as c::DWORD;
+        unsafe {
+            let mut overlapped: c::OVERLAPPED = mem::zeroed();
+            overlapped.Offset = offset as u32;
+            overlapped.OffsetHigh = (offset >> 32) as u32;
+            cvt(c::WriteFile(self.0, buf.as_ptr() as c::LPVOID,
+                             len, &mut written, &mut overlapped))?;
+        }
+        Ok(written as usize)
+    }
+
     pub fn duplicate(&self, access: c::DWORD, inherit: bool,
                      options: c::DWORD) -> io::Result<Handle> {
         let mut ret = 0 as c::HANDLE;