3 use std
::ffi
::{CStr, CString}
;
4 use std
::io
::{Read, BufRead, BufReader, Write, Seek}
;
5 use std
::convert
::TryFrom
;
7 use chrono
::offset
::{TimeZone, Local}
;
9 use proxmox
::tools
::io
::ReadExt
;
11 use crate::pxar
::catalog
::{BackupCatalogWriter, CatalogEntryType}
;
13 use super::{DataBlobWriter, DataBlobReader, CryptConfig}
;
15 pub struct CatalogBlobWriter
<W
: Write
+ Seek
> {
16 writer
: DataBlobWriter
<W
>,
20 impl <W
: Write
+ Seek
> CatalogBlobWriter
<W
> {
21 pub fn new_compressed(writer
: W
) -> Result
<Self, Error
> {
22 let writer
= DataBlobWriter
::new_compressed(writer
)?
;
23 Ok(Self { writer, level: 0 }
)
25 pub fn new_signed_compressed(writer
: W
, config
: Arc
<CryptConfig
>) -> Result
<Self, Error
> {
26 let writer
= DataBlobWriter
::new_signed_compressed(writer
, config
)?
;
27 Ok(Self { writer, level: 0 }
)
29 pub fn new_encrypted_compressed(writer
: W
, config
: Arc
<CryptConfig
>) -> Result
<Self, Error
> {
30 let writer
= DataBlobWriter
::new_encrypted_compressed(writer
, config
)?
;
31 Ok(Self { writer, level: 0 }
)
33 pub fn finish(self) -> Result
<W
, Error
> {
38 impl <W
: Write
+ Seek
> BackupCatalogWriter
for CatalogBlobWriter
<W
> {
40 fn start_directory(&mut self, name
: &CStr
) -> Result
<(), Error
> {
41 self.writer
.write(&[CatalogEntryType
::Directory
as u8])?
;
42 self.writer
.write(name
.to_bytes_with_nul())?
;
43 self.writer
.write(b
"{")?
;
48 fn end_directory(&mut self) -> Result
<(), Error
> {
50 bail
!("got unexpected end_directory level 0");
52 self.writer
.write(b
"}")?
;
57 fn add_file(&mut self, name
: &CStr
, size
: u64, mtime
: u64) -> Result
<(), Error
> {
58 self.writer
.write(&[CatalogEntryType
::File
as u8])?
;
59 self.writer
.write(&size
.to_le_bytes())?
;
60 self.writer
.write(&mtime
.to_le_bytes())?
;
61 self.writer
.write(name
.to_bytes_with_nul())?
;
65 fn add_symlink(&mut self, name
: &CStr
) -> Result
<(), Error
> {
66 self.writer
.write(&[CatalogEntryType
::Symlink
as u8])?
;
67 self.writer
.write(name
.to_bytes_with_nul())?
;
71 fn add_hardlink(&mut self, name
: &CStr
) -> Result
<(), Error
> {
72 self.writer
.write(&[CatalogEntryType
::Hardlink
as u8])?
;
73 self.writer
.write(name
.to_bytes_with_nul())?
;
77 fn add_block_device(&mut self, name
: &CStr
) -> Result
<(), Error
> {
78 self.writer
.write(&[CatalogEntryType
::BlockDevice
as u8])?
;
79 self.writer
.write(name
.to_bytes_with_nul())?
;
83 fn add_char_device(&mut self, name
: &CStr
) -> Result
<(), Error
> {
84 self.writer
.write(&[CatalogEntryType
::CharDevice
as u8])?
;
85 self.writer
.write(name
.to_bytes_with_nul())?
;
89 fn add_fifo(&mut self, name
: &CStr
) -> Result
<(), Error
> {
90 self.writer
.write(&[CatalogEntryType
::Fifo
as u8])?
;
91 self.writer
.write(name
.to_bytes_with_nul())?
;
95 fn add_socket(&mut self, name
: &CStr
) -> Result
<(), Error
> {
96 self.writer
.write(&[CatalogEntryType
::Socket
as u8])?
;
97 self.writer
.write(name
.to_bytes_with_nul())?
;
103 pub struct CatalogBlobReader
<R
: Read
+ BufRead
> {
104 reader
: BufReader
<DataBlobReader
<R
>>,
105 dir_stack
: Vec
<CString
>,
108 impl <R
: Read
+ BufRead
> CatalogBlobReader
<R
> {
110 pub fn new(reader
: R
, crypt_config
: Option
<Arc
<CryptConfig
>>) -> Result
<Self, Error
> {
111 let dir_stack
= Vec
::new();
113 let reader
= BufReader
::new(DataBlobReader
::new(reader
, crypt_config
)?
);
115 Ok(Self { reader, dir_stack }
)
118 fn read_filename(&mut self) -> Result
<std
::ffi
::CString
, Error
> {
119 let mut filename
= Vec
::new();
120 self.reader
.read_until(0u8, &mut filename
)?
;
121 if filename
.len() > 0 && filename
[filename
.len()-1] == 0u8 {
124 if filename
.len() == 0 {
125 bail
!("got zero length filename");
127 if filename
.iter().find(|b
| **b
== b'
/'
).is_some() {
128 bail
!("found invalid filename with slashes.");
130 Ok(unsafe { CString::from_vec_unchecked(filename) }
)
133 fn next_byte(&mut self) -> Result
<u8, std
::io
::Error
> {
134 let mut buf
= [0u8; 1];
135 self.reader
.read_exact(&mut buf
)?
;
139 fn expect_next(&mut self, expect
: u8) -> Result
<(), Error
> {
140 let next
= self.next_byte()?
;
142 bail
!("got unexpected byte ({} != {})", next
, expect
);
147 fn print_entry(&self, etype
: CatalogEntryType
, filename
: &CStr
, size
: u64, mtime
: u64) -> Result
<(), Error
> {
148 let mut out
= Vec
::new();
150 write
!(out
, "{} ", char::from(etype
as u8))?
;
152 for name
in &self.dir_stack
{
153 out
.extend(name
.to_bytes());
157 out
.extend(filename
.to_bytes());
159 let dt
= Local
.timestamp(mtime
as i64, 0);
161 if etype
== CatalogEntryType
::File
{
162 write
!(out
, " {} {}", size
, dt
.to_rfc3339_opts(chrono
::SecondsFormat
::Secs
, false))?
;
166 std
::io
::stdout().write_all(&out
)?
;
171 fn parse_entries(&mut self) -> Result
<(), Error
> {
174 let etype
= match self.next_byte() {
177 if err
.kind() == std
::io
::ErrorKind
::UnexpectedEof
{
178 if self.dir_stack
.len() == 0 {
182 return Err(err
.into());
186 if self.dir_stack
.pop().is_none() {
187 bail
!("got unexpected '}'");
192 let etype
= CatalogEntryType
::try_from(etype
)?
;
194 CatalogEntryType
::Directory
=> {
195 let filename
= self.read_filename()?
;
196 self.print_entry(etype
.into(), &filename
, 0, 0)?
;
197 self.dir_stack
.push(filename
);
198 self.expect_next(b'
{'
)?
;
199 self.parse_entries()?
;
201 CatalogEntryType
::File
=> {
202 let size
= unsafe { self.reader.read_le_value::<u64>()? }
;
203 let mtime
= unsafe { self.reader.read_le_value::<u64>()? }
;
204 let filename
= self.read_filename()?
;
205 self.print_entry(etype
.into(), &filename
, size
, mtime
)?
;
207 CatalogEntryType
::Symlink
|
208 CatalogEntryType
::Hardlink
|
209 CatalogEntryType
::Fifo
|
210 CatalogEntryType
::Socket
|
211 CatalogEntryType
::BlockDevice
|
212 CatalogEntryType
::CharDevice
=> {
213 let filename
= self.read_filename()?
;
214 self.print_entry(etype
.into(), &filename
, 0, 0)?
;
221 pub fn dump(&mut self) -> Result
<(), Error
> {
222 self.parse_entries()?
;