]> git.proxmox.com Git - proxmox-backup.git/blob - src/pxar/decoder.rs
22ac01dde13dddb6f4652b34b01ab3140910c47c
[proxmox-backup.git] / src / pxar / decoder.rs
1 //! *pxar* format decoder for seekable files
2 //!
3 //! This module contain the code to decode *pxar* archive files.
4
5 use failure::*;
6
7 use super::format_definition::*;
8 use super::sequential_decoder::*;
9
10 use std::io::{Read, Seek, SeekFrom};
11 use std::path::{Path, PathBuf};
12
13 use std::ffi::OsString;
14
15
16 pub struct CaDirectoryEntry {
17 start: u64,
18 end: u64,
19 pub filename: OsString,
20 pub entry: CaFormatEntry,
21 }
22
23 // This one needs Read+Seek
24 pub struct Decoder<'a, R: Read + Seek> {
25 inner: SequentialDecoder<'a, R>,
26 root_start: u64,
27 root_end: u64,
28 }
29
30 const HEADER_SIZE: u64 = std::mem::size_of::<CaFormatHeader>() as u64;
31
32 impl <'a, R: Read + Seek> Decoder<'a, R> {
33
34 pub fn new(reader: &'a mut R) -> Result<Self, Error> {
35
36 let root_end = reader.seek(SeekFrom::End(0))?;
37 let no_xattrs = false;
38 let no_fcaps = false;
39
40 Ok(Self {
41 inner: SequentialDecoder::new(reader, no_xattrs, no_fcaps),
42 root_start: 0,
43 root_end: root_end,
44 })
45 }
46
47 pub fn root(&self) -> CaDirectoryEntry {
48 CaDirectoryEntry {
49 start: self.root_start,
50 end: self.root_end,
51 filename: OsString::new(), // Empty
52 entry: CaFormatEntry {
53 mode: 0,
54 flags: 0,
55 uid: 0,
56 gid: 0,
57 mtime: 0,
58 }
59 }
60 }
61
62 fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error> {
63 let pos = self.inner.get_reader_mut().seek(pos)?;
64 Ok(pos)
65 }
66
67 pub fn restore<F>(
68 &mut self,
69 dir: &CaDirectoryEntry,
70 path: &Path,
71 callback: F,
72 ) -> Result<(), Error>
73 where F: Fn(&Path) -> Result<(), Error>
74 {
75 let start = dir.start;
76
77 self.seek(SeekFrom::Start(start))?;
78
79 self.inner.restore(path, &callback)?;
80
81 Ok(())
82 }
83
84 fn read_directory_entry(&mut self, start: u64, end: u64) -> Result<CaDirectoryEntry, Error> {
85
86 self.seek(SeekFrom::Start(start))?;
87
88 let head: CaFormatHeader = self.inner.read_item()?;
89
90 if head.htype != CA_FORMAT_FILENAME {
91 bail!("wrong filename header type for object [{}..{}]", start, end);
92 }
93
94 let entry_start = start + head.size;
95
96 let filename = self.inner.read_filename(head.size)?;
97
98 let head: CaFormatHeader = self.inner.read_item()?;
99 check_ca_header::<CaFormatEntry>(&head, CA_FORMAT_ENTRY)?;
100 let entry: CaFormatEntry = self.inner.read_item()?;
101
102 Ok(CaDirectoryEntry {
103 start: entry_start,
104 end: end,
105 filename: filename,
106 entry,
107 })
108 }
109
110 pub fn list_dir(&mut self, dir: &CaDirectoryEntry) -> Result<Vec<CaDirectoryEntry>, Error> {
111
112 const GOODBYE_ITEM_SIZE: u64 = std::mem::size_of::<CaFormatGoodbyeItem>() as u64;
113
114 let start = dir.start;
115 let end = dir.end;
116
117 //println!("list_dir1: {} {}", start, end);
118
119 if (end - start) < (HEADER_SIZE + GOODBYE_ITEM_SIZE) {
120 bail!("detected short object [{}..{}]", start, end);
121 }
122
123 self.seek(SeekFrom::Start(end - GOODBYE_ITEM_SIZE))?;
124
125 let item: CaFormatGoodbyeItem = self.inner.read_item()?;
126
127 if item.hash != CA_FORMAT_GOODBYE_TAIL_MARKER {
128 bail!("missing goodbye tail marker for object [{}..{}]", start, end);
129 }
130
131 let goodbye_table_size = item.size;
132 if goodbye_table_size < (HEADER_SIZE + GOODBYE_ITEM_SIZE) {
133 bail!("short goodbye table size for object [{}..{}]", start, end);
134
135 }
136 let goodbye_inner_size = goodbye_table_size - HEADER_SIZE - GOODBYE_ITEM_SIZE;
137 if (goodbye_inner_size % GOODBYE_ITEM_SIZE) != 0 {
138 bail!("wrong goodbye inner table size for entry [{}..{}]", start, end);
139 }
140
141 let goodbye_start = end - goodbye_table_size;
142
143 if item.offset != (goodbye_start - start) {
144 println!("DEBUG: {} {}", u64::from_le(item.offset), goodbye_start - start);
145 bail!("wrong offset in goodbye tail marker for entry [{}..{}]", start, end);
146 }
147
148 self.seek(SeekFrom::Start(goodbye_start))?;
149 let head: CaFormatHeader = self.inner.read_item()?;
150
151 if head.htype != CA_FORMAT_GOODBYE {
152 bail!("wrong goodbye table header type for entry [{}..{}]", start, end);
153 }
154
155 if head.size != goodbye_table_size {
156 bail!("wrong goodbye table size for entry [{}..{}]", start, end);
157 }
158
159 let mut range_list = Vec::new();
160
161 for i in 0..goodbye_inner_size/GOODBYE_ITEM_SIZE {
162 let item: CaFormatGoodbyeItem = self.inner.read_item()?;
163
164 if item.offset > (goodbye_start - start) {
165 bail!("goodbye entry {} offset out of range [{}..{}] {} {} {}",
166 i, start, end, item.offset, goodbye_start, start);
167 }
168 let item_start = goodbye_start - item.offset;
169 let item_end = item_start + item.size;
170 if item_end > goodbye_start {
171 bail!("goodbye entry {} end out of range [{}..{}]",
172 i, start, end);
173 }
174
175 range_list.push((item_start, item_end));
176 }
177
178 let mut result = vec![];
179
180 for (item_start, item_end) in range_list {
181 let entry = self.read_directory_entry(item_start, item_end)?;
182 //println!("ENTRY: {} {} {:?}", item_start, item_end, entry.filename);
183 result.push(entry);
184 }
185
186 Ok(result)
187 }
188
189 pub fn print_filenames<W: std::io::Write>(
190 &mut self,
191 output: &mut W,
192 prefix: &mut PathBuf,
193 dir: &CaDirectoryEntry,
194 ) -> Result<(), Error> {
195
196 let mut list = self.list_dir(dir)?;
197
198 list.sort_unstable_by(|a, b| a.filename.cmp(&b.filename));
199
200 for item in &list {
201
202 prefix.push(item.filename.clone());
203
204 let mode = item.entry.mode as u32;
205
206 let ifmt = mode & libc::S_IFMT;
207
208 writeln!(output, "{:?}", prefix)?;
209
210 if ifmt == libc::S_IFDIR {
211 self.print_filenames(output, prefix, item)?;
212 } else if ifmt == libc::S_IFREG {
213 } else if ifmt == libc::S_IFLNK {
214 } else if ifmt == libc::S_IFBLK {
215 } else if ifmt == libc::S_IFCHR {
216 } else {
217 bail!("unknown item mode/type for {:?}", prefix);
218 }
219
220 prefix.pop();
221 }
222
223 Ok(())
224 }
225 }