1use std::{
2 fs::File,
3 io::{self, BufRead, Read, Result as IoResult, Seek, SeekFrom, Write},
4 os::unix::fs::MetadataExt,
5 path::Path,
6};
7
8pub trait Backend: Read + Write + Seek {}
9
10impl<T: Read + Write + Seek> Backend for T {}
11
12pub struct BlockReader<T: Backend> {
16 inner: T,
17 block: Vec<u8>,
18 idx: usize,
19 dirty: bool,
20 rw: bool,
21}
22
23impl BlockReader<File> {
24 pub fn open(path: &Path, rw: bool) -> IoResult<Self> {
25 let file = File::options().read(true).write(rw).open(path)?;
26 let bs = file.metadata()?.blksize() as usize;
27 Ok(BlockReader::new(file, bs, rw))
28 }
29}
30
31impl<T: Backend> BlockReader<T> {
32 pub fn new(inner: T, bs: usize, rw: bool) -> Self {
33 let block = vec![0u8; bs];
34 Self {
35 inner,
36 block,
37 idx: bs,
38 dirty: false,
39 rw,
40 }
41 }
42
43 pub fn write_enabled(&self) -> bool {
44 self.rw
45 }
46
47 fn refill(&mut self) -> IoResult<()> {
48 if self.dirty {
49 panic!("Cannot refill dirty BlockReader");
50 }
51 self.block.fill(0u8);
52 let mut num = 0;
53 while num < self.block.len() {
54 match self.inner.read(&mut self.block[num..])? {
55 0 => break,
56 n => num += n,
57 }
58 }
59 if num < self.block.len() {
60 log::error!("BlockReader::refill(): num={num}, eof?");
61 }
62 self.idx = 0;
63 Ok(())
64 }
65
66 fn buffered(&self) -> usize {
67 self.block.len() - self.idx
68 }
69
70 fn refill_if_empty(&mut self) -> IoResult<()> {
71 if self.buffered() == 0 {
72 self.refill()?;
73 }
74 Ok(())
75 }
76
77 pub fn blksize(&self) -> usize {
79 self.block.len()
80 }
81}
82
83impl<T: Backend> Read for BlockReader<T> {
84 fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
85 self.refill_if_empty()?;
86 let num = buf.len().min(self.buffered());
87 let buf = &mut buf[0..num];
88 buf.copy_from_slice(&self.block[self.idx..(self.idx + num)]);
89 self.idx += num;
90 Ok(num)
91 }
92}
93
94impl<T: Backend> Write for BlockReader<T> {
95 fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
96 if !self.rw {
97 panic!(
98 "BUG: BlockReader::write() should never be called when the medium is not writable"
99 );
100 }
101 self.refill_if_empty()?;
102 let num = buf.len().min(self.buffered());
103 self.block[self.idx..(self.idx + num)].copy_from_slice(&buf[0..num]);
104 self.idx += num;
105 self.dirty = true;
106 self.flush()?;
107 Ok(num)
108 }
109
110 fn flush(&mut self) -> IoResult<()> {
111 if !self.dirty {
112 return Ok(());
113 }
114
115 self.inner
116 .seek(SeekFrom::Current(-(self.block.len() as i64)))?;
117 let mut num = 0;
118 while num < self.block.len() {
119 match self.inner.write(&self.block[num..])? {
120 0 => break,
121 n => num += n,
122 }
123 }
124 if num < self.block.len() {
125 let pos = self.inner.stream_position()?;
126 log::error!(
127 "short write: pos={pos}, num={num}, len={}",
128 self.block.len()
129 );
130 }
131 self.dirty = false;
132 Ok(())
133 }
134}
135
136impl<T: Backend> BufRead for BlockReader<T> {
137 fn fill_buf(&mut self) -> IoResult<&[u8]> {
138 self.refill_if_empty()?;
139 Ok(&self.block[self.idx..])
140 }
141
142 fn consume(&mut self, amt: usize) {
143 assert!(amt <= self.buffered());
144 self.idx += amt;
145 }
146}
147
148impl<T: Backend> Seek for BlockReader<T> {
149 fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> {
150 let bs = self.blksize() as u64;
151 match pos {
152 SeekFrom::Start(pos) => {
153 self.flush()?;
154 let real = self.inner.seek(SeekFrom::Start(pos / bs * bs))?;
155 let rem = pos - real;
156 assert!(rem < bs);
157
158 self.refill()?;
159 self.idx = rem as usize;
160
161 Ok(real + rem)
162 }
163 SeekFrom::Current(offset) => {
164 let real = self.inner.stream_position()?;
165 let cur = real - self.block.len() as u64 + self.idx as u64;
166 let newidx = offset + self.idx as i64;
167 if newidx >= 0 && newidx < self.blksize() as i64 {
168 self.idx = newidx as usize;
170 Ok(real - self.block.len() as u64 + newidx as u64)
171 } else if cur as i64 + offset < 0 {
172 Err(io::Error::from_raw_os_error(libc::EINVAL))
173 } else {
174 self.seek(SeekFrom::Start((cur as i64 + offset) as u64))
175 }
176 }
177 SeekFrom::End(_) => todo!("SeekFrom::End()"),
178 }
179 }
180}
181
182#[cfg(test)]
183mod t {
184 use super::*;
185
186 const FSIZE: u64 = 1 << 20;
187
188 fn harness(rw: bool) -> BlockReader<File> {
189 let f = tempfile::NamedTempFile::new().unwrap();
190 f.as_file().set_len(FSIZE).unwrap();
191 let br = BlockReader::open(f.path(), rw).unwrap();
192 let bs = br.blksize();
193 assert!(FSIZE > 2 * bs as u64);
194 br
195 }
196
197 mod write {
198 use super::*;
199
200 #[test]
201 fn simple_write() {
202 let mut br = harness(true);
203 let bs = br.blksize();
204 let pos = bs + (bs >> 2);
205 let mut buf = vec![0x55u8; bs];
206 br.seek(SeekFrom::Start(pos as u64)).unwrap();
207 br.write_all(&buf).unwrap();
208 buf.fill(0);
209 br.seek(SeekFrom::Start(pos as u64)).unwrap();
210 br.read_exact(&mut buf).unwrap();
211 assert_eq!(buf, vec![0x55u8; bs]);
212 }
213 }
214
215 mod seek {
216 use super::*;
217
218 #[test]
221 #[allow(clippy::seek_from_current)] fn current_0() {
223 let mut br = harness(false);
224 let bs = br.blksize();
225 let pos = bs + (bs >> 2);
226 br.seek(SeekFrom::Start(pos as u64)).unwrap();
227 let idx = br.idx;
228 let real_pos = br.inner.stream_position().unwrap();
229
230 br.seek(SeekFrom::Current(0)).unwrap();
231 assert_eq!(real_pos, br.inner.stream_position().unwrap());
232 assert_eq!(idx, br.idx);
233 }
234
235 #[test]
237 fn current_neg() {
238 let mut br = harness(false);
239 let bs = br.blksize();
240 let initial = bs + (bs >> 2);
241 br.seek(SeekFrom::Start(initial as u64)).unwrap();
242 let idx = br.idx as u64;
243 let real_pos = br.inner.stream_position().unwrap();
244
245 br.seek(SeekFrom::Current(-1)).unwrap();
246 assert_eq!(
247 real_pos + idx - 1,
248 br.inner.stream_position().unwrap() + br.idx as u64
249 );
250 }
251
252 #[test]
254 fn current_neg_neg() {
255 let mut br = harness(false);
256 let bs = br.blksize();
257 let initial = bs + (bs >> 2);
258 br.seek(SeekFrom::Start(initial as u64)).unwrap();
259
260 let e = br.seek(SeekFrom::Current(-2 * initial as i64)).unwrap_err();
261 assert_eq!(libc::EINVAL, e.raw_os_error().unwrap());
262 }
263
264 #[test]
266 fn current_pos_incr() {
267 let mut br = harness(false);
268 let bs = br.blksize();
269 let initial = bs + (bs >> 2);
270 br.seek(SeekFrom::Start(initial as u64)).unwrap();
271 let idx = br.idx as u64;
272 let real_pos = br.inner.stream_position().unwrap();
273
274 br.seek(SeekFrom::Current(1)).unwrap();
275 assert_eq!(
276 real_pos + idx + 1,
277 br.inner.stream_position().unwrap() + br.idx as u64
278 );
279 }
280
281 #[test]
283 fn current_pos_large() {
284 let mut br = harness(false);
285 let bs = br.blksize();
286 let initial = bs + (bs >> 2);
287 br.seek(SeekFrom::Start(initial as u64)).unwrap();
288 let idx = br.idx as u64;
289 let real_pos = br.inner.stream_position().unwrap();
290
291 br.seek(SeekFrom::Current(bs as i64)).unwrap();
292 assert_eq!(
293 real_pos + idx + bs as u64,
294 br.inner.stream_position().unwrap() + br.idx as u64
295 );
296 }
297 }
298}