]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/file_formats/blocked_reader.rs
sgutils2: use thiserror to derive Error
[proxmox-backup.git] / src / tape / file_formats / blocked_reader.rs
CommitLineData
2e7014e3
DM
1use std::io::Read;
2
3use crate::tape::{
4 TapeRead,
0db57124
DM
5 BlockRead,
6 BlockReadStatus,
2e7014e3
DM
7 file_formats::{
8 PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0,
9 BlockHeader,
10 BlockHeaderFlags,
11 },
12};
13
14/// Read a block stream generated by 'BlockWriter'.
15///
16/// This class implements 'TapeRead'. It always read whole blocks from
17/// the underlying reader, and does additional error checks:
18///
19/// - check magic number (detect streams not written by 'BlockWriter')
20/// - check block size
21/// - check block sequence numbers
22///
23/// The reader consumes the EOF mark after the data stream (if read to
24/// the end of the stream).
25pub struct BlockedReader<R> {
26 reader: R,
27 buffer: Box<BlockHeader>,
28 seq_nr: u32,
29 found_end_marker: bool,
30 incomplete: bool,
31 got_eod: bool,
32 read_error: bool,
33 read_pos: usize,
34}
35
0db57124 36impl <R: BlockRead> BlockedReader<R> {
2e7014e3
DM
37
38 /// Create a new BlockedReader instance.
39 ///
40 /// This tries to read the first block, and returns None if we are
41 /// at EOT.
42 pub fn open(mut reader: R) -> Result<Option<Self>, std::io::Error> {
43
44 let mut buffer = BlockHeader::new();
45
46 if !Self::read_block_frame(&mut buffer, &mut reader)? {
47 return Ok(None);
48 }
49
50 let (_size, found_end_marker) = Self::check_buffer(&buffer, 0)?;
51
52 let mut incomplete = false;
7d2c156e
DM
53 let mut got_eod = false;
54
2e7014e3
DM
55 if found_end_marker {
56 incomplete = buffer.flags.contains(BlockHeaderFlags::INCOMPLETE);
7d2c156e
DM
57 Self::consume_eof_marker(&mut reader)?;
58 got_eod = true;
2e7014e3 59 }
7d2c156e 60
2e7014e3
DM
61 Ok(Some(Self {
62 reader,
63 buffer,
64 found_end_marker,
65 incomplete,
7d2c156e 66 got_eod,
2e7014e3 67 seq_nr: 1,
2e7014e3
DM
68 read_error: false,
69 read_pos: 0,
70 }))
71 }
72
73 fn check_buffer(buffer: &BlockHeader, seq_nr: u32) -> Result<(usize, bool), std::io::Error> {
74
75 if buffer.magic != PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0 {
76 proxmox::io_bail!("detected tape block with wrong magic number - not written by proxmox tape");
77 }
78
79 if seq_nr != buffer.seq_nr() {
80 proxmox::io_bail!(
d1d74c43 81 "detected tape block with wrong sequence number ({} != {})",
2e7014e3
DM
82 seq_nr, buffer.seq_nr())
83 }
84
85 let size = buffer.size();
86 let found_end_marker = buffer.flags.contains(BlockHeaderFlags::END_OF_STREAM);
87
88 if size > buffer.payload.len() {
89 proxmox::io_bail!("detected tape block with wrong payload size ({} > {}", size, buffer.payload.len());
6334bdc1
FG
90 } else if size == 0 && !found_end_marker {
91 proxmox::io_bail!("detected tape block with zero payload size");
2e7014e3
DM
92 }
93
94
95 Ok((size, found_end_marker))
96 }
97
98 fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<bool, std::io::Error> {
99
100 let data = unsafe {
101 std::slice::from_raw_parts_mut(
102 (buffer as *mut BlockHeader) as *mut u8,
103 BlockHeader::SIZE,
104 )
105 };
106
0db57124
DM
107 match reader.read_block(data) {
108 Ok(BlockReadStatus::Ok(bytes)) => {
109 if bytes != BlockHeader::SIZE {
110 proxmox::io_bail!("got wrong block size");
111 }
112 Ok(true)
113 }
114 Ok(BlockReadStatus::EndOfFile) => {
115 Ok(false)
116 }
117 Ok(BlockReadStatus::EndOfStream) => {
118 return Err(std::io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32));
119 }
120 Err(err) => {
121 Err(err)
122 }
123 }
2e7014e3
DM
124 }
125
7d2c156e
DM
126 fn consume_eof_marker(reader: &mut R) -> Result<(), std::io::Error> {
127 let mut tmp_buf = [0u8; 512]; // use a small buffer for testing EOF
0db57124
DM
128 match reader.read_block(&mut tmp_buf) {
129 Ok(BlockReadStatus::Ok(_)) => {
130 proxmox::io_bail!("detected tape block after block-stream end marker");
131 }
132 Ok(BlockReadStatus::EndOfFile) => {
133 return Ok(());
134 }
135 Ok(BlockReadStatus::EndOfStream) => {
136 proxmox::io_bail!("got unexpected end of tape");
137 }
138 Err(err) => {
139 return Err(err);
140 }
7d2c156e 141 }
7d2c156e
DM
142 }
143
2e7014e3
DM
144 fn read_block(&mut self) -> Result<usize, std::io::Error> {
145
146 if !Self::read_block_frame(&mut self.buffer, &mut self.reader)? {
147 self.got_eod = true;
148 self.read_pos = self.buffer.payload.len();
149 if !self.found_end_marker {
150 proxmox::io_bail!("detected tape stream without end marker");
151 }
152 return Ok(0); // EOD
153 }
154
155 let (size, found_end_marker) = Self::check_buffer(&self.buffer, self.seq_nr)?;
156 self.seq_nr += 1;
157
158 if found_end_marker { // consume EOF mark
159 self.found_end_marker = true;
160 self.incomplete = self.buffer.flags.contains(BlockHeaderFlags::INCOMPLETE);
7d2c156e
DM
161 Self::consume_eof_marker(&mut self.reader)?;
162 self.got_eod = true;
2e7014e3
DM
163 }
164
165 self.read_pos = 0;
166
167 Ok(size)
168 }
169}
170
0db57124 171impl <R: BlockRead> TapeRead for BlockedReader<R> {
2e7014e3
DM
172
173 fn is_incomplete(&self) -> Result<bool, std::io::Error> {
174 if !self.got_eod {
175 proxmox::io_bail!("is_incomplete failed: EOD not reached");
176 }
177 if !self.found_end_marker {
178 proxmox::io_bail!("is_incomplete failed: no end marker found");
179 }
180
181 Ok(self.incomplete)
182 }
183
184 fn has_end_marker(&self) -> Result<bool, std::io::Error> {
185 if !self.got_eod {
186 proxmox::io_bail!("has_end_marker failed: EOD not reached");
187 }
188
189 Ok(self.found_end_marker)
190 }
191}
192
0db57124 193impl <R: BlockRead> Read for BlockedReader<R> {
2e7014e3
DM
194
195 fn read(&mut self, buffer: &mut [u8]) -> Result<usize, std::io::Error> {
196
197 if self.read_error {
198 proxmox::io_bail!("detected read after error - internal error");
199 }
200
201 let mut buffer_size = self.buffer.size();
202 let mut rest = (buffer_size as isize) - (self.read_pos as isize);
203
204 if rest <= 0 && !self.got_eod { // try to refill buffer
205 buffer_size = match self.read_block() {
206 Ok(len) => len,
207 err => {
208 self.read_error = true;
209 return err;
210 }
211 };
212 rest = buffer_size as isize;
213 }
214
215 if rest <= 0 {
38556bf6 216 Ok(0)
2e7014e3
DM
217 } else {
218 let copy_len = if (buffer.len() as isize) < rest {
219 buffer.len()
220 } else {
221 rest as usize
222 };
223 buffer[..copy_len].copy_from_slice(
224 &self.buffer.payload[self.read_pos..(self.read_pos + copy_len)]);
225 self.read_pos += copy_len;
38556bf6 226 Ok(copy_len)
2e7014e3
DM
227 }
228 }
229}
230
231#[cfg(test)]
232mod test {
233 use std::io::Read;
234 use anyhow::Error;
235 use crate::tape::{
236 TapeWrite,
0db57124 237 helpers::{EmulateTapeReader, EmulateTapeWriter},
ddebbb52
DM
238 file_formats::{
239 PROXMOX_TAPE_BLOCK_SIZE,
2e7014e3
DM
240 BlockedReader,
241 BlockedWriter,
242 },
243 };
244
245 fn write_and_verify(data: &[u8]) -> Result<(), Error> {
246
247 let mut tape_data = Vec::new();
248
0db57124
DM
249 {
250 let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024*10);
251 let mut writer = BlockedWriter::new(writer);
2e7014e3 252
0db57124 253 writer.write_all(data)?;
2e7014e3 254
0db57124
DM
255 writer.finish(false)?;
256 }
2e7014e3
DM
257
258 assert_eq!(
259 tape_data.len(),
260 ((data.len() + PROXMOX_TAPE_BLOCK_SIZE)/PROXMOX_TAPE_BLOCK_SIZE)
261 *PROXMOX_TAPE_BLOCK_SIZE
262 );
263
264 let reader = &mut &tape_data[..];
0db57124 265 let reader = EmulateTapeReader::new(reader);
2e7014e3
DM
266 let mut reader = BlockedReader::open(reader)?.unwrap();
267
268 let mut read_data = Vec::with_capacity(PROXMOX_TAPE_BLOCK_SIZE);
269 reader.read_to_end(&mut read_data)?;
270
271 assert_eq!(data.len(), read_data.len());
272
273 assert_eq!(data, &read_data[..]);
274
275 Ok(())
276 }
277
278 #[test]
279 fn empty_stream() -> Result<(), Error> {
280 write_and_verify(b"")
281 }
282
283 #[test]
284 fn small_data() -> Result<(), Error> {
285 write_and_verify(b"ABC")
286 }
287
288 #[test]
289 fn large_data() -> Result<(), Error> {
290 let data = proxmox::sys::linux::random_data(1024*1024*5)?;
291 write_and_verify(&data)
292 }
293
294 #[test]
295 fn no_data() -> Result<(), Error> {
296 let tape_data = Vec::new();
297 let reader = &mut &tape_data[..];
0db57124 298 let reader = EmulateTapeReader::new(reader);
2e7014e3
DM
299 let reader = BlockedReader::open(reader)?;
300 assert!(reader.is_none());
301
302 Ok(())
303 }
304
305 #[test]
306 fn no_end_marker() -> Result<(), Error> {
307 let mut tape_data = Vec::new();
308 {
0db57124
DM
309 let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024);
310 let mut writer = BlockedWriter::new(writer);
2e7014e3
DM
311 // write at least one block
312 let data = proxmox::sys::linux::random_data(PROXMOX_TAPE_BLOCK_SIZE)?;
313 writer.write_all(&data)?;
314 // but do not call finish here
315 }
0db57124 316
2e7014e3 317 let reader = &mut &tape_data[..];
0db57124 318 let reader = EmulateTapeReader::new(reader);
2e7014e3
DM
319 let mut reader = BlockedReader::open(reader)?.unwrap();
320
321 let mut data = Vec::with_capacity(PROXMOX_TAPE_BLOCK_SIZE);
322 assert!(reader.read_to_end(&mut data).is_err());
323
324 Ok(())
325 }
326
327 #[test]
328 fn small_read_buffer() -> Result<(), Error> {
329 let mut tape_data = Vec::new();
330
0db57124
DM
331 {
332 let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024);
333 let mut writer = BlockedWriter::new(writer);
2e7014e3 334
0db57124 335 writer.write_all(b"ABC")?;
2e7014e3 336
0db57124
DM
337 writer.finish(false)?;
338 }
2e7014e3
DM
339
340 let reader = &mut &tape_data[..];
0db57124 341 let reader = EmulateTapeReader::new(reader);
2e7014e3
DM
342 let mut reader = BlockedReader::open(reader)?.unwrap();
343
344 let mut buf = [0u8; 1];
345 assert_eq!(reader.read(&mut buf)?, 1, "wrong byte count");
346 assert_eq!(&buf, b"A");
347 assert_eq!(reader.read(&mut buf)?, 1, "wrong byte count");
348 assert_eq!(&buf, b"B");
349 assert_eq!(reader.read(&mut buf)?, 1, "wrong byte count");
350 assert_eq!(&buf, b"C");
351 assert_eq!(reader.read(&mut buf)?, 0, "wrong byte count");
352 assert_eq!(reader.read(&mut buf)?, 0, "wrong byte count");
353
354 Ok(())
355 }
356}