]>
Commit | Line | Data |
---|---|---|
f7d4e4b5 | 1 | use anyhow::{bail, format_err, Error}; |
dc9596de | 2 | use std::fmt; |
55c0b3cc DM |
3 | use std::ffi::{CStr, CString, OsStr}; |
4 | use std::os::unix::ffi::OsStrExt; | |
bf6e3217 | 5 | use std::io::{Read, Write, Seek, SeekFrom}; |
9d135fe6 DM |
6 | use std::convert::TryFrom; |
7 | ||
8 | use chrono::offset::{TimeZone, Local}; | |
9 | ||
10 | use proxmox::tools::io::ReadExt; | |
d973aa82 | 11 | use proxmox::sys::error::io_err_other; |
9d135fe6 | 12 | |
dc9596de | 13 | use crate::pxar::catalog::BackupCatalogWriter; |
90dfd0a7 | 14 | use crate::pxar::{MatchPattern, MatchPatternSlice, MatchType}; |
c74c074b | 15 | use crate::backup::file_formats::PROXMOX_CATALOG_FILE_MAGIC_1_0; |
d973aa82 | 16 | use crate::tools::runtime::block_on; |
9d135fe6 | 17 | |
dc9596de DM |
18 | #[repr(u8)] |
19 | #[derive(Copy,Clone,PartialEq)] | |
7d017123 | 20 | enum CatalogEntryType { |
dc9596de DM |
21 | Directory = b'd', |
22 | File = b'f', | |
23 | Symlink = b'l', | |
24 | Hardlink = b'h', | |
25 | BlockDevice = b'b', | |
26 | CharDevice = b'c', | |
27 | Fifo = b'p', // Fifo,Pipe | |
28 | Socket = b's', | |
29 | } | |
30 | ||
31 | impl TryFrom<u8> for CatalogEntryType { | |
32 | type Error=Error; | |
33 | ||
34 | fn try_from(value: u8) -> Result<Self, Error> { | |
35 | Ok(match value { | |
36 | b'd' => CatalogEntryType::Directory, | |
37 | b'f' => CatalogEntryType::File, | |
38 | b'l' => CatalogEntryType::Symlink, | |
39 | b'h' => CatalogEntryType::Hardlink, | |
40 | b'b' => CatalogEntryType::BlockDevice, | |
41 | b'c' => CatalogEntryType::CharDevice, | |
42 | b'p' => CatalogEntryType::Fifo, | |
43 | b's' => CatalogEntryType::Socket, | |
44 | _ => bail!("invalid CatalogEntryType value '{}'", char::from(value)), | |
45 | }) | |
46 | } | |
47 | } | |
48 | ||
49 | impl fmt::Display for CatalogEntryType { | |
50 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
51 | write!(f, "{}", char::from(*self as u8)) | |
52 | } | |
53 | } | |
54 | ||
780dd2b0 DM |
55 | /// Represents a named directory entry |
56 | /// | |
57 | /// The ``attr`` property contain the exact type with type specific | |
58 | /// attributes. | |
11ee5c05 | 59 | #[derive(Clone, PartialEq)] |
7d017123 DM |
60 | pub struct DirEntry { |
61 | pub name: Vec<u8>, | |
62 | pub attr: DirEntryAttribute, | |
55c0b3cc DM |
63 | } |
64 | ||
780dd2b0 | 65 | /// Used to specific additional attributes inside DirEntry |
11ee5c05 | 66 | #[derive(Clone, PartialEq)] |
780dd2b0 DM |
67 | pub enum DirEntryAttribute { |
68 | Directory { start: u64 }, | |
69 | File { size: u64, mtime: u64 }, | |
70 | Symlink, | |
71 | Hardlink, | |
72 | BlockDevice, | |
73 | CharDevice, | |
74 | Fifo, | |
75 | Socket, | |
76 | } | |
77 | ||
7d017123 DM |
78 | impl DirEntry { |
79 | ||
80 | fn new(etype: CatalogEntryType, name: Vec<u8>, start: u64, size: u64, mtime:u64) -> Self { | |
81 | match etype { | |
82 | CatalogEntryType::Directory => { | |
83 | DirEntry { name, attr: DirEntryAttribute::Directory { start } } | |
84 | } | |
85 | CatalogEntryType::File => { | |
86 | DirEntry { name, attr: DirEntryAttribute::File { size, mtime } } | |
87 | } | |
88 | CatalogEntryType::Symlink => { | |
89 | DirEntry { name, attr: DirEntryAttribute::Symlink } | |
90 | } | |
91 | CatalogEntryType::Hardlink => { | |
92 | DirEntry { name, attr: DirEntryAttribute::Hardlink } | |
93 | } | |
94 | CatalogEntryType::BlockDevice => { | |
95 | DirEntry { name, attr: DirEntryAttribute::BlockDevice } | |
96 | } | |
97 | CatalogEntryType::CharDevice => { | |
98 | DirEntry { name, attr: DirEntryAttribute::CharDevice } | |
99 | } | |
100 | CatalogEntryType::Fifo => { | |
101 | DirEntry { name, attr: DirEntryAttribute::Fifo } | |
102 | } | |
103 | CatalogEntryType::Socket => { | |
104 | DirEntry { name, attr: DirEntryAttribute::Socket } | |
105 | } | |
106 | } | |
107 | } | |
c9f00221 | 108 | |
4145c367 | 109 | /// Check if DirEntry is a directory |
c9f00221 CE |
110 | pub fn is_directory(&self) -> bool { |
111 | match self.attr { | |
112 | DirEntryAttribute::Directory { .. } => true, | |
113 | _ => false, | |
114 | } | |
115 | } | |
c2f91494 CE |
116 | |
117 | /// Check if DirEntry is a symlink | |
118 | pub fn is_symlink(&self) -> bool { | |
119 | match self.attr { | |
120 | DirEntryAttribute::Symlink { .. } => true, | |
121 | _ => false, | |
122 | } | |
123 | } | |
7d017123 DM |
124 | } |
125 | ||
bf6e3217 DM |
126 | struct DirInfo { |
127 | name: CString, | |
128 | entries: Vec<DirEntry>, | |
9d135fe6 DM |
129 | } |
130 | ||
bf6e3217 DM |
131 | impl DirInfo { |
132 | ||
133 | fn new(name: CString) -> Self { | |
134 | DirInfo { name, entries: Vec::new() } | |
135 | } | |
136 | ||
137 | fn new_rootdir() -> Self { | |
138 | DirInfo::new(CString::new(b"/".to_vec()).unwrap()) | |
139 | } | |
140 | ||
12a1975a DM |
141 | fn encode_entry<W: Write>( |
142 | writer: &mut W, | |
143 | entry: &DirEntry, | |
144 | pos: u64, | |
145 | ) -> Result<(), Error> { | |
bf6e3217 | 146 | match entry { |
55c0b3cc | 147 | DirEntry { name, attr: DirEntryAttribute::Directory { start } } => { |
12a1975a DM |
148 | writer.write_all(&[CatalogEntryType::Directory as u8])?; |
149 | catalog_encode_u64(writer, name.len() as u64)?; | |
150 | writer.write_all(name)?; | |
151 | catalog_encode_u64(writer, pos - start)?; | |
bf6e3217 | 152 | } |
55c0b3cc | 153 | DirEntry { name, attr: DirEntryAttribute::File { size, mtime } } => { |
12a1975a DM |
154 | writer.write_all(&[CatalogEntryType::File as u8])?; |
155 | catalog_encode_u64(writer, name.len() as u64)?; | |
156 | writer.write_all(name)?; | |
157 | catalog_encode_u64(writer, *size)?; | |
158 | catalog_encode_u64(writer, *mtime)?; | |
bf6e3217 | 159 | } |
55c0b3cc | 160 | DirEntry { name, attr: DirEntryAttribute::Symlink } => { |
12a1975a DM |
161 | writer.write_all(&[CatalogEntryType::Symlink as u8])?; |
162 | catalog_encode_u64(writer, name.len() as u64)?; | |
163 | writer.write_all(name)?; | |
bf6e3217 | 164 | } |
55c0b3cc | 165 | DirEntry { name, attr: DirEntryAttribute::Hardlink } => { |
12a1975a DM |
166 | writer.write_all(&[CatalogEntryType::Hardlink as u8])?; |
167 | catalog_encode_u64(writer, name.len() as u64)?; | |
168 | writer.write_all(name)?; | |
bf6e3217 | 169 | } |
55c0b3cc | 170 | DirEntry { name, attr: DirEntryAttribute::BlockDevice } => { |
12a1975a DM |
171 | writer.write_all(&[CatalogEntryType::BlockDevice as u8])?; |
172 | catalog_encode_u64(writer, name.len() as u64)?; | |
173 | writer.write_all(name)?; | |
bf6e3217 | 174 | } |
55c0b3cc | 175 | DirEntry { name, attr: DirEntryAttribute::CharDevice } => { |
12a1975a DM |
176 | writer.write_all(&[CatalogEntryType::CharDevice as u8])?; |
177 | catalog_encode_u64(writer, name.len() as u64)?; | |
178 | writer.write_all(name)?; | |
bf6e3217 | 179 | } |
55c0b3cc | 180 | DirEntry { name, attr: DirEntryAttribute::Fifo } => { |
12a1975a DM |
181 | writer.write_all(&[CatalogEntryType::Fifo as u8])?; |
182 | catalog_encode_u64(writer, name.len() as u64)?; | |
183 | writer.write_all(name)?; | |
bf6e3217 | 184 | } |
55c0b3cc | 185 | DirEntry { name, attr: DirEntryAttribute::Socket } => { |
12a1975a DM |
186 | writer.write_all(&[CatalogEntryType::Socket as u8])?; |
187 | catalog_encode_u64(writer, name.len() as u64)?; | |
188 | writer.write_all(name)?; | |
bf6e3217 DM |
189 | } |
190 | } | |
12a1975a | 191 | Ok(()) |
9d135fe6 | 192 | } |
bf6e3217 DM |
193 | |
194 | fn encode(self, start: u64) -> Result<(CString, Vec<u8>), Error> { | |
195 | let mut table = Vec::new(); | |
12a1975a | 196 | catalog_encode_u64(&mut table, self.entries.len() as u64)?; |
bf6e3217 | 197 | for entry in self.entries { |
12a1975a | 198 | Self::encode_entry(&mut table, &entry, start)?; |
bf6e3217 DM |
199 | } |
200 | ||
12a1975a DM |
201 | let mut data = Vec::new(); |
202 | catalog_encode_u64(&mut data, table.len() as u64)?; | |
203 | data.extend_from_slice(&table); | |
204 | ||
205 | Ok((self.name, data)) | |
9d135fe6 | 206 | } |
26566451 | 207 | |
8f24a9ea | 208 | fn parse<C: FnMut(CatalogEntryType, &[u8], u64, u64, u64) -> Result<bool, Error>>( |
26566451 DM |
209 | data: &[u8], |
210 | mut callback: C, | |
211 | ) -> Result<(), Error> { | |
212 | ||
213 | let mut cursor = data; | |
214 | ||
215 | let entries = catalog_decode_u64(&mut cursor)?; | |
216 | ||
55c0b3cc DM |
217 | let mut name_buf = vec![0u8; 4096]; |
218 | ||
26566451 DM |
219 | for _ in 0..entries { |
220 | ||
221 | let mut buf = [ 0u8 ]; | |
222 | cursor.read_exact(&mut buf)?; | |
223 | let etype = CatalogEntryType::try_from(buf[0])?; | |
224 | ||
55c0b3cc DM |
225 | let name_len = catalog_decode_u64(&mut cursor)? as usize; |
226 | if name_len >= name_buf.len() { | |
227 | bail!("directory entry name too long ({} >= {})", name_len, name_buf.len()); | |
228 | } | |
229 | let name = &mut name_buf[0..name_len]; | |
230 | cursor.read_exact(name)?; | |
26566451 | 231 | |
8f24a9ea | 232 | let cont = match etype { |
26566451 DM |
233 | CatalogEntryType::Directory => { |
234 | let offset = catalog_decode_u64(&mut cursor)?; | |
8f24a9ea | 235 | callback(etype, name, offset, 0, 0)? |
26566451 DM |
236 | } |
237 | CatalogEntryType::File => { | |
238 | let size = catalog_decode_u64(&mut cursor)?; | |
239 | let mtime = catalog_decode_u64(&mut cursor)?; | |
8f24a9ea | 240 | callback(etype, name, 0, size, mtime)? |
26566451 DM |
241 | } |
242 | _ => { | |
8f24a9ea | 243 | callback(etype, name, 0, 0, 0)? |
26566451 | 244 | } |
8f24a9ea DM |
245 | }; |
246 | if !cont { | |
247 | return Ok(()); | |
26566451 DM |
248 | } |
249 | } | |
250 | ||
251 | if !cursor.is_empty() { | |
252 | bail!("unable to parse whole catalog data block"); | |
253 | } | |
254 | ||
255 | Ok(()) | |
256 | } | |
bf6e3217 DM |
257 | } |
258 | ||
780dd2b0 DM |
259 | /// Write small catalog files |
260 | /// | |
261 | /// A Catalogs simply contains list of files and directories | |
262 | /// (directory tree). They are use to find content without having to | |
263 | /// search the real archive (which may be large). For files, they | |
264 | /// include the last modification time and file size. | |
bf6e3217 DM |
265 | pub struct CatalogWriter<W> { |
266 | writer: W, | |
267 | dirstack: Vec<DirInfo>, | |
268 | pos: u64, | |
269 | } | |
270 | ||
271 | impl <W: Write> CatalogWriter<W> { | |
272 | ||
780dd2b0 | 273 | /// Create a new CatalogWriter instance |
bf6e3217 | 274 | pub fn new(writer: W) -> Result<Self, Error> { |
c74c074b DM |
275 | let mut me = Self { writer, dirstack: vec![ DirInfo::new_rootdir() ], pos: 0 }; |
276 | me.write_all(&PROXMOX_CATALOG_FILE_MAGIC_1_0)?; | |
277 | Ok(me) | |
9d135fe6 | 278 | } |
bf6e3217 | 279 | |
f89359c2 DM |
280 | fn write_all(&mut self, data: &[u8]) -> Result<(), Error> { |
281 | self.writer.write_all(data)?; | |
282 | self.pos += u64::try_from(data.len())?; | |
283 | Ok(()) | |
284 | } | |
285 | ||
780dd2b0 DM |
286 | /// Finish writing, flush all data |
287 | /// | |
288 | /// This need to be called before drop. | |
bf6e3217 DM |
289 | pub fn finish(&mut self) -> Result<(), Error> { |
290 | if self.dirstack.len() != 1 { | |
291 | bail!("unable to finish catalog at level {}", self.dirstack.len()); | |
292 | } | |
293 | ||
294 | let dir = self.dirstack.pop().unwrap(); | |
295 | ||
296 | let start = self.pos; | |
297 | let (_, data) = dir.encode(start)?; | |
298 | self.write_all(&data)?; | |
299 | ||
300 | self.write_all(&start.to_le_bytes())?; | |
301 | ||
302 | self.writer.flush()?; | |
303 | ||
304 | Ok(()) | |
9d135fe6 DM |
305 | } |
306 | } | |
307 | ||
bf6e3217 | 308 | impl <W: Write> BackupCatalogWriter for CatalogWriter<W> { |
9d135fe6 DM |
309 | |
310 | fn start_directory(&mut self, name: &CStr) -> Result<(), Error> { | |
bf6e3217 DM |
311 | let new = DirInfo::new(name.to_owned()); |
312 | self.dirstack.push(new); | |
9d135fe6 DM |
313 | Ok(()) |
314 | } | |
315 | ||
316 | fn end_directory(&mut self) -> Result<(), Error> { | |
bf6e3217 DM |
317 | let (start, name) = match self.dirstack.pop() { |
318 | Some(dir) => { | |
319 | let start = self.pos; | |
320 | let (name, data) = dir.encode(start)?; | |
321 | self.write_all(&data)?; | |
322 | (start, name) | |
323 | } | |
324 | None => { | |
325 | bail!("got unexpected end_directory level 0"); | |
326 | } | |
327 | }; | |
328 | ||
329 | let current = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?; | |
330 | let name = name.to_bytes().to_vec(); | |
55c0b3cc | 331 | current.entries.push(DirEntry { name, attr: DirEntryAttribute::Directory { start } }); |
bf6e3217 | 332 | |
9d135fe6 DM |
333 | Ok(()) |
334 | } | |
335 | ||
336 | fn add_file(&mut self, name: &CStr, size: u64, mtime: u64) -> Result<(), Error> { | |
bf6e3217 DM |
337 | let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?; |
338 | let name = name.to_bytes().to_vec(); | |
55c0b3cc | 339 | dir.entries.push(DirEntry { name, attr: DirEntryAttribute::File { size, mtime } }); |
9d135fe6 DM |
340 | Ok(()) |
341 | } | |
342 | ||
343 | fn add_symlink(&mut self, name: &CStr) -> Result<(), Error> { | |
bf6e3217 DM |
344 | let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?; |
345 | let name = name.to_bytes().to_vec(); | |
55c0b3cc | 346 | dir.entries.push(DirEntry { name, attr: DirEntryAttribute::Symlink }); |
9d135fe6 DM |
347 | Ok(()) |
348 | } | |
349 | ||
350 | fn add_hardlink(&mut self, name: &CStr) -> Result<(), Error> { | |
bf6e3217 DM |
351 | let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?; |
352 | let name = name.to_bytes().to_vec(); | |
55c0b3cc | 353 | dir.entries.push(DirEntry { name, attr: DirEntryAttribute::Hardlink }); |
9d135fe6 DM |
354 | Ok(()) |
355 | } | |
356 | ||
357 | fn add_block_device(&mut self, name: &CStr) -> Result<(), Error> { | |
bf6e3217 DM |
358 | let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?; |
359 | let name = name.to_bytes().to_vec(); | |
55c0b3cc DM |
360 | dir.entries.push(DirEntry { name, attr: DirEntryAttribute::BlockDevice }); |
361 | Ok(()) | |
9d135fe6 DM |
362 | } |
363 | ||
364 | fn add_char_device(&mut self, name: &CStr) -> Result<(), Error> { | |
bf6e3217 DM |
365 | let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?; |
366 | let name = name.to_bytes().to_vec(); | |
55c0b3cc | 367 | dir.entries.push(DirEntry { name, attr: DirEntryAttribute::CharDevice }); |
9d135fe6 DM |
368 | Ok(()) |
369 | } | |
370 | ||
371 | fn add_fifo(&mut self, name: &CStr) -> Result<(), Error> { | |
bf6e3217 DM |
372 | let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?; |
373 | let name = name.to_bytes().to_vec(); | |
55c0b3cc | 374 | dir.entries.push(DirEntry { name, attr: DirEntryAttribute::Fifo }); |
9d135fe6 DM |
375 | Ok(()) |
376 | } | |
377 | ||
378 | fn add_socket(&mut self, name: &CStr) -> Result<(), Error> { | |
bf6e3217 DM |
379 | let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?; |
380 | let name = name.to_bytes().to_vec(); | |
55c0b3cc | 381 | dir.entries.push(DirEntry { name, attr: DirEntryAttribute::Socket }); |
9d135fe6 DM |
382 | Ok(()) |
383 | } | |
384 | } | |
385 | ||
bf6e3217 DM |
386 | // fixme: move to somehere else? |
387 | /// Implement Write to tokio mpsc channel Sender | |
388 | pub struct SenderWriter(tokio::sync::mpsc::Sender<Result<Vec<u8>, Error>>); | |
389 | ||
390 | impl SenderWriter { | |
391 | pub fn new(sender: tokio::sync::mpsc::Sender<Result<Vec<u8>, Error>>) -> Self { | |
392 | Self(sender) | |
393 | } | |
9d135fe6 DM |
394 | } |
395 | ||
bf6e3217 DM |
396 | impl Write for SenderWriter { |
397 | fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> { | |
d973aa82 WB |
398 | block_on(async move { |
399 | self.0 | |
400 | .send(Ok(buf.to_vec())) | |
401 | .await | |
402 | .map_err(io_err_other) | |
403 | .and(Ok(buf.len())) | |
bf6e3217 DM |
404 | }) |
405 | } | |
9d135fe6 | 406 | |
bf6e3217 DM |
407 | fn flush(&mut self) -> Result<(), std::io::Error> { |
408 | Ok(()) | |
409 | } | |
410 | } | |
9d135fe6 | 411 | |
780dd2b0 | 412 | /// Read Catalog files |
bf6e3217 DM |
413 | pub struct CatalogReader<R> { |
414 | reader: R, | |
415 | } | |
9d135fe6 | 416 | |
bf6e3217 | 417 | impl <R: Read + Seek> CatalogReader<R> { |
9d135fe6 | 418 | |
780dd2b0 | 419 | /// Create a new CatalogReader instance |
bf6e3217 DM |
420 | pub fn new(reader: R) -> Self { |
421 | Self { reader } | |
9d135fe6 DM |
422 | } |
423 | ||
780dd2b0 | 424 | /// Print whole catalog to stdout |
bf6e3217 | 425 | pub fn dump(&mut self) -> Result<(), Error> { |
9d135fe6 | 426 | |
2ec208ae DM |
427 | let root = self.root()?; |
428 | match root { | |
429 | DirEntry { attr: DirEntryAttribute::Directory { start }, .. }=> { | |
430 | self.dump_dir(std::path::Path::new("./"), start) | |
431 | } | |
432 | _ => unreachable!(), | |
433 | } | |
bf6e3217 | 434 | } |
9d135fe6 | 435 | |
7d017123 DM |
436 | /// Get the root DirEntry |
437 | pub fn root(&mut self) -> Result<DirEntry, Error> { | |
438 | // Root dir is special | |
6aa906b5 DM |
439 | self.reader.seek(SeekFrom::Start(0))?; |
440 | let mut magic = [ 0u8; 8]; | |
441 | self.reader.read_exact(&mut magic)?; | |
442 | if magic != PROXMOX_CATALOG_FILE_MAGIC_1_0 { | |
443 | bail!("got unexpected magic number for catalog"); | |
444 | } | |
7d017123 DM |
445 | self.reader.seek(SeekFrom::End(-8))?; |
446 | let start = unsafe { self.reader.read_le_value::<u64>()? }; | |
447 | Ok(DirEntry { name: b"".to_vec(), attr: DirEntryAttribute::Directory { start } }) | |
448 | } | |
9d135fe6 | 449 | |
7d017123 DM |
450 | /// Read all directory entries |
451 | pub fn read_dir( | |
452 | &mut self, | |
453 | parent: &DirEntry, | |
454 | ) -> Result<Vec<DirEntry>, Error> { | |
9d135fe6 | 455 | |
7d017123 DM |
456 | let start = match parent.attr { |
457 | DirEntryAttribute::Directory { start } => start, | |
458 | _ => bail!("parent is not a directory - internal error"), | |
459 | }; | |
9d135fe6 | 460 | |
7d017123 | 461 | let data = self.read_raw_dirinfo_block(start)?; |
9d135fe6 | 462 | |
7d017123 DM |
463 | let mut entry_list = Vec::new(); |
464 | ||
465 | DirInfo::parse(&data, |etype, name, offset, size, mtime| { | |
b423958d | 466 | let entry = DirEntry::new(etype, name.to_vec(), start - offset, size, mtime); |
7d017123 | 467 | entry_list.push(entry); |
8f24a9ea | 468 | Ok(true) |
7d017123 DM |
469 | })?; |
470 | ||
471 | Ok(entry_list) | |
472 | } | |
473 | ||
474 | /// Lockup a DirEntry inside a parent directory | |
475 | pub fn lookup( | |
476 | &mut self, | |
477 | parent: &DirEntry, | |
478 | filename: &[u8], | |
479 | ) -> Result<DirEntry, Error> { | |
480 | ||
481 | let start = match parent.attr { | |
482 | DirEntryAttribute::Directory { start } => start, | |
483 | _ => bail!("parent is not a directory - internal error"), | |
484 | }; | |
485 | ||
486 | let data = self.read_raw_dirinfo_block(start)?; | |
487 | ||
488 | let mut item = None; | |
489 | DirInfo::parse(&data, |etype, name, offset, size, mtime| { | |
490 | if name != filename { | |
8f24a9ea | 491 | return Ok(true); |
7d017123 DM |
492 | } |
493 | ||
b423958d | 494 | let entry = DirEntry::new(etype, name.to_vec(), start - offset, size, mtime); |
7d017123 | 495 | item = Some(entry); |
8f24a9ea | 496 | Ok(false) // stop parsing |
7d017123 DM |
497 | })?; |
498 | ||
499 | match item { | |
500 | None => bail!("no such file"), | |
501 | Some(entry) => Ok(entry), | |
502 | } | |
503 | } | |
504 | ||
505 | /// Read the raw directory info block from current reader position. | |
506 | fn read_raw_dirinfo_block(&mut self, start: u64) -> Result<Vec<u8>, Error> { | |
507 | self.reader.seek(SeekFrom::Start(start))?; | |
508 | let size = catalog_decode_u64(&mut self.reader)?; | |
509 | if size < 1 { bail!("got small directory size {}", size) }; | |
12a1975a | 510 | let data = self.reader.read_exact_allocated(size as usize)?; |
7d017123 DM |
511 | Ok(data) |
512 | } | |
513 | ||
780dd2b0 | 514 | /// Print the content of a directory to stdout |
7d017123 DM |
515 | pub fn dump_dir(&mut self, prefix: &std::path::Path, start: u64) -> Result<(), Error> { |
516 | ||
517 | let data = self.read_raw_dirinfo_block(start)?; | |
9d135fe6 | 518 | |
26566451 | 519 | DirInfo::parse(&data, |etype, name, offset, size, mtime| { |
bf6e3217 DM |
520 | |
521 | let mut path = std::path::PathBuf::from(prefix); | |
55c0b3cc DM |
522 | let name: &OsStr = OsStrExt::from_bytes(name); |
523 | path.push(name); | |
9d135fe6 | 524 | |
9d135fe6 DM |
525 | match etype { |
526 | CatalogEntryType::Directory => { | |
3f1c5b5e | 527 | println!("{} {:?}", etype, path); |
bf6e3217 DM |
528 | if offset > start { |
529 | bail!("got wrong directory offset ({} > {})", offset, start); | |
530 | } | |
531 | let pos = start - offset; | |
532 | self.dump_dir(&path, pos)?; | |
9d135fe6 DM |
533 | } |
534 | CatalogEntryType::File => { | |
bf6e3217 DM |
535 | let dt = Local.timestamp(mtime as i64, 0); |
536 | ||
26566451 DM |
537 | println!( |
538 | "{} {:?} {} {}", | |
3f1c5b5e | 539 | etype, |
26566451 DM |
540 | path, |
541 | size, | |
542 | dt.to_rfc3339_opts(chrono::SecondsFormat::Secs, false), | |
bf6e3217 | 543 | ); |
9d135fe6 | 544 | } |
bf6e3217 | 545 | _ => { |
3f1c5b5e | 546 | println!("{} {:?}", etype, path); |
9d135fe6 DM |
547 | } |
548 | } | |
5d92935e | 549 | |
8f24a9ea | 550 | Ok(true) |
26566451 | 551 | }) |
9d135fe6 | 552 | } |
90dfd0a7 CE |
553 | |
554 | /// Finds all entries matching the given match patterns and calls the | |
555 | /// provided callback on them. | |
556 | pub fn find( | |
557 | &mut self, | |
558 | mut entry: &mut Vec<DirEntry>, | |
559 | pattern: &[MatchPatternSlice], | |
560 | callback: &Box<fn(&[DirEntry])>, | |
561 | ) -> Result<(), Error> { | |
38d9a698 CE |
562 | let parent = entry.last().unwrap(); |
563 | if !parent.is_directory() { | |
90dfd0a7 CE |
564 | return Ok(()) |
565 | } | |
566 | ||
38d9a698 CE |
567 | for e in self.read_dir(parent)? { |
568 | match MatchPatternSlice::match_filename_include( | |
569 | &CString::new(e.name.clone())?, | |
570 | e.is_directory(), | |
571 | pattern, | |
572 | )? { | |
573 | (MatchType::Positive, _) => { | |
90dfd0a7 | 574 | entry.push(e); |
38d9a698 CE |
575 | callback(&entry); |
576 | let pattern = MatchPattern::from_line(b"**/*").unwrap().unwrap(); | |
577 | let child_pattern = vec![pattern.as_slice()]; | |
90dfd0a7 CE |
578 | self.find(&mut entry, &child_pattern, callback)?; |
579 | entry.pop(); | |
580 | } | |
38d9a698 CE |
581 | (MatchType::PartialPositive, child_pattern) |
582 | | (MatchType::PartialNegative, child_pattern) => { | |
90dfd0a7 CE |
583 | entry.push(e); |
584 | self.find(&mut entry, &child_pattern, callback)?; | |
585 | entry.pop(); | |
586 | } | |
38d9a698 | 587 | _ => {} |
90dfd0a7 | 588 | } |
90dfd0a7 | 589 | } |
38d9a698 | 590 | |
90dfd0a7 CE |
591 | Ok(()) |
592 | } | |
9d135fe6 | 593 | } |
12a1975a DM |
594 | |
595 | /// Serialize u64 as short, variable length byte sequence | |
596 | /// | |
597 | /// Stores 7 bits per byte, Bit 8 indicates the end of the sequence (when not set). | |
598 | /// We limit values to a maximum of 2^63. | |
599 | pub fn catalog_encode_u64<W: Write>(writer: &mut W, v: u64) -> Result<(), Error> { | |
600 | let mut enc = Vec::new(); | |
601 | ||
602 | if (v & (1<<63)) != 0 { bail!("catalog_encode_u64 failed - value >= 2^63"); } | |
603 | let mut d = v; | |
604 | loop { | |
605 | if d < 128 { | |
606 | enc.push(d as u8); | |
607 | break; | |
608 | } | |
609 | enc.push((128 | (d & 127)) as u8); | |
610 | d = d >> 7; | |
611 | } | |
612 | writer.write_all(&enc)?; | |
613 | ||
614 | Ok(()) | |
615 | } | |
616 | ||
617 | /// Deserialize u64 from variable length byte sequence | |
618 | /// | |
619 | /// We currently read maximal 9 bytes, which give a maximum of 63 bits. | |
620 | pub fn catalog_decode_u64<R: Read>(reader: &mut R) -> Result<u64, Error> { | |
621 | ||
622 | let mut v: u64 = 0; | |
623 | let mut buf = [0u8]; | |
624 | ||
625 | for i in 0..9 { // only allow 9 bytes (63 bits) | |
626 | if buf.is_empty() { | |
627 | bail!("decode_u64 failed - unexpected EOB"); | |
628 | } | |
629 | reader.read_exact(&mut buf)?; | |
630 | let t = buf[0]; | |
631 | if t < 128 { | |
632 | v |= (t as u64) << (i*7); | |
633 | return Ok(v); | |
634 | } else { | |
635 | v |= ((t & 127) as u64) << (i*7); | |
636 | } | |
637 | } | |
638 | ||
639 | bail!("decode_u64 failed - missing end marker"); | |
640 | } | |
641 | ||
642 | #[test] | |
643 | fn test_catalog_u64_encoder() { | |
644 | ||
645 | fn test_encode_decode(value: u64) { | |
646 | ||
647 | let mut data = Vec::new(); | |
648 | catalog_encode_u64(&mut data, value).unwrap(); | |
649 | ||
650 | //println!("ENCODE {} {:?}", value, data); | |
651 | ||
652 | let slice = &mut &data[..]; | |
653 | let decoded = catalog_decode_u64(slice).unwrap(); | |
654 | ||
655 | //println!("DECODE {}", decoded); | |
656 | ||
657 | assert!(decoded == value); | |
658 | } | |
659 | ||
660 | test_encode_decode(126); | |
661 | test_encode_decode((1<<12)-1); | |
662 | test_encode_decode((1<<20)-1); | |
663 | test_encode_decode((1<<50)-1); | |
664 | test_encode_decode((1<<63)-1); | |
665 | } |