Skip to content

Commit b3f2644

Browse files
committed
Implement reading and writing atomically at certain offsets
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).
1 parent b98cc35 commit b3f2644

File tree

9 files changed

+295
-2
lines changed

9 files changed

+295
-2
lines changed

src/libstd/fs.rs

Lines changed: 125 additions & 1 deletion
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/ext/fs.rs

Lines changed: 45 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ use sys::cvt;
1818
use sys_common::AsInner;
1919
use sys_common::io::read_to_end_uninitialized;
2020

21+
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
22+
use libc::{pread64, pwrite64, off64_t};
23+
#[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))]
24+
use libc::{pread as pread64, pwrite as pwrite64, off_t as off64_t};
25+
2126
pub struct FileDesc {
2227
fd: c_int,
2328
}
@@ -50,6 +55,16 @@ impl FileDesc {
5055
(&mut me).read_to_end(buf)
5156
}
5257

58+
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
59+
let ret = cvt(unsafe {
60+
pread64(self.fd,
61+
buf.as_mut_ptr() as *mut c_void,
62+
buf.len(),
63+
offset as off64_t)
64+
})?;
65+
Ok(ret as usize)
66+
}
67+
5368
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
5469
let ret = cvt(unsafe {
5570
libc::write(self.fd,
@@ -59,6 +74,16 @@ impl FileDesc {
5974
Ok(ret as usize)
6075
}
6176

77+
pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
78+
let ret = cvt(unsafe {
79+
pwrite64(self.fd,
80+
buf.as_ptr() as *const c_void,
81+
buf.len(),
82+
offset as off64_t)
83+
})?;
84+
Ok(ret as usize)
85+
}
86+
6287
#[cfg(not(any(target_env = "newlib",
6388
target_os = "solaris",
6489
target_os = "emscripten",

src/libstd/sys/unix/fs.rs

Lines changed: 8 additions & 0 deletions
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> {

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,61 @@
1212
1313
#![stable(feature = "rust1", since = "1.0.0")]
1414

15-
use fs::{OpenOptions, Metadata};
15+
use fs::{self, OpenOptions, Metadata};
1616
use io;
1717
use path::Path;
1818
use sys;
1919
use sys_common::{AsInnerMut, AsInner};
2020

21+
/// Windows-specific extensions to `File`
22+
#[unstable(feature = "file_offset", issue = "35918")]
23+
pub trait FileExt {
24+
/// Seeks to a given position and reads a number of bytes.
25+
///
26+
/// Returns the number of bytes read.
27+
///
28+
/// The offset is relative to the start of the file and thus independent
29+
/// from the current cursor. The current cursor **is** affected by this
30+
/// function, it is set to the end of the read.
31+
///
32+
/// Reading beyond the end of the file will always return with a length of
33+
/// 0.
34+
///
35+
/// Note that similar to `File::read`, it is not an error to return with a
36+
/// short read. When returning from such a short read, the file pointer is
37+
/// still updated.
38+
#[unstable(feature = "file_offset", issue = "35918")]
39+
fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
40+
41+
/// Seeks to a given position and writes a number of bytes.
42+
///
43+
/// Returns the number of bytes written.
44+
///
45+
/// The offset is relative to the start of the file and thus independent
46+
/// from the current cursor. The current cursor **is** affected by this
47+
/// function, it is set to the end of the write.
48+
///
49+
/// When writing beyond the end of the file, the file is appropiately
50+
/// extended and the intermediate bytes are left uninitialized.
51+
///
52+
/// Note that similar to `File::write`, it is not an error to return a
53+
/// short write. When returning from such a short write, the file pointer
54+
/// is still updated.
55+
#[unstable(feature = "file_offset", issue = "35918")]
56+
fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
57+
}
58+
59+
#[unstable(feature = "file_offset", issue = "35918")]
60+
impl FileExt for fs::File {
61+
fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
62+
self.as_inner().read_at(buf, offset)
63+
}
64+
65+
fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
66+
self.as_inner().write_at(buf, offset)
67+
}
68+
}
69+
2170
/// Windows-specific extensions to `OpenOptions`
2271
#[stable(feature = "open_options_ext", since = "1.10.0")]
2372
pub trait OpenOptionsExt {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ pub mod prelude {
3636
pub use super::ffi::{OsStrExt, OsStringExt};
3737
#[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
3838
pub use super::fs::{OpenOptionsExt, MetadataExt};
39+
#[doc(no_inline)] #[unstable(feature = "file_offset", issue = "35918")]
40+
pub use super::fs::FileExt;
3941
}

src/libstd/sys/windows/fs.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,10 @@ impl File {
311311
self.handle.read(buf)
312312
}
313313

314+
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
315+
self.handle.read_at(buf, offset)
316+
}
317+
314318
pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
315319
self.handle.read_to_end(buf)
316320
}
@@ -319,6 +323,10 @@ impl File {
319323
self.handle.write(buf)
320324
}
321325

326+
pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
327+
self.handle.write_at(buf, offset)
328+
}
329+
322330
pub fn flush(&self) -> io::Result<()> { Ok(()) }
323331

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

0 commit comments

Comments
 (0)