metrale_storage/tier/
pio.rs1use std::fs::File;
13use std::io;
14
15pub fn write_all_at(f: &File, buf: &[u8], offset: u64) -> io::Result<()> {
18 let (mut off, mut done) = (offset, 0usize);
19 while done < buf.len() {
20 let n = write_at(f, &buf[done..], off)?;
21 if n == 0 {
22 return Err(io::Error::new(
23 io::ErrorKind::WriteZero,
24 format!("positional write returned 0 bytes at offset {off}"),
25 ));
26 }
27 done += n;
28 off += n as u64;
29 }
30 Ok(())
31}
32
33pub fn read_exact_at(f: &File, buf: &mut [u8], offset: u64) -> io::Result<()> {
36 let (mut off, mut done) = (offset, 0usize);
37 let total = buf.len();
38 while done < total {
39 let n = read_at(f, &mut buf[done..], off)?;
40 if n == 0 {
41 return Err(io::Error::new(
42 io::ErrorKind::UnexpectedEof,
43 format!("positional read hit EOF after {done} of {total} bytes at offset {off}"),
44 ));
45 }
46 done += n;
47 off += n as u64;
48 }
49 Ok(())
50}
51
52pub fn read_at_least_at(f: &File, buf: &mut [u8], offset: u64, needed: usize) -> io::Result<usize> {
61 debug_assert!(needed <= buf.len());
62 let (mut off, mut done) = (offset, 0usize);
63 while done < buf.len() {
64 let n = read_at(f, &mut buf[done..], off)?;
65 if n == 0 {
66 break;
67 }
68 done += n;
69 off += n as u64;
70 }
71 if done < needed {
72 return Err(io::Error::new(
73 io::ErrorKind::UnexpectedEof,
74 format!(
75 "positional read got {done} bytes at offset {offset}, needed {needed} \
76 — the file is shorter than the data it is supposed to hold"
77 ),
78 ));
79 }
80 Ok(done)
81}
82
83#[cfg(unix)]
84fn write_at(f: &File, buf: &[u8], offset: u64) -> io::Result<usize> {
85 use std::os::unix::fs::FileExt;
86 f.write_at(buf, offset)
87}
88
89#[cfg(unix)]
90fn read_at(f: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
91 use std::os::unix::fs::FileExt;
92 f.read_at(buf, offset)
93}
94
95#[cfg(windows)]
96fn write_at(f: &File, buf: &[u8], offset: u64) -> io::Result<usize> {
97 use std::os::windows::fs::FileExt;
98 f.seek_write(buf, offset)
99}
100
101#[cfg(windows)]
102fn read_at(f: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
103 use std::os::windows::fs::FileExt;
104 f.seek_read(buf, offset)
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
115 fn a_block_read_past_eof_succeeds_for_the_bytes_that_exist() {
116 let dir = std::env::temp_dir().join(format!("metrale_pio_tail_{}", std::process::id()));
117 std::fs::create_dir_all(&dir).unwrap();
118 let path = dir.join("tail.bin");
119 let len = 4096usize + 288;
120 std::fs::write(&path, vec![7u8; len]).unwrap();
121 let f = std::fs::File::open(&path).unwrap();
122
123 let mut buf = vec![0u8; 8192];
124 let n = read_at_least_at(&f, &mut buf, 4096, 288).unwrap();
125 assert_eq!(n, 288, "should read to EOF and no further");
126 assert!(
127 buf[..288].iter().all(|&b| b == 7),
128 "the row's bytes must arrive"
129 );
130
131 let e = read_at_least_at(&f, &mut buf, 4096, 289).unwrap_err();
132 assert_eq!(e.kind(), io::ErrorKind::UnexpectedEof);
133
134 assert!(read_exact_at(&f, &mut buf[..8192], 4096).is_err());
135
136 drop(f);
137 let _ = std::fs::remove_dir_all(&dir);
138 }
139
140 #[test]
143 fn round_trip_at_offset() {
144 use std::io::{Seek, SeekFrom};
145
146 let dir = std::env::temp_dir().join(format!("metrale_pio_{}", std::process::id()));
147 std::fs::create_dir_all(&dir).unwrap();
148 let path = dir.join("pio.bin");
149 let mut f = std::fs::OpenOptions::new()
150 .read(true)
151 .write(true)
152 .create(true)
153 .truncate(true)
154 .open(&path)
155 .unwrap();
156 f.seek(SeekFrom::Start(137)).unwrap();
157
158 write_all_at(&f, &[0u8; 4096], 0).unwrap();
159 assert_eq!(f.stream_position().unwrap(), 137);
160 let payload: Vec<u8> = (0..1024u32).map(|i| (i % 251) as u8).collect();
161 write_all_at(&f, &payload, 2048).unwrap();
162 assert_eq!(f.stream_position().unwrap(), 137);
163
164 let mut out = vec![0u8; payload.len()];
165 read_exact_at(&f, &mut out, 2048).unwrap();
166 assert_eq!(out, payload);
167 assert_eq!(f.stream_position().unwrap(), 137);
168
169 let mut past = vec![0u8; 8192];
170 assert!(read_exact_at(&f, &mut past, 4096).is_err());
171 assert_eq!(f.stream_position().unwrap(), 137);
172
173 drop(f);
174 let _ = std::fs::remove_dir_all(&dir);
175 }
176}