Skip to content

Commit

Permalink
Auto merge of #35704 - tbu-:pr_pread_pwrite, r=alexcrichton
Browse files Browse the repository at this point in the history
Implement `read_offset` and `write_offset`

These functions allow to read from and write to a file from multiple
threads without changing the per-file cursor, avoiding the race between
the seek and the read.
  • Loading branch information
bors authored Oct 14, 2016
2 parents 40cd1fd + 94aa08b commit 8c09454
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 8 deletions.
126 changes: 125 additions & 1 deletion src/libstd/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1903,6 +1903,130 @@ mod tests {
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();
Expand Down Expand Up @@ -2221,8 +2345,8 @@ mod tests {
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()));
Expand Down
62 changes: 56 additions & 6 deletions src/libstd/sys/unix/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
#![cfg(target_os = "android")]

use libc::{c_int, sighandler_t};
use libc::{c_int, c_void, sighandler_t, size_t, ssize_t};
use libc::{ftruncate, pread, pwrite};

use convert::TryInto;
use io;
use sys::cvt_r;
use super::{cvt, cvt_r};

// The `log2` and `log2f` functions apparently appeared in android-18, or at
// least you can see they're not present in the android-17 header [1] and they
Expand Down Expand Up @@ -96,13 +98,10 @@ pub unsafe fn signal(signum: c_int, handler: sighandler_t) -> sighandler_t {
//
// If it doesn't we just fall back to `ftruncate`, generating an error for
// too-large values.
#[cfg(target_pointer_width = "32")]
pub fn ftruncate64(fd: c_int, size: u64) -> io::Result<()> {
weak!(fn ftruncate64(c_int, i64) -> c_int);

extern {
fn ftruncate(fd: c_int, off: i32) -> c_int;
}

unsafe {
match ftruncate64.get() {
Some(f) => cvt_r(|| f(fd, size as i64)).map(|_| ()),
Expand All @@ -117,3 +116,54 @@ pub fn ftruncate64(fd: c_int, size: u64) -> io::Result<()> {
}
}
}

#[cfg(target_pointer_width = "64")]
pub fn ftruncate64(fd: c_int, size: u64) -> io::Result<()> {
unsafe {
cvt_r(|| ftruncate(fd, size as i64)).map(|_| ())
}
}

#[cfg(target_pointer_width = "32")]
pub unsafe fn cvt_pread64(fd: c_int, buf: *mut c_void, count: size_t, offset: i64)
-> io::Result<ssize_t>
{
weak!(fn pread64(c_int, *mut c_void, size_t, i64) -> ssize_t);
pread64.get().map(|f| cvt(f(fd, buf, count, offset))).unwrap_or_else(|| {
if let Ok(o) = offset.try_into() {
cvt(pread(fd, buf, count, o))
} else {
Err(io::Error::new(io::ErrorKind::InvalidInput,
"cannot pread >2GB"))
}
})
}

#[cfg(target_pointer_width = "32")]
pub unsafe fn cvt_pwrite64(fd: c_int, buf: *const c_void, count: size_t, offset: i64)
-> io::Result<ssize_t>
{
weak!(fn pwrite64(c_int, *const c_void, size_t, i64) -> ssize_t);
pwrite64.get().map(|f| cvt(f(fd, buf, count, offset))).unwrap_or_else(|| {
if let Ok(o) = offset.try_into() {
cvt(pwrite(fd, buf, count, o))
} else {
Err(io::Error::new(io::ErrorKind::InvalidInput,
"cannot pwrite >2GB"))
}
})
}

#[cfg(target_pointer_width = "64")]
pub unsafe fn cvt_pread64(fd: c_int, buf: *mut c_void, count: size_t, offset: i64)
-> io::Result<ssize_t>
{
cvt(pread(fd, buf, count, offset))
}

#[cfg(target_pointer_width = "64")]
pub unsafe fn cvt_pwrite64(fd: c_int, buf: *const c_void, count: size_t, offset: i64)
-> io::Result<ssize_t>
{
cvt(pwrite(fd, buf, count, offset))
}
45 changes: 45 additions & 0 deletions src/libstd/sys/unix/ext/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,51 @@ use sys;
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 {
Expand Down
2 changes: 2 additions & 0 deletions src/libstd/sys/unix/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
48 changes: 48 additions & 0 deletions src/libstd/sys/unix/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ impl FileDesc {
(&mut me).read_to_end(buf)
}

pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
#[cfg(target_os = "android")]
use super::android::cvt_pread64;

#[cfg(not(target_os = "android"))]
unsafe fn cvt_pread64(fd: c_int, buf: *mut c_void, count: usize, offset: i64)
-> io::Result<isize>
{
#[cfg(any(target_os = "linux", target_os = "emscripten"))]
use libc::pread64;
#[cfg(not(any(target_os = "linux", target_os = "emscripten")))]
use libc::pread as pread64;
cvt(pread64(fd, buf, count, offset))
}

unsafe {
cvt_pread64(self.fd,
buf.as_mut_ptr() as *mut c_void,
buf.len(),
offset as i64)
.map(|n| n as usize)
}
}

pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::write(self.fd,
Expand All @@ -59,6 +83,30 @@ impl FileDesc {
Ok(ret as usize)
}

pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
#[cfg(target_os = "android")]
use super::android::cvt_pwrite64;

#[cfg(not(target_os = "android"))]
unsafe fn cvt_pwrite64(fd: c_int, buf: *const c_void, count: usize, offset: i64)
-> io::Result<isize>
{
#[cfg(any(target_os = "linux", target_os = "emscripten"))]
use libc::pwrite64;
#[cfg(not(any(target_os = "linux", target_os = "emscripten")))]
use libc::pwrite as pwrite64;
cvt(pwrite64(fd, buf, count, offset))
}

unsafe {
cvt_pwrite64(self.fd,
buf.as_ptr() as *const c_void,
buf.len(),
offset as i64)
.map(|n| n as usize)
}
}

#[cfg(not(any(target_env = "newlib",
target_os = "solaris",
target_os = "emscripten",
Expand Down
8 changes: 8 additions & 0 deletions src/libstd/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,10 +483,18 @@ impl File {
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> {
Expand Down
Loading

0 comments on commit 8c09454

Please sign in to comment.