Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement read_offset and write_offset #35704

Merged
merged 7 commits into from
Oct 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this also assert that the first 5 bytes were filled in with 0s?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same below for windows)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done for Unix. On Windows, these bytes are left in an unspecified state.

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
63 changes: 57 additions & 6 deletions src/libstd/sys/unix/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@

#![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 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 +97,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 +115,56 @@ 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>
{
use convert::TryInto;
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>
{
use convert::TryInto;
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add this to the prelude as well? (as well as the windows prelude)

/// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could these docs also clarify what happens if you read beyond the end of a file? E.g. if a file is 10 bytes long what do these return:

  • read_at(buf, 10)
  • read_at(buf, 100)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(ping on this)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure what I can write there. It'll return with an empty buffer.

#[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