3 use std
::ffi
::{CStr, CString, OsStr}
;
4 use std
::os
::unix
::ffi
::OsStrExt
;
5 use std
::io
::{Read, Write, Seek, SeekFrom}
;
6 use std
::convert
::TryFrom
;
8 use chrono
::offset
::{TimeZone, Local}
;
10 use proxmox
::tools
::io
::ReadExt
;
12 use crate::pxar
::catalog
::BackupCatalogWriter
;
13 use crate::pxar
::{MatchPattern, MatchPatternSlice, MatchType}
;
14 use crate::backup
::file_formats
::PROXMOX_CATALOG_FILE_MAGIC_1_0
;
17 #[derive(Copy,Clone,PartialEq)]
18 enum CatalogEntryType
{
25 Fifo
= b'p'
, // Fifo,Pipe
29 impl TryFrom
<u8> for CatalogEntryType
{
32 fn try_from(value
: u8) -> Result
<Self, Error
> {
34 b'd'
=> CatalogEntryType
::Directory
,
35 b'f'
=> CatalogEntryType
::File
,
36 b'l'
=> CatalogEntryType
::Symlink
,
37 b'h'
=> CatalogEntryType
::Hardlink
,
38 b'b'
=> CatalogEntryType
::BlockDevice
,
39 b'c'
=> CatalogEntryType
::CharDevice
,
40 b'p'
=> CatalogEntryType
::Fifo
,
41 b's'
=> CatalogEntryType
::Socket
,
42 _
=> bail
!("invalid CatalogEntryType value '{}'", char::from(value
)),
47 impl fmt
::Display
for CatalogEntryType
{
48 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
49 write
!(f
, "{}", char::from(*self as u8))
53 /// Represents a named directory entry
55 /// The ``attr`` property contain the exact type with type specific
60 pub attr
: DirEntryAttribute
,
63 /// Used to specific additional attributes inside DirEntry
65 pub enum DirEntryAttribute
{
66 Directory { start: u64 }
,
67 File { size: u64, mtime: u64 }
,
78 fn new(etype
: CatalogEntryType
, name
: Vec
<u8>, start
: u64, size
: u64, mtime
:u64) -> Self {
80 CatalogEntryType
::Directory
=> {
81 DirEntry { name, attr: DirEntryAttribute::Directory { start }
}
83 CatalogEntryType
::File
=> {
84 DirEntry { name, attr: DirEntryAttribute::File { size, mtime }
}
86 CatalogEntryType
::Symlink
=> {
87 DirEntry { name, attr: DirEntryAttribute::Symlink }
89 CatalogEntryType
::Hardlink
=> {
90 DirEntry { name, attr: DirEntryAttribute::Hardlink }
92 CatalogEntryType
::BlockDevice
=> {
93 DirEntry { name, attr: DirEntryAttribute::BlockDevice }
95 CatalogEntryType
::CharDevice
=> {
96 DirEntry { name, attr: DirEntryAttribute::CharDevice }
98 CatalogEntryType
::Fifo
=> {
99 DirEntry { name, attr: DirEntryAttribute::Fifo }
101 CatalogEntryType
::Socket
=> {
102 DirEntry { name, attr: DirEntryAttribute::Socket }
107 pub fn is_directory(&self) -> bool
{
109 DirEntryAttribute
::Directory { .. }
=> true,
117 entries
: Vec
<DirEntry
>,
122 fn new(name
: CString
) -> Self {
123 DirInfo { name, entries: Vec::new() }
126 fn new_rootdir() -> Self {
127 DirInfo
::new(CString
::new(b
"/".to_vec()).unwrap())
130 fn encode_entry
<W
: Write
>(
134 ) -> Result
<(), Error
> {
136 DirEntry { name, attr: DirEntryAttribute::Directory { start }
} => {
137 writer
.write_all(&[CatalogEntryType
::Directory
as u8])?
;
138 catalog_encode_u64(writer
, name
.len() as u64)?
;
139 writer
.write_all(name
)?
;
140 catalog_encode_u64(writer
, pos
- start
)?
;
142 DirEntry { name, attr: DirEntryAttribute::File { size, mtime }
} => {
143 writer
.write_all(&[CatalogEntryType
::File
as u8])?
;
144 catalog_encode_u64(writer
, name
.len() as u64)?
;
145 writer
.write_all(name
)?
;
146 catalog_encode_u64(writer
, *size
)?
;
147 catalog_encode_u64(writer
, *mtime
)?
;
149 DirEntry { name, attr: DirEntryAttribute::Symlink }
=> {
150 writer
.write_all(&[CatalogEntryType
::Symlink
as u8])?
;
151 catalog_encode_u64(writer
, name
.len() as u64)?
;
152 writer
.write_all(name
)?
;
154 DirEntry { name, attr: DirEntryAttribute::Hardlink }
=> {
155 writer
.write_all(&[CatalogEntryType
::Hardlink
as u8])?
;
156 catalog_encode_u64(writer
, name
.len() as u64)?
;
157 writer
.write_all(name
)?
;
159 DirEntry { name, attr: DirEntryAttribute::BlockDevice }
=> {
160 writer
.write_all(&[CatalogEntryType
::BlockDevice
as u8])?
;
161 catalog_encode_u64(writer
, name
.len() as u64)?
;
162 writer
.write_all(name
)?
;
164 DirEntry { name, attr: DirEntryAttribute::CharDevice }
=> {
165 writer
.write_all(&[CatalogEntryType
::CharDevice
as u8])?
;
166 catalog_encode_u64(writer
, name
.len() as u64)?
;
167 writer
.write_all(name
)?
;
169 DirEntry { name, attr: DirEntryAttribute::Fifo }
=> {
170 writer
.write_all(&[CatalogEntryType
::Fifo
as u8])?
;
171 catalog_encode_u64(writer
, name
.len() as u64)?
;
172 writer
.write_all(name
)?
;
174 DirEntry { name, attr: DirEntryAttribute::Socket }
=> {
175 writer
.write_all(&[CatalogEntryType
::Socket
as u8])?
;
176 catalog_encode_u64(writer
, name
.len() as u64)?
;
177 writer
.write_all(name
)?
;
183 fn encode(self, start
: u64) -> Result
<(CString
, Vec
<u8>), Error
> {
184 let mut table
= Vec
::new();
185 catalog_encode_u64(&mut table
, self.entries
.len() as u64)?
;
186 for entry
in self.entries
{
187 Self::encode_entry(&mut table
, &entry
, start
)?
;
190 let mut data
= Vec
::new();
191 catalog_encode_u64(&mut data
, table
.len() as u64)?
;
192 data
.extend_from_slice(&table
);
194 Ok((self.name
, data
))
197 fn parse
<C
: FnMut(CatalogEntryType
, &[u8], u64, u64, u64) -> Result
<bool
, Error
>>(
200 ) -> Result
<(), Error
> {
202 let mut cursor
= data
;
204 let entries
= catalog_decode_u64(&mut cursor
)?
;
206 let mut name_buf
= vec
![0u8; 4096];
208 for _
in 0..entries
{
210 let mut buf
= [ 0u8 ];
211 cursor
.read_exact(&mut buf
)?
;
212 let etype
= CatalogEntryType
::try_from(buf
[0])?
;
214 let name_len
= catalog_decode_u64(&mut cursor
)?
as usize;
215 if name_len
>= name_buf
.len() {
216 bail
!("directory entry name too long ({} >= {})", name_len
, name_buf
.len());
218 let name
= &mut name_buf
[0..name_len
];
219 cursor
.read_exact(name
)?
;
221 let cont
= match etype
{
222 CatalogEntryType
::Directory
=> {
223 let offset
= catalog_decode_u64(&mut cursor
)?
;
224 callback(etype
, name
, offset
, 0, 0)?
226 CatalogEntryType
::File
=> {
227 let size
= catalog_decode_u64(&mut cursor
)?
;
228 let mtime
= catalog_decode_u64(&mut cursor
)?
;
229 callback(etype
, name
, 0, size
, mtime
)?
232 callback(etype
, name
, 0, 0, 0)?
240 if !cursor
.is_empty() {
241 bail
!("unable to parse whole catalog data block");
248 /// Write small catalog files
250 /// A Catalogs simply contains list of files and directories
251 /// (directory tree). They are use to find content without having to
252 /// search the real archive (which may be large). For files, they
253 /// include the last modification time and file size.
254 pub struct CatalogWriter
<W
> {
256 dirstack
: Vec
<DirInfo
>,
260 impl <W
: Write
> CatalogWriter
<W
> {
262 /// Create a new CatalogWriter instance
263 pub fn new(writer
: W
) -> Result
<Self, Error
> {
264 let mut me
= Self { writer, dirstack: vec![ DirInfo::new_rootdir() ], pos: 0 }
;
265 me
.write_all(&PROXMOX_CATALOG_FILE_MAGIC_1_0
)?
;
269 fn write_all(&mut self, data
: &[u8]) -> Result
<(), Error
> {
270 self.writer
.write_all(data
)?
;
271 self.pos
+= u64::try_from(data
.len())?
;
275 /// Finish writing, flush all data
277 /// This need to be called before drop.
278 pub fn finish(&mut self) -> Result
<(), Error
> {
279 if self.dirstack
.len() != 1 {
280 bail
!("unable to finish catalog at level {}", self.dirstack
.len());
283 let dir
= self.dirstack
.pop().unwrap();
285 let start
= self.pos
;
286 let (_
, data
) = dir
.encode(start
)?
;
287 self.write_all(&data
)?
;
289 self.write_all(&start
.to_le_bytes())?
;
291 self.writer
.flush()?
;
297 impl <W
: Write
> BackupCatalogWriter
for CatalogWriter
<W
> {
299 fn start_directory(&mut self, name
: &CStr
) -> Result
<(), Error
> {
300 let new
= DirInfo
::new(name
.to_owned());
301 self.dirstack
.push(new
);
305 fn end_directory(&mut self) -> Result
<(), Error
> {
306 let (start
, name
) = match self.dirstack
.pop() {
308 let start
= self.pos
;
309 let (name
, data
) = dir
.encode(start
)?
;
310 self.write_all(&data
)?
;
314 bail
!("got unexpected end_directory level 0");
318 let current
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
319 let name
= name
.to_bytes().to_vec();
320 current
.entries
.push(DirEntry { name, attr: DirEntryAttribute::Directory { start }
});
325 fn add_file(&mut self, name
: &CStr
, size
: u64, mtime
: u64) -> Result
<(), Error
> {
326 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
327 let name
= name
.to_bytes().to_vec();
328 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::File { size, mtime }
});
332 fn add_symlink(&mut self, name
: &CStr
) -> Result
<(), Error
> {
333 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
334 let name
= name
.to_bytes().to_vec();
335 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::Symlink }
);
339 fn add_hardlink(&mut self, name
: &CStr
) -> Result
<(), Error
> {
340 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
341 let name
= name
.to_bytes().to_vec();
342 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::Hardlink }
);
346 fn add_block_device(&mut self, name
: &CStr
) -> Result
<(), Error
> {
347 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
348 let name
= name
.to_bytes().to_vec();
349 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::BlockDevice }
);
353 fn add_char_device(&mut self, name
: &CStr
) -> Result
<(), Error
> {
354 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
355 let name
= name
.to_bytes().to_vec();
356 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::CharDevice }
);
360 fn add_fifo(&mut self, name
: &CStr
) -> Result
<(), Error
> {
361 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
362 let name
= name
.to_bytes().to_vec();
363 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::Fifo }
);
367 fn add_socket(&mut self, name
: &CStr
) -> Result
<(), Error
> {
368 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
369 let name
= name
.to_bytes().to_vec();
370 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::Socket }
);
375 // fixme: move to somehere else?
376 /// Implement Write to tokio mpsc channel Sender
377 pub struct SenderWriter(tokio
::sync
::mpsc
::Sender
<Result
<Vec
<u8>, Error
>>);
380 pub fn new(sender
: tokio
::sync
::mpsc
::Sender
<Result
<Vec
<u8>, Error
>>) -> Self {
385 impl Write
for SenderWriter
{
386 fn write(&mut self, buf
: &[u8]) -> Result
<usize, std
::io
::Error
> {
387 tokio
::task
::block_in_place(|| {
388 futures
::executor
::block_on(async
move {
389 self.0.send(Ok(buf
.to_vec())).await
390 .map_err(|err
| std
::io
::Error
::new(std
::io
::ErrorKind
::Other
, err
.to_string()))?
;
396 fn flush(&mut self) -> Result
<(), std
::io
::Error
> {
401 /// Read Catalog files
402 pub struct CatalogReader
<R
> {
406 impl <R
: Read
+ Seek
> CatalogReader
<R
> {
408 /// Create a new CatalogReader instance
409 pub fn new(reader
: R
) -> Self {
413 /// Print whole catalog to stdout
414 pub fn dump(&mut self) -> Result
<(), Error
> {
416 let root
= self.root()?
;
418 DirEntry { attr: DirEntryAttribute::Directory { start }
, .. }=> {
419 self.dump_dir(std
::path
::Path
::new("./"), start
)
425 /// Get the root DirEntry
426 pub fn root(&mut self) -> Result
<DirEntry
, Error
> {
427 // Root dir is special
428 self.reader
.seek(SeekFrom
::Start(0))?
;
429 let mut magic
= [ 0u8; 8];
430 self.reader
.read_exact(&mut magic
)?
;
431 if magic
!= PROXMOX_CATALOG_FILE_MAGIC_1_0
{
432 bail
!("got unexpected magic number for catalog");
434 self.reader
.seek(SeekFrom
::End(-8))?
;
435 let start
= unsafe { self.reader.read_le_value::<u64>()? }
;
436 Ok(DirEntry { name: b"".to_vec(), attr: DirEntryAttribute::Directory { start }
})
439 /// Read all directory entries
443 ) -> Result
<Vec
<DirEntry
>, Error
> {
445 let start
= match parent
.attr
{
446 DirEntryAttribute
::Directory { start }
=> start
,
447 _
=> bail
!("parent is not a directory - internal error"),
450 let data
= self.read_raw_dirinfo_block(start
)?
;
452 let mut entry_list
= Vec
::new();
454 DirInfo
::parse(&data
, |etype
, name
, offset
, size
, mtime
| {
455 let entry
= DirEntry
::new(etype
, name
.to_vec(), start
- offset
, size
, mtime
);
456 entry_list
.push(entry
);
463 /// Lockup a DirEntry inside a parent directory
468 ) -> Result
<DirEntry
, Error
> {
470 let start
= match parent
.attr
{
471 DirEntryAttribute
::Directory { start }
=> start
,
472 _
=> bail
!("parent is not a directory - internal error"),
475 let data
= self.read_raw_dirinfo_block(start
)?
;
478 DirInfo
::parse(&data
, |etype
, name
, offset
, size
, mtime
| {
479 if name
!= filename
{
483 let entry
= DirEntry
::new(etype
, name
.to_vec(), start
- offset
, size
, mtime
);
485 Ok(false) // stop parsing
489 None
=> bail
!("no such file"),
490 Some(entry
) => Ok(entry
),
494 /// Read the raw directory info block from current reader position.
495 fn read_raw_dirinfo_block(&mut self, start
: u64) -> Result
<Vec
<u8>, Error
> {
496 self.reader
.seek(SeekFrom
::Start(start
))?
;
497 let size
= catalog_decode_u64(&mut self.reader
)?
;
498 if size
< 1 { bail!("got small directory size {}
", size) };
499 let data = self.reader.read_exact_allocated(size as usize)?;
503 /// Print the content of a directory to stdout
504 pub fn dump_dir(&mut self, prefix: &std::path::Path, start: u64) -> Result<(), Error> {
506 let data = self.read_raw_dirinfo_block(start)?;
508 DirInfo::parse(&data, |etype, name, offset, size, mtime| {
510 let mut path = std::path::PathBuf::from(prefix);
511 let name: &OsStr = OsStrExt::from_bytes(name);
515 CatalogEntryType::Directory => {
516 println!("{} {:?}
", etype, path);
518 bail!("got wrong directory
offset ({}
> {}
)", offset, start);
520 let pos = start - offset;
521 self.dump_dir(&path, pos)?;
523 CatalogEntryType::File => {
524 let dt = Local.timestamp(mtime as i64, 0);
531 dt.to_rfc3339_opts(chrono::SecondsFormat::Secs, false),
535 println!("{} {:?}
", etype, path);
543 /// Finds all entries matching the given match patterns and calls the
544 /// provided callback on them.
547 mut entry: &mut Vec<DirEntry>,
548 pattern: &[MatchPatternSlice],
549 callback: &Box<fn(&[DirEntry])>,
550 ) -> Result<(), Error> {
551 let parent = entry.last().unwrap();
552 if !parent.is_directory() {
556 for e in self.read_dir(parent)? {
557 match MatchPatternSlice::match_filename_include(
558 &CString::new(e.name.clone())?,
562 (MatchType::Positive, _) => {
565 let pattern = MatchPattern::from_line(b"**/
*").unwrap().unwrap();
566 let child_pattern = vec![pattern.as_slice()];
567 self.find(&mut entry, &child_pattern, callback)?;
570 (MatchType::PartialPositive, child_pattern)
571 | (MatchType::PartialNegative, child_pattern) => {
573 self.find(&mut entry, &child_pattern, callback)?;
584 /// Serialize u64 as short, variable length byte sequence
586 /// Stores 7 bits per byte, Bit 8 indicates the end of the sequence (when not set).
587 /// We limit values to a maximum of 2^63.
588 pub fn catalog_encode_u64<W: Write>(writer: &mut W, v: u64) -> Result<(), Error> {
589 let mut enc = Vec::new();
591 if (v & (1<<63)) != 0 { bail!("catalog_encode_u64 failed - value >= 2^63"); }
598 enc.push((128 | (d & 127)) as u8);
601 writer.write_all(&enc)?;
606 /// Deserialize u64 from variable length byte sequence
608 /// We currently read maximal 9 bytes, which give a maximum of 63 bits.
609 pub fn catalog_decode_u64<R: Read>(reader: &mut R) -> Result<u64, Error> {
614 for i in 0..9 { // only allow 9 bytes (63 bits)
616 bail!("decode_u64 failed
- unexpected EOB
");
618 reader.read_exact(&mut buf)?;
621 v |= (t as u64) << (i*7);
624 v |= ((t & 127) as u64) << (i*7);
628 bail!("decode_u64 failed
- missing end marker
");
632 fn test_catalog_u64_encoder() {
634 fn test_encode_decode(value: u64) {
636 let mut data = Vec::new();
637 catalog_encode_u64(&mut data, value).unwrap();
639 //println!("ENCODE {} {:?}
", value, data);
641 let slice = &mut &data[..];
642 let decoded = catalog_decode_u64(slice).unwrap();
644 //println!("DECODE {}
", decoded);
646 assert!(decoded == value);
649 test_encode_decode(126);
650 test_encode_decode((1<<12)-1);
651 test_encode_decode((1<<20)-1);
652 test_encode_decode((1<<50)-1);
653 test_encode_decode((1<<63)-1);