]>
Commit | Line | Data |
---|---|---|
2e7014e3 DM |
1 | use std::io::Read; |
2 | ||
3 | use 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). | |
25 | pub 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 | 36 | impl <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 | 171 | impl <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 | 193 | impl <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)] | |
232 | mod 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 | } |