]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-datastore/src/catalog.rs
datastore: catalog: added lifetime to find function
[proxmox-backup.git] / pbs-datastore / src / catalog.rs
CommitLineData
55c0b3cc 1use std::ffi::{CStr, CString, OsStr};
c443f58b 2use std::fmt;
42c2b5be 3use std::io::{Read, Seek, SeekFrom, Write};
c443f58b 4use std::os::unix::ffi::OsStrExt;
9d135fe6 5
c443f58b 6use anyhow::{bail, format_err, Error};
013b1e8b 7use serde::{Deserialize, Serialize};
9d135fe6 8
c443f58b 9use pathpatterns::{MatchList, MatchType};
6ef1b649
WB
10
11use proxmox_io::ReadExt;
12use proxmox_schema::api;
9d135fe6 13
f323e906
WB
14use crate::file_formats::PROXMOX_CATALOG_FILE_MAGIC_1_0;
15
16/// Trait for writing file list catalogs.
17///
18/// A file list catalog simply stores a directory tree. Such catalogs may be used as index to do a
19/// fast search for files.
20pub trait BackupCatalogWriter {
21 fn start_directory(&mut self, name: &CStr) -> Result<(), Error>;
22 fn end_directory(&mut self) -> Result<(), Error>;
23 fn add_file(&mut self, name: &CStr, size: u64, mtime: i64) -> Result<(), Error>;
24 fn add_symlink(&mut self, name: &CStr) -> Result<(), Error>;
25 fn add_hardlink(&mut self, name: &CStr) -> Result<(), Error>;
26 fn add_block_device(&mut self, name: &CStr) -> Result<(), Error>;
27 fn add_char_device(&mut self, name: &CStr) -> Result<(), Error>;
28 fn add_fifo(&mut self, name: &CStr) -> Result<(), Error>;
29 fn add_socket(&mut self, name: &CStr) -> Result<(), Error>;
30}
9d135fe6 31
dc9596de 32#[repr(u8)]
1be05037 33#[derive(Copy, Clone, PartialEq, Eq)]
f323e906 34pub enum CatalogEntryType {
dc9596de
DM
35 Directory = b'd',
36 File = b'f',
37 Symlink = b'l',
38 Hardlink = b'h',
39 BlockDevice = b'b',
40 CharDevice = b'c',
41 Fifo = b'p', // Fifo,Pipe
42 Socket = b's',
43}
44
45impl TryFrom<u8> for CatalogEntryType {
42c2b5be 46 type Error = Error;
dc9596de
DM
47
48 fn try_from(value: u8) -> Result<Self, Error> {
49 Ok(match value {
50 b'd' => CatalogEntryType::Directory,
51 b'f' => CatalogEntryType::File,
52 b'l' => CatalogEntryType::Symlink,
53 b'h' => CatalogEntryType::Hardlink,
54 b'b' => CatalogEntryType::BlockDevice,
55 b'c' => CatalogEntryType::CharDevice,
56 b'p' => CatalogEntryType::Fifo,
57 b's' => CatalogEntryType::Socket,
58 _ => bail!("invalid CatalogEntryType value '{}'", char::from(value)),
59 })
60 }
61}
62
05d18b90
DC
63impl From<&DirEntryAttribute> for CatalogEntryType {
64 fn from(value: &DirEntryAttribute) -> Self {
65 match value {
66 DirEntryAttribute::Directory { .. } => CatalogEntryType::Directory,
67 DirEntryAttribute::File { .. } => CatalogEntryType::File,
68 DirEntryAttribute::Symlink => CatalogEntryType::Symlink,
69 DirEntryAttribute::Hardlink => CatalogEntryType::Hardlink,
70 DirEntryAttribute::BlockDevice => CatalogEntryType::BlockDevice,
71 DirEntryAttribute::CharDevice => CatalogEntryType::CharDevice,
72 DirEntryAttribute::Fifo => CatalogEntryType::Fifo,
73 DirEntryAttribute::Socket => CatalogEntryType::Socket,
74 }
75 }
76}
77
dc9596de
DM
78impl fmt::Display for CatalogEntryType {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "{}", char::from(*self as u8))
81 }
82}
83
780dd2b0
DM
84/// Represents a named directory entry
85///
86/// The ``attr`` property contain the exact type with type specific
87/// attributes.
1be05037 88#[derive(Clone, PartialEq, Eq)]
7d017123
DM
89pub struct DirEntry {
90 pub name: Vec<u8>,
91 pub attr: DirEntryAttribute,
55c0b3cc
DM
92}
93
780dd2b0 94/// Used to specific additional attributes inside DirEntry
1be05037 95#[derive(Clone, Debug, PartialEq, Eq)]
780dd2b0
DM
96pub enum DirEntryAttribute {
97 Directory { start: u64 },
5499bd3d 98 File { size: u64, mtime: i64 },
780dd2b0
DM
99 Symlink,
100 Hardlink,
101 BlockDevice,
102 CharDevice,
103 Fifo,
104 Socket,
105}
106
7d017123 107impl DirEntry {
5499bd3d 108 fn new(etype: CatalogEntryType, name: Vec<u8>, start: u64, size: u64, mtime: i64) -> Self {
7d017123 109 match etype {
42c2b5be
TL
110 CatalogEntryType::Directory => DirEntry {
111 name,
112 attr: DirEntryAttribute::Directory { start },
113 },
114 CatalogEntryType::File => DirEntry {
115 name,
116 attr: DirEntryAttribute::File { size, mtime },
117 },
118 CatalogEntryType::Symlink => DirEntry {
119 name,
120 attr: DirEntryAttribute::Symlink,
121 },
122 CatalogEntryType::Hardlink => DirEntry {
123 name,
124 attr: DirEntryAttribute::Hardlink,
125 },
126 CatalogEntryType::BlockDevice => DirEntry {
127 name,
128 attr: DirEntryAttribute::BlockDevice,
129 },
130 CatalogEntryType::CharDevice => DirEntry {
131 name,
132 attr: DirEntryAttribute::CharDevice,
133 },
134 CatalogEntryType::Fifo => DirEntry {
135 name,
136 attr: DirEntryAttribute::Fifo,
137 },
138 CatalogEntryType::Socket => DirEntry {
139 name,
140 attr: DirEntryAttribute::Socket,
141 },
7d017123
DM
142 }
143 }
c9f00221 144
c443f58b
WB
145 /// Get file mode bits for this entry to be used with the `MatchList` api.
146 pub fn get_file_mode(&self) -> Option<u32> {
42c2b5be
TL
147 Some(match self.attr {
148 DirEntryAttribute::Directory { .. } => pxar::mode::IFDIR,
149 DirEntryAttribute::File { .. } => pxar::mode::IFREG,
150 DirEntryAttribute::Symlink => pxar::mode::IFLNK,
151 DirEntryAttribute::Hardlink => return None,
152 DirEntryAttribute::BlockDevice => pxar::mode::IFBLK,
153 DirEntryAttribute::CharDevice => pxar::mode::IFCHR,
154 DirEntryAttribute::Fifo => pxar::mode::IFIFO,
155 DirEntryAttribute::Socket => pxar::mode::IFSOCK,
156 } as u32)
c443f58b
WB
157 }
158
4145c367 159 /// Check if DirEntry is a directory
c9f00221 160 pub fn is_directory(&self) -> bool {
a6bd6698 161 matches!(self.attr, DirEntryAttribute::Directory { .. })
c9f00221 162 }
c2f91494
CE
163
164 /// Check if DirEntry is a symlink
165 pub fn is_symlink(&self) -> bool {
a6bd6698 166 matches!(self.attr, DirEntryAttribute::Symlink { .. })
c2f91494 167 }
7d017123
DM
168}
169
bf6e3217
DM
170struct DirInfo {
171 name: CString,
172 entries: Vec<DirEntry>,
9d135fe6
DM
173}
174
bf6e3217 175impl DirInfo {
bf6e3217 176 fn new(name: CString) -> Self {
42c2b5be
TL
177 DirInfo {
178 name,
179 entries: Vec::new(),
180 }
bf6e3217
DM
181 }
182
183 fn new_rootdir() -> Self {
184 DirInfo::new(CString::new(b"/".to_vec()).unwrap())
185 }
186
42c2b5be 187 fn encode_entry<W: Write>(writer: &mut W, entry: &DirEntry, pos: u64) -> Result<(), Error> {
bf6e3217 188 match entry {
42c2b5be
TL
189 DirEntry {
190 name,
191 attr: DirEntryAttribute::Directory { start },
192 } => {
12a1975a
DM
193 writer.write_all(&[CatalogEntryType::Directory as u8])?;
194 catalog_encode_u64(writer, name.len() as u64)?;
195 writer.write_all(name)?;
196 catalog_encode_u64(writer, pos - start)?;
bf6e3217 197 }
42c2b5be
TL
198 DirEntry {
199 name,
200 attr: DirEntryAttribute::File { size, mtime },
201 } => {
12a1975a
DM
202 writer.write_all(&[CatalogEntryType::File as u8])?;
203 catalog_encode_u64(writer, name.len() as u64)?;
204 writer.write_all(name)?;
205 catalog_encode_u64(writer, *size)?;
5499bd3d 206 catalog_encode_i64(writer, *mtime)?;
bf6e3217 207 }
42c2b5be
TL
208 DirEntry {
209 name,
210 attr: DirEntryAttribute::Symlink,
211 } => {
12a1975a
DM
212 writer.write_all(&[CatalogEntryType::Symlink as u8])?;
213 catalog_encode_u64(writer, name.len() as u64)?;
214 writer.write_all(name)?;
bf6e3217 215 }
42c2b5be
TL
216 DirEntry {
217 name,
218 attr: DirEntryAttribute::Hardlink,
219 } => {
12a1975a
DM
220 writer.write_all(&[CatalogEntryType::Hardlink as u8])?;
221 catalog_encode_u64(writer, name.len() as u64)?;
222 writer.write_all(name)?;
bf6e3217 223 }
42c2b5be
TL
224 DirEntry {
225 name,
226 attr: DirEntryAttribute::BlockDevice,
227 } => {
12a1975a
DM
228 writer.write_all(&[CatalogEntryType::BlockDevice as u8])?;
229 catalog_encode_u64(writer, name.len() as u64)?;
230 writer.write_all(name)?;
bf6e3217 231 }
42c2b5be
TL
232 DirEntry {
233 name,
234 attr: DirEntryAttribute::CharDevice,
235 } => {
12a1975a
DM
236 writer.write_all(&[CatalogEntryType::CharDevice as u8])?;
237 catalog_encode_u64(writer, name.len() as u64)?;
238 writer.write_all(name)?;
bf6e3217 239 }
42c2b5be
TL
240 DirEntry {
241 name,
242 attr: DirEntryAttribute::Fifo,
243 } => {
12a1975a
DM
244 writer.write_all(&[CatalogEntryType::Fifo as u8])?;
245 catalog_encode_u64(writer, name.len() as u64)?;
246 writer.write_all(name)?;
bf6e3217 247 }
42c2b5be
TL
248 DirEntry {
249 name,
250 attr: DirEntryAttribute::Socket,
251 } => {
12a1975a
DM
252 writer.write_all(&[CatalogEntryType::Socket as u8])?;
253 catalog_encode_u64(writer, name.len() as u64)?;
254 writer.write_all(name)?;
bf6e3217
DM
255 }
256 }
12a1975a 257 Ok(())
9d135fe6 258 }
bf6e3217
DM
259
260 fn encode(self, start: u64) -> Result<(CString, Vec<u8>), Error> {
261 let mut table = Vec::new();
12a1975a 262 catalog_encode_u64(&mut table, self.entries.len() as u64)?;
bf6e3217 263 for entry in self.entries {
12a1975a 264 Self::encode_entry(&mut table, &entry, start)?;
bf6e3217
DM
265 }
266
12a1975a
DM
267 let mut data = Vec::new();
268 catalog_encode_u64(&mut data, table.len() as u64)?;
269 data.extend_from_slice(&table);
270
271 Ok((self.name, data))
9d135fe6 272 }
26566451 273
5499bd3d 274 fn parse<C: FnMut(CatalogEntryType, &[u8], u64, u64, i64) -> Result<bool, Error>>(
26566451
DM
275 data: &[u8],
276 mut callback: C,
277 ) -> Result<(), Error> {
26566451
DM
278 let mut cursor = data;
279
280 let entries = catalog_decode_u64(&mut cursor)?;
281
55c0b3cc
DM
282 let mut name_buf = vec![0u8; 4096];
283
26566451 284 for _ in 0..entries {
42c2b5be 285 let mut buf = [0u8];
26566451
DM
286 cursor.read_exact(&mut buf)?;
287 let etype = CatalogEntryType::try_from(buf[0])?;
288
55c0b3cc
DM
289 let name_len = catalog_decode_u64(&mut cursor)? as usize;
290 if name_len >= name_buf.len() {
42c2b5be
TL
291 bail!(
292 "directory entry name too long ({} >= {})",
293 name_len,
294 name_buf.len()
295 );
55c0b3cc
DM
296 }
297 let name = &mut name_buf[0..name_len];
298 cursor.read_exact(name)?;
26566451 299
8f24a9ea 300 let cont = match etype {
26566451
DM
301 CatalogEntryType::Directory => {
302 let offset = catalog_decode_u64(&mut cursor)?;
8f24a9ea 303 callback(etype, name, offset, 0, 0)?
26566451
DM
304 }
305 CatalogEntryType::File => {
306 let size = catalog_decode_u64(&mut cursor)?;
5499bd3d 307 let mtime = catalog_decode_i64(&mut cursor)?;
8f24a9ea 308 callback(etype, name, 0, size, mtime)?
26566451 309 }
42c2b5be 310 _ => callback(etype, name, 0, 0, 0)?,
8f24a9ea
DM
311 };
312 if !cont {
313 return Ok(());
26566451
DM
314 }
315 }
316
317 if !cursor.is_empty() {
318 bail!("unable to parse whole catalog data block");
319 }
320
321 Ok(())
322 }
bf6e3217
DM
323}
324
780dd2b0
DM
325/// Write small catalog files
326///
327/// A Catalogs simply contains list of files and directories
328/// (directory tree). They are use to find content without having to
329/// search the real archive (which may be large). For files, they
330/// include the last modification time and file size.
bf6e3217
DM
331pub struct CatalogWriter<W> {
332 writer: W,
333 dirstack: Vec<DirInfo>,
334 pos: u64,
335}
336
42c2b5be 337impl<W: Write> CatalogWriter<W> {
780dd2b0 338 /// Create a new CatalogWriter instance
bf6e3217 339 pub fn new(writer: W) -> Result<Self, Error> {
42c2b5be
TL
340 let mut me = Self {
341 writer,
342 dirstack: vec![DirInfo::new_rootdir()],
343 pos: 0,
344 };
c74c074b
DM
345 me.write_all(&PROXMOX_CATALOG_FILE_MAGIC_1_0)?;
346 Ok(me)
9d135fe6 347 }
bf6e3217 348
f89359c2
DM
349 fn write_all(&mut self, data: &[u8]) -> Result<(), Error> {
350 self.writer.write_all(data)?;
351 self.pos += u64::try_from(data.len())?;
352 Ok(())
353 }
354
780dd2b0
DM
355 /// Finish writing, flush all data
356 ///
357 /// This need to be called before drop.
bf6e3217
DM
358 pub fn finish(&mut self) -> Result<(), Error> {
359 if self.dirstack.len() != 1 {
360 bail!("unable to finish catalog at level {}", self.dirstack.len());
361 }
362
363 let dir = self.dirstack.pop().unwrap();
364
365 let start = self.pos;
366 let (_, data) = dir.encode(start)?;
367 self.write_all(&data)?;
368
369 self.write_all(&start.to_le_bytes())?;
370
371 self.writer.flush()?;
372
373 Ok(())
9d135fe6
DM
374 }
375}
376
42c2b5be 377impl<W: Write> BackupCatalogWriter for CatalogWriter<W> {
9d135fe6 378 fn start_directory(&mut self, name: &CStr) -> Result<(), Error> {
bf6e3217
DM
379 let new = DirInfo::new(name.to_owned());
380 self.dirstack.push(new);
9d135fe6
DM
381 Ok(())
382 }
383
384 fn end_directory(&mut self) -> Result<(), Error> {
bf6e3217
DM
385 let (start, name) = match self.dirstack.pop() {
386 Some(dir) => {
387 let start = self.pos;
388 let (name, data) = dir.encode(start)?;
389 self.write_all(&data)?;
390 (start, name)
391 }
392 None => {
393 bail!("got unexpected end_directory level 0");
394 }
395 };
396
42c2b5be
TL
397 let current = self
398 .dirstack
399 .last_mut()
400 .ok_or_else(|| format_err!("outside root"))?;
bf6e3217 401 let name = name.to_bytes().to_vec();
42c2b5be
TL
402 current.entries.push(DirEntry {
403 name,
404 attr: DirEntryAttribute::Directory { start },
405 });
bf6e3217 406
9d135fe6
DM
407 Ok(())
408 }
409
5499bd3d 410 fn add_file(&mut self, name: &CStr, size: u64, mtime: i64) -> Result<(), Error> {
42c2b5be
TL
411 let dir = self
412 .dirstack
413 .last_mut()
414 .ok_or_else(|| format_err!("outside root"))?;
bf6e3217 415 let name = name.to_bytes().to_vec();
42c2b5be
TL
416 dir.entries.push(DirEntry {
417 name,
418 attr: DirEntryAttribute::File { size, mtime },
419 });
9d135fe6
DM
420 Ok(())
421 }
422
423 fn add_symlink(&mut self, name: &CStr) -> Result<(), Error> {
42c2b5be
TL
424 let dir = self
425 .dirstack
426 .last_mut()
427 .ok_or_else(|| format_err!("outside root"))?;
bf6e3217 428 let name = name.to_bytes().to_vec();
42c2b5be
TL
429 dir.entries.push(DirEntry {
430 name,
431 attr: DirEntryAttribute::Symlink,
432 });
9d135fe6
DM
433 Ok(())
434 }
435
436 fn add_hardlink(&mut self, name: &CStr) -> Result<(), Error> {
42c2b5be
TL
437 let dir = self
438 .dirstack
439 .last_mut()
440 .ok_or_else(|| format_err!("outside root"))?;
bf6e3217 441 let name = name.to_bytes().to_vec();
42c2b5be
TL
442 dir.entries.push(DirEntry {
443 name,
444 attr: DirEntryAttribute::Hardlink,
445 });
9d135fe6
DM
446 Ok(())
447 }
448
449 fn add_block_device(&mut self, name: &CStr) -> Result<(), Error> {
42c2b5be
TL
450 let dir = self
451 .dirstack
452 .last_mut()
453 .ok_or_else(|| format_err!("outside root"))?;
bf6e3217 454 let name = name.to_bytes().to_vec();
42c2b5be
TL
455 dir.entries.push(DirEntry {
456 name,
457 attr: DirEntryAttribute::BlockDevice,
458 });
55c0b3cc 459 Ok(())
9d135fe6
DM
460 }
461
462 fn add_char_device(&mut self, name: &CStr) -> Result<(), Error> {
42c2b5be
TL
463 let dir = self
464 .dirstack
465 .last_mut()
466 .ok_or_else(|| format_err!("outside root"))?;
bf6e3217 467 let name = name.to_bytes().to_vec();
42c2b5be
TL
468 dir.entries.push(DirEntry {
469 name,
470 attr: DirEntryAttribute::CharDevice,
471 });
9d135fe6
DM
472 Ok(())
473 }
474
475 fn add_fifo(&mut self, name: &CStr) -> Result<(), Error> {
42c2b5be
TL
476 let dir = self
477 .dirstack
478 .last_mut()
479 .ok_or_else(|| format_err!("outside root"))?;
bf6e3217 480 let name = name.to_bytes().to_vec();
42c2b5be
TL
481 dir.entries.push(DirEntry {
482 name,
483 attr: DirEntryAttribute::Fifo,
484 });
9d135fe6
DM
485 Ok(())
486 }
487
488 fn add_socket(&mut self, name: &CStr) -> Result<(), Error> {
42c2b5be
TL
489 let dir = self
490 .dirstack
491 .last_mut()
492 .ok_or_else(|| format_err!("outside root"))?;
bf6e3217 493 let name = name.to_bytes().to_vec();
42c2b5be
TL
494 dir.entries.push(DirEntry {
495 name,
496 attr: DirEntryAttribute::Socket,
497 });
9d135fe6
DM
498 Ok(())
499 }
500}
501
780dd2b0 502/// Read Catalog files
bf6e3217
DM
503pub struct CatalogReader<R> {
504 reader: R,
505}
9d135fe6 506
42c2b5be 507impl<R: Read + Seek> CatalogReader<R> {
780dd2b0 508 /// Create a new CatalogReader instance
bf6e3217
DM
509 pub fn new(reader: R) -> Self {
510 Self { reader }
9d135fe6
DM
511 }
512
780dd2b0 513 /// Print whole catalog to stdout
bf6e3217 514 pub fn dump(&mut self) -> Result<(), Error> {
2ec208ae
DM
515 let root = self.root()?;
516 match root {
42c2b5be
TL
517 DirEntry {
518 attr: DirEntryAttribute::Directory { start },
519 ..
520 } => self.dump_dir(std::path::Path::new("./"), start),
48efb526 521 _ => bail!("unexpected root entry type, not a directory!"),
2ec208ae 522 }
bf6e3217 523 }
9d135fe6 524
7d017123 525 /// Get the root DirEntry
42c2b5be 526 pub fn root(&mut self) -> Result<DirEntry, Error> {
7d017123 527 // Root dir is special
6aa906b5 528 self.reader.seek(SeekFrom::Start(0))?;
42c2b5be 529 let mut magic = [0u8; 8];
6aa906b5
DM
530 self.reader.read_exact(&mut magic)?;
531 if magic != PROXMOX_CATALOG_FILE_MAGIC_1_0 {
532 bail!("got unexpected magic number for catalog");
533 }
7d017123
DM
534 self.reader.seek(SeekFrom::End(-8))?;
535 let start = unsafe { self.reader.read_le_value::<u64>()? };
42c2b5be
TL
536 Ok(DirEntry {
537 name: b"".to_vec(),
538 attr: DirEntryAttribute::Directory { start },
539 })
7d017123 540 }
9d135fe6 541
7d017123 542 /// Read all directory entries
42c2b5be 543 pub fn read_dir(&mut self, parent: &DirEntry) -> Result<Vec<DirEntry>, Error> {
7d017123
DM
544 let start = match parent.attr {
545 DirEntryAttribute::Directory { start } => start,
546 _ => bail!("parent is not a directory - internal error"),
547 };
9d135fe6 548
7d017123 549 let data = self.read_raw_dirinfo_block(start)?;
9d135fe6 550
7d017123
DM
551 let mut entry_list = Vec::new();
552
553 DirInfo::parse(&data, |etype, name, offset, size, mtime| {
b423958d 554 let entry = DirEntry::new(etype, name.to_vec(), start - offset, size, mtime);
7d017123 555 entry_list.push(entry);
8f24a9ea 556 Ok(true)
7d017123
DM
557 })?;
558
559 Ok(entry_list)
560 }
561
227501c0 562 /// Lookup a DirEntry from an absolute path
42c2b5be 563 pub fn lookup_recursive(&mut self, path: &[u8]) -> Result<DirEntry, Error> {
227501c0
DC
564 let mut current = self.root()?;
565 if path == b"/" {
566 return Ok(current);
567 }
568
569 let components = if !path.is_empty() && path[0] == b'/' {
570 &path[1..]
571 } else {
572 path
42c2b5be
TL
573 }
574 .split(|c| *c == b'/');
227501c0
DC
575
576 for comp in components {
577 if let Some(entry) = self.lookup(&current, comp)? {
578 current = entry;
579 } else {
42c2b5be
TL
580 bail!(
581 "path {:?} not found in catalog",
582 String::from_utf8_lossy(path)
583 );
227501c0
DC
584 }
585 }
586 Ok(current)
587 }
588
7d017123
DM
589 /// Lockup a DirEntry inside a parent directory
590 pub fn lookup(
591 &mut self,
592 parent: &DirEntry,
593 filename: &[u8],
42c2b5be 594 ) -> Result<Option<DirEntry>, Error> {
7d017123
DM
595 let start = match parent.attr {
596 DirEntryAttribute::Directory { start } => start,
597 _ => bail!("parent is not a directory - internal error"),
598 };
599
600 let data = self.read_raw_dirinfo_block(start)?;
601
602 let mut item = None;
603 DirInfo::parse(&data, |etype, name, offset, size, mtime| {
604 if name != filename {
8f24a9ea 605 return Ok(true);
7d017123
DM
606 }
607
b423958d 608 let entry = DirEntry::new(etype, name.to_vec(), start - offset, size, mtime);
7d017123 609 item = Some(entry);
8f24a9ea 610 Ok(false) // stop parsing
7d017123
DM
611 })?;
612
c443f58b 613 Ok(item)
7d017123
DM
614 }
615
616 /// Read the raw directory info block from current reader position.
42c2b5be 617 fn read_raw_dirinfo_block(&mut self, start: u64) -> Result<Vec<u8>, Error> {
7d017123
DM
618 self.reader.seek(SeekFrom::Start(start))?;
619 let size = catalog_decode_u64(&mut self.reader)?;
42c2b5be
TL
620 if size < 1 {
621 bail!("got small directory size {}", size)
622 };
12a1975a 623 let data = self.reader.read_exact_allocated(size as usize)?;
7d017123
DM
624 Ok(data)
625 }
626
780dd2b0 627 /// Print the content of a directory to stdout
7d017123 628 pub fn dump_dir(&mut self, prefix: &std::path::Path, start: u64) -> Result<(), Error> {
7d017123 629 let data = self.read_raw_dirinfo_block(start)?;
9d135fe6 630
26566451 631 DirInfo::parse(&data, |etype, name, offset, size, mtime| {
bf6e3217 632 let mut path = std::path::PathBuf::from(prefix);
55c0b3cc
DM
633 let name: &OsStr = OsStrExt::from_bytes(name);
634 path.push(name);
9d135fe6 635
9d135fe6
DM
636 match etype {
637 CatalogEntryType::Directory => {
dce4b540 638 log::info!("{} {:?}", etype, path);
bf6e3217
DM
639 if offset > start {
640 bail!("got wrong directory offset ({} > {})", offset, start);
641 }
642 let pos = start - offset;
643 self.dump_dir(&path, pos)?;
9d135fe6
DM
644 }
645 CatalogEntryType::File => {
6a7be83e 646 let mut mtime_string = mtime.to_string();
a57413a5 647 if let Ok(s) = proxmox_time::strftime_local("%FT%TZ", mtime) {
6a7be83e
DM
648 mtime_string = s;
649 }
bf6e3217 650
dce4b540 651 log::info!("{} {:?} {} {}", etype, path, size, mtime_string,);
9d135fe6 652 }
bf6e3217 653 _ => {
dce4b540 654 log::info!("{} {:?}", etype, path);
9d135fe6
DM
655 }
656 }
5d92935e 657
8f24a9ea 658 Ok(true)
26566451 659 })
9d135fe6 660 }
90dfd0a7
CE
661
662 /// Finds all entries matching the given match patterns and calls the
663 /// provided callback on them.
e9c18590 664 pub fn find<'a>(
90dfd0a7 665 &mut self,
c443f58b
WB
666 parent: &DirEntry,
667 file_path: &mut Vec<u8>,
e9c18590 668 match_list: &'a impl MatchList<'a>, //&[MatchEntry],
c443f58b 669 callback: &mut dyn FnMut(&[u8]) -> Result<(), Error>,
90dfd0a7 670 ) -> Result<(), Error> {
c443f58b 671 let file_len = file_path.len();
42c2b5be 672 for e in self.read_dir(parent)? {
c443f58b
WB
673 let is_dir = e.is_directory();
674 file_path.truncate(file_len);
675 if !e.name.starts_with(b"/") {
676 file_path.reserve(e.name.len() + 1);
677 file_path.push(b'/');
678 }
679 file_path.extend(&e.name);
680 match match_list.matches(&file_path, e.get_file_mode()) {
b65908fa
GG
681 Ok(Some(MatchType::Exclude)) => continue,
682 Ok(Some(MatchType::Include)) => callback(file_path)?,
683 _ => (),
c443f58b
WB
684 }
685 if is_dir {
686 self.find(&e, file_path, match_list, callback)?;
90dfd0a7 687 }
90dfd0a7 688 }
c443f58b 689 file_path.truncate(file_len);
38d9a698 690
90dfd0a7
CE
691 Ok(())
692 }
86582454
WB
693
694 /// Returns the list of content of the given path
695 pub fn list_dir_contents(&mut self, path: &[u8]) -> Result<Vec<ArchiveEntry>, Error> {
696 let dir = self.lookup_recursive(path)?;
697 let mut res = vec![];
698 let mut path = path.to_vec();
699 if !path.is_empty() && path[0] == b'/' {
700 path.remove(0);
701 }
702
703 for direntry in self.read_dir(&dir)? {
704 let mut components = path.clone();
705 components.push(b'/');
706 components.extend(&direntry.name);
707 let mut entry = ArchiveEntry::new(&components, Some(&direntry.attr));
a7646fe4
TL
708 if let DirEntryAttribute::File { size, mtime } = direntry.attr {
709 entry.size = size.into();
710 entry.mtime = mtime.into();
86582454
WB
711 }
712 res.push(entry);
713 }
714
715 Ok(res)
716 }
9d135fe6 717}
12a1975a 718
5499bd3d
DC
719/// Serialize i64 as short, variable length byte sequence
720///
721/// Stores 7 bits per byte, Bit 8 indicates the end of the sequence (when not set).
722/// If the value is negative, we end with a zero byte (0x00).
ea368a06 723#[allow(clippy::neg_multiply)]
5499bd3d
DC
724pub fn catalog_encode_i64<W: Write>(writer: &mut W, v: i64) -> Result<(), Error> {
725 let mut enc = Vec::new();
726
727 let mut d = if v < 0 {
728 (-1 * (v + 1)) as u64 + 1 // also handles i64::MIN
729 } else {
730 v as u64
731 };
732
733 loop {
734 if d < 128 {
735 if v < 0 {
736 enc.push(128 | d as u8);
737 enc.push(0u8);
738 } else {
739 enc.push(d as u8);
740 }
741 break;
742 }
743 enc.push((128 | (d & 127)) as u8);
a3775bb4 744 d >>= 7;
5499bd3d
DC
745 }
746 writer.write_all(&enc)?;
747
748 Ok(())
749}
750
751/// Deserialize i64 from variable length byte sequence
752///
753/// We currently read maximal 11 bytes, which give a maximum of 70 bits + sign.
754/// this method is compatible with catalog_encode_u64 iff the
755/// value encoded is <= 2^63 (values > 2^63 cannot be represented in an i64)
ea368a06 756#[allow(clippy::neg_multiply)]
5499bd3d 757pub fn catalog_decode_i64<R: Read>(reader: &mut R) -> Result<i64, Error> {
5499bd3d
DC
758 let mut v: u64 = 0;
759 let mut buf = [0u8];
760
42c2b5be
TL
761 for i in 0..11 {
762 // only allow 11 bytes (70 bits + sign marker)
5499bd3d
DC
763 if buf.is_empty() {
764 bail!("decode_i64 failed - unexpected EOB");
765 }
766 reader.read_exact(&mut buf)?;
767
768 let t = buf[0];
769
770 if t == 0 {
771 if v == 0 {
772 return Ok(0);
773 }
774 return Ok(((v - 1) as i64 * -1) - 1); // also handles i64::MIN
775 } else if t < 128 {
42c2b5be 776 v |= (t as u64) << (i * 7);
5499bd3d
DC
777 return Ok(v as i64);
778 } else {
42c2b5be 779 v |= ((t & 127) as u64) << (i * 7);
5499bd3d
DC
780 }
781 }
782
783 bail!("decode_i64 failed - missing end marker");
784}
785
12a1975a
DM
786/// Serialize u64 as short, variable length byte sequence
787///
788/// Stores 7 bits per byte, Bit 8 indicates the end of the sequence (when not set).
12a1975a
DM
789pub fn catalog_encode_u64<W: Write>(writer: &mut W, v: u64) -> Result<(), Error> {
790 let mut enc = Vec::new();
791
12a1975a
DM
792 let mut d = v;
793 loop {
794 if d < 128 {
795 enc.push(d as u8);
796 break;
797 }
798 enc.push((128 | (d & 127)) as u8);
a3775bb4 799 d >>= 7;
12a1975a
DM
800 }
801 writer.write_all(&enc)?;
802
803 Ok(())
804}
805
806/// Deserialize u64 from variable length byte sequence
807///
5499bd3d
DC
808/// We currently read maximal 10 bytes, which give a maximum of 70 bits,
809/// but we currently only encode up to 64 bits
12a1975a 810pub fn catalog_decode_u64<R: Read>(reader: &mut R) -> Result<u64, Error> {
12a1975a
DM
811 let mut v: u64 = 0;
812 let mut buf = [0u8];
813
42c2b5be
TL
814 for i in 0..10 {
815 // only allow 10 bytes (70 bits)
12a1975a
DM
816 if buf.is_empty() {
817 bail!("decode_u64 failed - unexpected EOB");
818 }
819 reader.read_exact(&mut buf)?;
820 let t = buf[0];
821 if t < 128 {
42c2b5be 822 v |= (t as u64) << (i * 7);
12a1975a
DM
823 return Ok(v);
824 } else {
42c2b5be 825 v |= ((t & 127) as u64) << (i * 7);
12a1975a
DM
826 }
827 }
828
829 bail!("decode_u64 failed - missing end marker");
830}
831
832#[test]
833fn test_catalog_u64_encoder() {
12a1975a 834 fn test_encode_decode(value: u64) {
12a1975a
DM
835 let mut data = Vec::new();
836 catalog_encode_u64(&mut data, value).unwrap();
837
838 //println!("ENCODE {} {:?}", value, data);
839
840 let slice = &mut &data[..];
841 let decoded = catalog_decode_u64(slice).unwrap();
842
843 //println!("DECODE {}", decoded);
844
845 assert!(decoded == value);
846 }
847
5499bd3d
DC
848 test_encode_decode(u64::MIN);
849 test_encode_decode(126);
42c2b5be
TL
850 test_encode_decode((1 << 12) - 1);
851 test_encode_decode((1 << 20) - 1);
852 test_encode_decode((1 << 50) - 1);
5499bd3d
DC
853 test_encode_decode(u64::MAX);
854}
855
856#[test]
857fn test_catalog_i64_encoder() {
5499bd3d 858 fn test_encode_decode(value: i64) {
5499bd3d
DC
859 let mut data = Vec::new();
860 catalog_encode_i64(&mut data, value).unwrap();
861
862 let slice = &mut &data[..];
863 let decoded = catalog_decode_i64(slice).unwrap();
864
865 assert!(decoded == value);
866 }
867
868 test_encode_decode(0);
869 test_encode_decode(-0);
870 test_encode_decode(126);
871 test_encode_decode(-126);
42c2b5be
TL
872 test_encode_decode((1 << 12) - 1);
873 test_encode_decode(-(1 << 12) - 1);
874 test_encode_decode((1 << 20) - 1);
875 test_encode_decode(-(1 << 20) - 1);
5499bd3d
DC
876 test_encode_decode(i64::MIN);
877 test_encode_decode(i64::MAX);
878}
879
880#[test]
881fn test_catalog_i64_compatibility() {
5499bd3d 882 fn test_encode_decode(value: u64) {
5499bd3d
DC
883 let mut data = Vec::new();
884 catalog_encode_u64(&mut data, value).unwrap();
885
886 let slice = &mut &data[..];
887 let decoded = catalog_decode_i64(slice).unwrap() as u64;
888
889 assert!(decoded == value);
890 }
891
892 test_encode_decode(u64::MIN);
12a1975a 893 test_encode_decode(126);
42c2b5be
TL
894 test_encode_decode((1 << 12) - 1);
895 test_encode_decode((1 << 20) - 1);
896 test_encode_decode((1 << 50) - 1);
5499bd3d 897 test_encode_decode(u64::MAX);
12a1975a 898}
013b1e8b
WB
899
900/// An entry in a hierarchy of files for restore and listing.
901#[api]
902#[derive(Serialize, Deserialize)]
903pub struct ArchiveEntry {
904 /// Base64-encoded full path to the file, including the filename
905 pub filepath: String,
906 /// Displayable filename text for UIs
907 pub text: String,
908 /// File or directory type of this entry
909 #[serde(rename = "type")]
910 pub entry_type: String,
911 /// Is this entry a leaf node, or does it have children (i.e. a directory)?
912 pub leaf: bool,
a7646fe4 913 /// The file size, if entry_type is 'f' (file)
42c2b5be 914 #[serde(skip_serializing_if = "Option::is_none")]
013b1e8b
WB
915 pub size: Option<u64>,
916 /// The file "last modified" time stamp, if entry_type is 'f' (file)
42c2b5be 917 #[serde(skip_serializing_if = "Option::is_none")]
013b1e8b
WB
918 pub mtime: Option<i64>,
919}
920
921impl ArchiveEntry {
922 pub fn new(filepath: &[u8], entry_type: Option<&DirEntryAttribute>) -> Self {
923 let size = match entry_type {
924 Some(DirEntryAttribute::File { size, .. }) => Some(*size),
925 _ => None,
926 };
927 Self::new_with_size(filepath, entry_type, size)
928 }
929
930 pub fn new_with_size(
931 filepath: &[u8],
932 entry_type: Option<&DirEntryAttribute>,
933 size: Option<u64>,
934 ) -> Self {
935 Self {
936 filepath: base64::encode(filepath),
937 text: String::from_utf8_lossy(filepath.split(|x| *x == b'/').last().unwrap())
938 .to_string(),
939 entry_type: match entry_type {
940 Some(entry_type) => CatalogEntryType::from(entry_type).to_string(),
941 None => "v".to_owned(),
942 },
943 leaf: !matches!(entry_type, None | Some(DirEntryAttribute::Directory { .. })),
944 size,
945 mtime: match entry_type {
946 Some(DirEntryAttribute::File { mtime, .. }) => Some(*mtime),
947 _ => None,
948 },
949 }
950 }
951}