Skip to content

Commit e1b6777

Browse files
authored
Auto merge of #35704 - tbu-:pr_pread_pwrite, r=alexcrichton
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.
2 parents a8d189a + 1554993 commit e1b6777

File tree

10 files changed

+375
-8
lines changed

10 files changed

+375
-8
lines changed

src/libstd/fs.rs

+125-1
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,130 @@ mod tests {
19031903
check!(fs::remove_file(filename));
19041904
}
19051905

1906+
#[test]
1907+
fn file_test_io_eof() {
1908+
let tmpdir = tmpdir();
1909+
let filename = tmpdir.join("file_rt_io_file_test_eof.txt");
1910+
let mut buf = [0; 256];
1911+
{
1912+
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
1913+
let mut rw = check!(oo.open(&filename));
1914+
assert_eq!(check!(rw.read(&mut buf)), 0);
1915+
assert_eq!(check!(rw.read(&mut buf)), 0);
1916+
}
1917+
check!(fs::remove_file(&filename));
1918+
}
1919+
1920+
#[test]
1921+
#[cfg(unix)]
1922+
fn file_test_io_read_write_at() {
1923+
use os::unix::fs::FileExt;
1924+
1925+
let tmpdir = tmpdir();
1926+
let filename = tmpdir.join("file_rt_io_file_test_read_write_at.txt");
1927+
let mut buf = [0; 256];
1928+
let write1 = "asdf";
1929+
let write2 = "qwer-";
1930+
let write3 = "-zxcv";
1931+
let content = "qwer-asdf-zxcv";
1932+
{
1933+
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
1934+
let mut rw = check!(oo.open(&filename));
1935+
assert_eq!(check!(rw.write_at(write1.as_bytes(), 5)), write1.len());
1936+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
1937+
assert_eq!(check!(rw.read_at(&mut buf, 5)), write1.len());
1938+
assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
1939+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
1940+
assert_eq!(check!(rw.read_at(&mut buf[..write2.len()], 0)), write2.len());
1941+
assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok("\0\0\0\0\0"));
1942+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0);
1943+
assert_eq!(check!(rw.write(write2.as_bytes())), write2.len());
1944+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
1945+
assert_eq!(check!(rw.read(&mut buf)), write1.len());
1946+
assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
1947+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
1948+
assert_eq!(check!(rw.read_at(&mut buf[..write2.len()], 0)), write2.len());
1949+
assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2));
1950+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
1951+
assert_eq!(check!(rw.write_at(write3.as_bytes(), 9)), write3.len());
1952+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
1953+
}
1954+
{
1955+
let mut read = check!(File::open(&filename));
1956+
assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
1957+
assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
1958+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 0);
1959+
assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
1960+
assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
1961+
assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
1962+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 9);
1963+
assert_eq!(check!(read.read(&mut buf)), write3.len());
1964+
assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3));
1965+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
1966+
assert_eq!(check!(read.read_at(&mut buf, 0)), content.len());
1967+
assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
1968+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
1969+
assert_eq!(check!(read.read_at(&mut buf, 14)), 0);
1970+
assert_eq!(check!(read.read_at(&mut buf, 15)), 0);
1971+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
1972+
}
1973+
check!(fs::remove_file(&filename));
1974+
}
1975+
1976+
#[test]
1977+
#[cfg(windows)]
1978+
fn file_test_io_seek_read_write() {
1979+
use os::windows::fs::FileExt;
1980+
1981+
let tmpdir = tmpdir();
1982+
let filename = tmpdir.join("file_rt_io_file_test_seek_read_write.txt");
1983+
let mut buf = [0; 256];
1984+
let write1 = "asdf";
1985+
let write2 = "qwer-";
1986+
let write3 = "-zxcv";
1987+
let content = "qwer-asdf-zxcv";
1988+
{
1989+
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
1990+
let mut rw = check!(oo.open(&filename));
1991+
assert_eq!(check!(rw.seek_write(write1.as_bytes(), 5)), write1.len());
1992+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
1993+
assert_eq!(check!(rw.seek_read(&mut buf, 5)), write1.len());
1994+
assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
1995+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
1996+
assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0);
1997+
assert_eq!(check!(rw.write(write2.as_bytes())), write2.len());
1998+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
1999+
assert_eq!(check!(rw.read(&mut buf)), write1.len());
2000+
assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1));
2001+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9);
2002+
assert_eq!(check!(rw.seek_read(&mut buf[..write2.len()], 0)), write2.len());
2003+
assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2));
2004+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5);
2005+
assert_eq!(check!(rw.seek_write(write3.as_bytes(), 9)), write3.len());
2006+
assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 14);
2007+
}
2008+
{
2009+
let mut read = check!(File::open(&filename));
2010+
assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
2011+
assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
2012+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
2013+
assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
2014+
assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
2015+
assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
2016+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
2017+
assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9);
2018+
assert_eq!(check!(read.read(&mut buf)), write3.len());
2019+
assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3));
2020+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
2021+
assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len());
2022+
assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content));
2023+
assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14);
2024+
assert_eq!(check!(read.seek_read(&mut buf, 14)), 0);
2025+
assert_eq!(check!(read.seek_read(&mut buf, 15)), 0);
2026+
}
2027+
check!(fs::remove_file(&filename));
2028+
}
2029+
19062030
#[test]
19072031
fn file_test_stat_is_correct_on_is_file() {
19082032
let tmpdir = tmpdir();
@@ -2221,8 +2345,8 @@ mod tests {
22212345
check!(fs::set_permissions(&out, attr.permissions()));
22222346
}
22232347

2224-
#[cfg(windows)]
22252348
#[test]
2349+
#[cfg(windows)]
22262350
fn copy_file_preserves_streams() {
22272351
let tmp = tmpdir();
22282352
check!(check!(File::create(tmp.join("in.txt:bunny"))).write("carrot".as_bytes()));

src/libstd/sys/unix/android.rs

+57-6
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
2929
#![cfg(target_os = "android")]
3030

31-
use libc::{c_int, sighandler_t};
31+
use libc::{c_int, c_void, sighandler_t, size_t, ssize_t};
32+
use libc::{ftruncate, pread, pwrite};
3233

3334
use io;
34-
use sys::cvt_r;
35+
use super::{cvt, cvt_r};
3536

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

102-
extern {
103-
fn ftruncate(fd: c_int, off: i32) -> c_int;
104-
}
105-
106104
unsafe {
107105
match ftruncate64.get() {
108106
Some(f) => cvt_r(|| f(fd, size as i64)).map(|_| ()),
@@ -117,3 +115,56 @@ pub fn ftruncate64(fd: c_int, size: u64) -> io::Result<()> {
117115
}
118116
}
119117
}
118+
119+
#[cfg(target_pointer_width = "64")]
120+
pub fn ftruncate64(fd: c_int, size: u64) -> io::Result<()> {
121+
unsafe {
122+
cvt_r(|| ftruncate(fd, size as i64)).map(|_| ())
123+
}
124+
}
125+
126+
#[cfg(target_pointer_width = "32")]
127+
pub unsafe fn cvt_pread64(fd: c_int, buf: *mut c_void, count: size_t, offset: i64)
128+
-> io::Result<ssize_t>
129+
{
130+
use convert::TryInto;
131+
weak!(fn pread64(c_int, *mut c_void, size_t, i64) -> ssize_t);
132+
pread64.get().map(|f| cvt(f(fd, buf, count, offset))).unwrap_or_else(|| {
133+
if let Ok(o) = offset.try_into() {
134+
cvt(pread(fd, buf, count, o))
135+
} else {
136+
Err(io::Error::new(io::ErrorKind::InvalidInput,
137+
"cannot pread >2GB"))
138+
}
139+
})
140+
}
141+
142+
#[cfg(target_pointer_width = "32")]
143+
pub unsafe fn cvt_pwrite64(fd: c_int, buf: *const c_void, count: size_t, offset: i64)
144+
-> io::Result<ssize_t>
145+
{
146+
use convert::TryInto;
147+
weak!(fn pwrite64(c_int, *const c_void, size_t, i64) -> ssize_t);
148+
pwrite64.get().map(|f| cvt(f(fd, buf, count, offset))).unwrap_or_else(|| {
149+
if let Ok(o) = offset.try_into() {
150+
cvt(pwrite(fd, buf, count, o))
151+
} else {
152+
Err(io::Error::new(io::ErrorKind::InvalidInput,
153+
"cannot pwrite >2GB"))
154+
}
155+
})
156+
}
157+
158+
#[cfg(target_pointer_width = "64")]
159+
pub unsafe fn cvt_pread64(fd: c_int, buf: *mut c_void, count: size_t, offset: i64)
160+
-> io::Result<ssize_t>
161+
{
162+
cvt(pread(fd, buf, count, offset))
163+
}
164+
165+
#[cfg(target_pointer_width = "64")]
166+
pub unsafe fn cvt_pwrite64(fd: c_int, buf: *const c_void, count: size_t, offset: i64)
167+
-> io::Result<ssize_t>
168+
{
169+
cvt(pwrite(fd, buf, count, offset))
170+
}

src/libstd/sys/unix/ext/fs.rs

+45
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,51 @@ use sys;
2020
use sys_common::{FromInner, AsInner, AsInnerMut};
2121
use sys::platform::fs::MetadataExt as UnixMetadataExt;
2222

23+
/// Unix-specific extensions to `File`
24+
#[unstable(feature = "file_offset", issue = "35918")]
25+
pub trait FileExt {
26+
/// Reads a number of bytes starting from a given offset.
27+
///
28+
/// Returns the number of bytes read.
29+
///
30+
/// The offset is relative to the start of the file and thus independent
31+
/// from the current cursor.
32+
///
33+
/// The current file cursor is not affected by this function.
34+
///
35+
/// Note that similar to `File::read`, it is not an error to return with a
36+
/// short read.
37+
#[unstable(feature = "file_offset", issue = "35918")]
38+
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
39+
40+
/// Writes a number of bytes starting from a given offset.
41+
///
42+
/// Returns the number of bytes written.
43+
///
44+
/// The offset is relative to the start of the file and thus independent
45+
/// from the current cursor.
46+
///
47+
/// The current file cursor is not affected by this function.
48+
///
49+
/// When writing beyond the end of the file, the file is appropiately
50+
/// extended and the intermediate bytes are initialized with the value 0.
51+
///
52+
/// Note that similar to `File::write`, it is not an error to return a
53+
/// short write.
54+
#[unstable(feature = "file_offset", issue = "35918")]
55+
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
56+
}
57+
58+
#[unstable(feature = "file_offset", issue = "35918")]
59+
impl FileExt for fs::File {
60+
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
61+
self.as_inner().read_at(buf, offset)
62+
}
63+
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
64+
self.as_inner().write_at(buf, offset)
65+
}
66+
}
67+
2368
/// Unix-specific extensions to `Permissions`
2469
#[stable(feature = "fs_ext", since = "1.1.0")]
2570
pub trait PermissionsExt {

src/libstd/sys/unix/ext/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ pub mod prelude {
5050
pub use super::fs::{PermissionsExt, OpenOptionsExt, MetadataExt, FileTypeExt};
5151
#[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
5252
pub use super::fs::DirEntryExt;
53+
#[doc(no_inline)] #[unstable(feature = "file_offset", issue = "35918")]
54+
pub use super::fs::FileExt;
5355
#[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
5456
pub use super::thread::JoinHandleExt;
5557
#[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]

src/libstd/sys/unix/fd.rs

+48
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ impl FileDesc {
5050
(&mut me).read_to_end(buf)
5151
}
5252

53+
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
54+
#[cfg(target_os = "android")]
55+
use super::android::cvt_pread64;
56+
57+
#[cfg(not(target_os = "android"))]
58+
unsafe fn cvt_pread64(fd: c_int, buf: *mut c_void, count: usize, offset: i64)
59+
-> io::Result<isize>
60+
{
61+
#[cfg(any(target_os = "linux", target_os = "emscripten"))]
62+
use libc::pread64;
63+
#[cfg(not(any(target_os = "linux", target_os = "emscripten")))]
64+
use libc::pread as pread64;
65+
cvt(pread64(fd, buf, count, offset))
66+
}
67+
68+
unsafe {
69+
cvt_pread64(self.fd,
70+
buf.as_mut_ptr() as *mut c_void,
71+
buf.len(),
72+
offset as i64)
73+
.map(|n| n as usize)
74+
}
75+
}
76+
5377
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
5478
let ret = cvt(unsafe {
5579
libc::write(self.fd,
@@ -59,6 +83,30 @@ impl FileDesc {
5983
Ok(ret as usize)
6084
}
6185

86+
pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
87+
#[cfg(target_os = "android")]
88+
use super::android::cvt_pwrite64;
89+
90+
#[cfg(not(target_os = "android"))]
91+
unsafe fn cvt_pwrite64(fd: c_int, buf: *const c_void, count: usize, offset: i64)
92+
-> io::Result<isize>
93+
{
94+
#[cfg(any(target_os = "linux", target_os = "emscripten"))]
95+
use libc::pwrite64;
96+
#[cfg(not(any(target_os = "linux", target_os = "emscripten")))]
97+
use libc::pwrite as pwrite64;
98+
cvt(pwrite64(fd, buf, count, offset))
99+
}
100+
101+
unsafe {
102+
cvt_pwrite64(self.fd,
103+
buf.as_ptr() as *const c_void,
104+
buf.len(),
105+
offset as i64)
106+
.map(|n| n as usize)
107+
}
108+
}
109+
62110
#[cfg(not(any(target_env = "newlib",
63111
target_os = "solaris",
64112
target_os = "emscripten",

src/libstd/sys/unix/fs.rs

+8
Original file line numberDiff line numberDiff line change
@@ -483,10 +483,18 @@ impl File {
483483
self.0.read_to_end(buf)
484484
}
485485

486+
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
487+
self.0.read_at(buf, offset)
488+
}
489+
486490
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
487491
self.0.write(buf)
488492
}
489493

494+
pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
495+
self.0.write_at(buf, offset)
496+
}
497+
490498
pub fn flush(&self) -> io::Result<()> { Ok(()) }
491499

492500
pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {

0 commit comments

Comments
 (0)