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::backup
::file_formats
::PROXMOX_CATALOG_FILE_MAGIC_1_0
;
16 #[derive(Copy,Clone,PartialEq)]
17 enum CatalogEntryType
{
24 Fifo
= b'p'
, // Fifo,Pipe
28 impl TryFrom
<u8> for CatalogEntryType
{
31 fn try_from(value
: u8) -> Result
<Self, Error
> {
33 b'd'
=> CatalogEntryType
::Directory
,
34 b'f'
=> CatalogEntryType
::File
,
35 b'l'
=> CatalogEntryType
::Symlink
,
36 b'h'
=> CatalogEntryType
::Hardlink
,
37 b'b'
=> CatalogEntryType
::BlockDevice
,
38 b'c'
=> CatalogEntryType
::CharDevice
,
39 b'p'
=> CatalogEntryType
::Fifo
,
40 b's'
=> CatalogEntryType
::Socket
,
41 _
=> bail
!("invalid CatalogEntryType value '{}'", char::from(value
)),
46 impl fmt
::Display
for CatalogEntryType
{
47 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
48 write
!(f
, "{}", char::from(*self as u8))
54 pub attr
: DirEntryAttribute
,
59 fn new(etype
: CatalogEntryType
, name
: Vec
<u8>, start
: u64, size
: u64, mtime
:u64) -> Self {
61 CatalogEntryType
::Directory
=> {
62 DirEntry { name, attr: DirEntryAttribute::Directory { start }
}
64 CatalogEntryType
::File
=> {
65 DirEntry { name, attr: DirEntryAttribute::File { size, mtime }
}
67 CatalogEntryType
::Symlink
=> {
68 DirEntry { name, attr: DirEntryAttribute::Symlink }
70 CatalogEntryType
::Hardlink
=> {
71 DirEntry { name, attr: DirEntryAttribute::Hardlink }
73 CatalogEntryType
::BlockDevice
=> {
74 DirEntry { name, attr: DirEntryAttribute::BlockDevice }
76 CatalogEntryType
::CharDevice
=> {
77 DirEntry { name, attr: DirEntryAttribute::CharDevice }
79 CatalogEntryType
::Fifo
=> {
80 DirEntry { name, attr: DirEntryAttribute::Fifo }
82 CatalogEntryType
::Socket
=> {
83 DirEntry { name, attr: DirEntryAttribute::Socket }
89 pub enum DirEntryAttribute
{
90 Directory { start: u64 }
,
91 File { size: u64, mtime: u64 }
,
102 entries
: Vec
<DirEntry
>,
107 fn new(name
: CString
) -> Self {
108 DirInfo { name, entries: Vec::new() }
111 fn new_rootdir() -> Self {
112 DirInfo
::new(CString
::new(b
"/".to_vec()).unwrap())
115 fn encode_entry
<W
: Write
>(
119 ) -> Result
<(), Error
> {
121 DirEntry { name, attr: DirEntryAttribute::Directory { start }
} => {
122 writer
.write_all(&[CatalogEntryType
::Directory
as u8])?
;
123 catalog_encode_u64(writer
, name
.len() as u64)?
;
124 writer
.write_all(name
)?
;
125 catalog_encode_u64(writer
, pos
- start
)?
;
127 DirEntry { name, attr: DirEntryAttribute::File { size, mtime }
} => {
128 writer
.write_all(&[CatalogEntryType
::File
as u8])?
;
129 catalog_encode_u64(writer
, name
.len() as u64)?
;
130 writer
.write_all(name
)?
;
131 catalog_encode_u64(writer
, *size
)?
;
132 catalog_encode_u64(writer
, *mtime
)?
;
134 DirEntry { name, attr: DirEntryAttribute::Symlink }
=> {
135 writer
.write_all(&[CatalogEntryType
::Symlink
as u8])?
;
136 catalog_encode_u64(writer
, name
.len() as u64)?
;
137 writer
.write_all(name
)?
;
139 DirEntry { name, attr: DirEntryAttribute::Hardlink }
=> {
140 writer
.write_all(&[CatalogEntryType
::Hardlink
as u8])?
;
141 catalog_encode_u64(writer
, name
.len() as u64)?
;
142 writer
.write_all(name
)?
;
144 DirEntry { name, attr: DirEntryAttribute::BlockDevice }
=> {
145 writer
.write_all(&[CatalogEntryType
::BlockDevice
as u8])?
;
146 catalog_encode_u64(writer
, name
.len() as u64)?
;
147 writer
.write_all(name
)?
;
149 DirEntry { name, attr: DirEntryAttribute::CharDevice }
=> {
150 writer
.write_all(&[CatalogEntryType
::CharDevice
as u8])?
;
151 catalog_encode_u64(writer
, name
.len() as u64)?
;
152 writer
.write_all(name
)?
;
154 DirEntry { name, attr: DirEntryAttribute::Fifo }
=> {
155 writer
.write_all(&[CatalogEntryType
::Fifo
as u8])?
;
156 catalog_encode_u64(writer
, name
.len() as u64)?
;
157 writer
.write_all(name
)?
;
159 DirEntry { name, attr: DirEntryAttribute::Socket }
=> {
160 writer
.write_all(&[CatalogEntryType
::Socket
as u8])?
;
161 catalog_encode_u64(writer
, name
.len() as u64)?
;
162 writer
.write_all(name
)?
;
168 fn encode(self, start
: u64) -> Result
<(CString
, Vec
<u8>), Error
> {
169 let mut table
= Vec
::new();
170 catalog_encode_u64(&mut table
, self.entries
.len() as u64)?
;
171 for entry
in self.entries
{
172 Self::encode_entry(&mut table
, &entry
, start
)?
;
175 let mut data
= Vec
::new();
176 catalog_encode_u64(&mut data
, table
.len() as u64)?
;
177 data
.extend_from_slice(&table
);
179 Ok((self.name
, data
))
182 fn parse
<C
: FnMut(CatalogEntryType
, &[u8], u64, u64, u64) -> Result
<(), Error
>>(
185 ) -> Result
<(), Error
> {
187 let mut cursor
= data
;
189 let entries
= catalog_decode_u64(&mut cursor
)?
;
191 let mut name_buf
= vec
![0u8; 4096];
193 for _
in 0..entries
{
195 let mut buf
= [ 0u8 ];
196 cursor
.read_exact(&mut buf
)?
;
197 let etype
= CatalogEntryType
::try_from(buf
[0])?
;
199 let name_len
= catalog_decode_u64(&mut cursor
)?
as usize;
200 if name_len
>= name_buf
.len() {
201 bail
!("directory entry name too long ({} >= {})", name_len
, name_buf
.len());
203 let name
= &mut name_buf
[0..name_len
];
204 cursor
.read_exact(name
)?
;
207 CatalogEntryType
::Directory
=> {
208 let offset
= catalog_decode_u64(&mut cursor
)?
;
209 callback(etype
, name
, offset
, 0, 0)?
;
211 CatalogEntryType
::File
=> {
212 let size
= catalog_decode_u64(&mut cursor
)?
;
213 let mtime
= catalog_decode_u64(&mut cursor
)?
;
214 callback(etype
, name
, 0, size
, mtime
)?
;
217 callback(etype
, name
, 0, 0, 0)?
;
222 if !cursor
.is_empty() {
223 bail
!("unable to parse whole catalog data block");
230 pub struct CatalogWriter
<W
> {
232 dirstack
: Vec
<DirInfo
>,
236 impl <W
: Write
> CatalogWriter
<W
> {
238 pub fn new(writer
: W
) -> Result
<Self, Error
> {
239 let mut me
= Self { writer, dirstack: vec![ DirInfo::new_rootdir() ], pos: 0 }
;
240 me
.write_all(&PROXMOX_CATALOG_FILE_MAGIC_1_0
)?
;
244 fn write_all(&mut self, data
: &[u8]) -> Result
<(), Error
> {
245 self.writer
.write_all(data
)?
;
246 self.pos
+= u64::try_from(data
.len())?
;
250 pub fn finish(&mut self) -> Result
<(), Error
> {
251 if self.dirstack
.len() != 1 {
252 bail
!("unable to finish catalog at level {}", self.dirstack
.len());
255 let dir
= self.dirstack
.pop().unwrap();
257 let start
= self.pos
;
258 let (_
, data
) = dir
.encode(start
)?
;
259 self.write_all(&data
)?
;
261 self.write_all(&start
.to_le_bytes())?
;
263 self.writer
.flush()?
;
269 impl <W
: Write
> BackupCatalogWriter
for CatalogWriter
<W
> {
271 fn start_directory(&mut self, name
: &CStr
) -> Result
<(), Error
> {
272 let new
= DirInfo
::new(name
.to_owned());
273 self.dirstack
.push(new
);
277 fn end_directory(&mut self) -> Result
<(), Error
> {
278 let (start
, name
) = match self.dirstack
.pop() {
280 let start
= self.pos
;
281 let (name
, data
) = dir
.encode(start
)?
;
282 self.write_all(&data
)?
;
286 bail
!("got unexpected end_directory level 0");
290 let current
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
291 let name
= name
.to_bytes().to_vec();
292 current
.entries
.push(DirEntry { name, attr: DirEntryAttribute::Directory { start }
});
297 fn add_file(&mut self, name
: &CStr
, size
: u64, mtime
: u64) -> Result
<(), Error
> {
298 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
299 let name
= name
.to_bytes().to_vec();
300 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::File { size, mtime }
});
304 fn add_symlink(&mut self, name
: &CStr
) -> Result
<(), Error
> {
305 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
306 let name
= name
.to_bytes().to_vec();
307 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::Symlink }
);
311 fn add_hardlink(&mut self, name
: &CStr
) -> Result
<(), Error
> {
312 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
313 let name
= name
.to_bytes().to_vec();
314 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::Hardlink }
);
318 fn add_block_device(&mut self, name
: &CStr
) -> Result
<(), Error
> {
319 let dir
= self.dirstack
.last_mut().ok_or_else(|| format_err
!("outside root"))?
;
320 let name
= name
.to_bytes().to_vec();
321 dir
.entries
.push(DirEntry { name, attr: DirEntryAttribute::BlockDevice }
);
325 fn add_char_device(&mut self, name
: &CStr
) -> 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::CharDevice }
);
332 fn add_fifo(&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::Fifo }
);
339 fn add_socket(&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::Socket }
);
347 // fixme: move to somehere else?
348 /// Implement Write to tokio mpsc channel Sender
349 pub struct SenderWriter(tokio
::sync
::mpsc
::Sender
<Result
<Vec
<u8>, Error
>>);
352 pub fn new(sender
: tokio
::sync
::mpsc
::Sender
<Result
<Vec
<u8>, Error
>>) -> Self {
357 impl Write
for SenderWriter
{
358 fn write(&mut self, buf
: &[u8]) -> Result
<usize, std
::io
::Error
> {
359 futures
::executor
::block_on(async
move {
360 self.0.send(Ok(buf
.to_vec())).await
361 .map_err(|err
| std
::io
::Error
::new(std
::io
::ErrorKind
::Other
, err
.to_string()))?
;
366 fn flush(&mut self) -> Result
<(), std
::io
::Error
> {
371 pub struct CatalogReader
<R
> {
375 impl <R
: Read
+ Seek
> CatalogReader
<R
> {
377 pub fn new(reader
: R
) -> Self {
381 pub fn dump(&mut self) -> Result
<(), Error
> {
383 self.reader
.seek(SeekFrom
::End(-8))?
;
385 let start
= unsafe { self.reader.read_le_value::<u64>()? }
;
387 self.dump_dir(std
::path
::Path
::new("./"), start
)
390 /// Get the root DirEntry
391 pub fn root(&mut self) -> Result
<DirEntry
, Error
> {
392 // Root dir is special
393 // mixme: verify magic
394 self.reader
.seek(SeekFrom
::End(-8))?
;
395 let start
= unsafe { self.reader.read_le_value::<u64>()? }
;
396 Ok(DirEntry { name: b"".to_vec(), attr: DirEntryAttribute::Directory { start }
})
399 /// Read all directory entries
403 ) -> Result
<Vec
<DirEntry
>, Error
> {
405 let start
= match parent
.attr
{
406 DirEntryAttribute
::Directory { start }
=> start
,
407 _
=> bail
!("parent is not a directory - internal error"),
410 let data
= self.read_raw_dirinfo_block(start
)?
;
412 let mut entry_list
= Vec
::new();
414 DirInfo
::parse(&data
, |etype
, name
, offset
, size
, mtime
| {
415 let entry
= DirEntry
::new(etype
, name
.to_vec(), offset
, size
, mtime
);
416 entry_list
.push(entry
);
423 /// Lockup a DirEntry inside a parent directory
428 ) -> Result
<DirEntry
, Error
> {
430 let start
= match parent
.attr
{
431 DirEntryAttribute
::Directory { start }
=> start
,
432 _
=> bail
!("parent is not a directory - internal error"),
435 let data
= self.read_raw_dirinfo_block(start
)?
;
438 DirInfo
::parse(&data
, |etype
, name
, offset
, size
, mtime
| {
439 if name
!= filename
{
443 let entry
= DirEntry
::new(etype
, name
.to_vec(), offset
, size
, mtime
);
450 None
=> bail
!("no such file"),
451 Some(entry
) => Ok(entry
),
455 /// Read the raw directory info block from current reader position.
456 fn read_raw_dirinfo_block(&mut self, start
: u64) -> Result
<Vec
<u8>, Error
> {
457 self.reader
.seek(SeekFrom
::Start(start
))?
;
458 let size
= catalog_decode_u64(&mut self.reader
)?
;
459 if size
< 1 { bail!("got small directory size {}
", size) };
460 let data = self.reader.read_exact_allocated(size as usize)?;
464 pub fn dump_dir(&mut self, prefix: &std::path::Path, start: u64) -> Result<(), Error> {
466 let data = self.read_raw_dirinfo_block(start)?;
468 DirInfo::parse(&data, |etype, name, offset, size, mtime| {
470 let mut path = std::path::PathBuf::from(prefix);
471 let name: &OsStr = OsStrExt::from_bytes(name);
475 CatalogEntryType::Directory => {
476 println!("{} {:?}
", etype, path);
478 bail!("got wrong directory
offset ({}
> {}
)", offset, start);
480 let pos = start - offset;
481 self.dump_dir(&path, pos)?;
483 CatalogEntryType::File => {
484 let dt = Local.timestamp(mtime as i64, 0);
491 dt.to_rfc3339_opts(chrono::SecondsFormat::Secs, false),
495 println!("{} {:?}
", etype, path);
504 /// Serialize u64 as short, variable length byte sequence
506 /// Stores 7 bits per byte, Bit 8 indicates the end of the sequence (when not set).
507 /// We limit values to a maximum of 2^63.
508 pub fn catalog_encode_u64<W: Write>(writer: &mut W, v: u64) -> Result<(), Error> {
509 let mut enc = Vec::new();
511 if (v & (1<<63)) != 0 { bail!("catalog_encode_u64 failed - value >= 2^63"); }
518 enc.push((128 | (d & 127)) as u8);
521 writer.write_all(&enc)?;
526 /// Deserialize u64 from variable length byte sequence
528 /// We currently read maximal 9 bytes, which give a maximum of 63 bits.
529 pub fn catalog_decode_u64<R: Read>(reader: &mut R) -> Result<u64, Error> {
534 for i in 0..9 { // only allow 9 bytes (63 bits)
536 bail!("decode_u64 failed
- unexpected EOB
");
538 reader.read_exact(&mut buf)?;
541 v |= (t as u64) << (i*7);
544 v |= ((t & 127) as u64) << (i*7);
548 bail!("decode_u64 failed
- missing end marker
");
552 fn test_catalog_u64_encoder() {
554 fn test_encode_decode(value: u64) {
556 let mut data = Vec::new();
557 catalog_encode_u64(&mut data, value).unwrap();
559 //println!("ENCODE {} {:?}
", value, data);
561 let slice = &mut &data[..];
562 let decoded = catalog_decode_u64(slice).unwrap();
564 //println!("DECODE {}
", decoded);
566 assert!(decoded == value);
569 test_encode_decode(126);
570 test_encode_decode((1<<12)-1);
571 test_encode_decode((1<<20)-1);
572 test_encode_decode((1<<50)-1);
573 test_encode_decode((1<<63)-1);