rufs/
blockreader.rs

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
12/// Block-level Abstraction Layer.
13///
14/// `BlockReader` maps random access reads and writes onto block operations.
15pub 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	/// Get the underlying block size.
78	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					// The data is already buffered; just adjust the pointer
169					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		/// Seeking to SeekFrom::Current(0) should refill the internal buffer but otherwise be a
219		/// no-op.
220		#[test]
221		#[allow(clippy::seek_from_current)] // That's the whole point of the test
222		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		/// Seek to a negative offset from current
236		#[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		/// Seek to a negative absolute offset using SeekFrom::Current
253		#[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		/// Seek to a small positive offset from current, within the current block
265		#[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		/// Seek to a large positive offset from current
282		#[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}