]> git.proxmox.com Git - proxmox-backup.git/blame - src/catar/decoder.rs
src/catar/decoder.rs: simplify public restore API
[proxmox-backup.git] / src / catar / decoder.rs
CommitLineData
e75eac73 1//! *catar* format decoder.
3d8c24ec
DM
2//!
3//! This module contain the code to decode *catar* archive files.
0866748d 4
3d8c24ec 5use failure::*;
b6ebfb8d 6use endian_trait::Endian;
0866748d 7
3d8c24ec 8use super::format_definition::*;
3d8c24ec 9
e9c9409a 10use std::io::{Read, Write};
b6ebfb8d 11use std::path::{Path, PathBuf};
3d8c24ec 12
9b1bb5a2
DM
13use std::os::unix::io::AsRawFd;
14use std::os::unix::io::RawFd;
15use std::os::unix::io::FromRawFd;
e9c9409a 16use std::os::unix::ffi::{OsStringExt};
3d8c24ec
DM
17use std::ffi::{OsStr, OsString};
18
9b1bb5a2
DM
19use nix::fcntl::OFlag;
20use nix::sys::stat::Mode;
21use nix::errno::Errno;
22use nix::NixPath;
23
e9c9409a
DM
24// This one need Read, but works without Seek
25pub struct CaTarDecoder<'a, R: Read> {
3d8c24ec 26 reader: &'a mut R,
e9c9409a 27 skip_buffer: Vec<u8>,
3d8c24ec
DM
28}
29
30const HEADER_SIZE: u64 = std::mem::size_of::<CaFormatHeader>() as u64;
31
e9c9409a 32impl <'a, R: Read> CaTarDecoder<'a, R> {
3d8c24ec 33
e9c9409a
DM
34 pub fn new(reader: &'a mut R) -> Self {
35 let mut skip_buffer = vec![0u8; 64*1024];
36 Self { reader, skip_buffer }
3d8c24ec
DM
37 }
38
b6ebfb8d 39 fn read_item<T: Endian>(&mut self) -> Result<T, Error> {
3d8c24ec 40
b6ebfb8d 41 let mut result: T = unsafe { std::mem::uninitialized() };
3d8c24ec 42
b6ebfb8d
DM
43 let buffer = unsafe { std::slice::from_raw_parts_mut(
44 &mut result as *mut T as *mut u8,
45 std::mem::size_of::<T>()
46 )};
47
48 self.reader.read_exact(buffer)?;
49
50 Ok(result.from_le())
51 }
52
53 fn read_symlink(&mut self, size: u64) -> Result<PathBuf, Error> {
54 if size < (HEADER_SIZE + 2) {
55 bail!("dectected short symlink target.");
3d8c24ec 56 }
b6ebfb8d 57 let target_len = size - HEADER_SIZE;
3d8c24ec 58
b6ebfb8d
DM
59 if target_len > (libc::PATH_MAX as u64) {
60 bail!("symlink target too long ({}).", target_len);
61 }
3d8c24ec 62
b6ebfb8d
DM
63 let mut buffer = vec![0u8; target_len as usize];
64 self.reader.read_exact(&mut buffer)?;
3d8c24ec 65
b6ebfb8d
DM
66 let last_byte = buffer.pop().unwrap();
67 if last_byte != 0u8 {
68 bail!("symlink target not nul terminated.");
3d8c24ec 69 }
b6ebfb8d
DM
70
71 Ok(PathBuf::from(std::ffi::OsString::from_vec(buffer)))
72 }
73
74 fn read_filename(&mut self, size: u64) -> Result<OsString, Error> {
75 if size < (HEADER_SIZE + 2) {
76 bail!("dectected short filename");
77 }
78 let name_len = size - HEADER_SIZE;
3d8c24ec
DM
79
80 if name_len > ((libc::FILENAME_MAX as u64) + 1) {
b6ebfb8d 81 bail!("filename too long ({}).", name_len);
3d8c24ec
DM
82 }
83
84 let mut buffer = vec![0u8; name_len as usize];
85 self.reader.read_exact(&mut buffer)?;
86
3d8c24ec
DM
87 let last_byte = buffer.pop().unwrap();
88 if last_byte != 0u8 {
b6ebfb8d
DM
89 bail!("filename entry not nul terminated.");
90 }
3d8c24ec 91
d5c34d98
DM
92 if (buffer.len() == 1 && buffer[0] == b'.') || (buffer.len() == 2 && buffer[0] == b'.' && buffer[1] == b'.') {
93 bail!("found invalid filename with slashes.");
94 }
95
a7e37131 96 if buffer.iter().find(|b| (**b == b'/')).is_some() {
25f60394
DM
97 bail!("found invalid filename with slashes.");
98 }
9b1bb5a2 99
d5c34d98
DM
100 let name = std::ffi::OsString::from_vec(buffer);
101 if name.is_empty() {
102 bail!("found empty filename.");
103 }
104
105 Ok(name)
b6ebfb8d
DM
106 }
107
ddbdf80d 108 fn restore_attributes(&mut self, _entry: &CaFormatEntry) -> Result<CaFormatHeader, Error> {
23f68e53
DM
109
110 loop {
111 let head: CaFormatHeader = self.read_item()?;
112 match head.htype {
ddbdf80d 113 // fimxe: impl ...
23f68e53
DM
114 _ => return Ok(head),
115 }
116 }
117 }
118
119 fn restore_mode(&mut self, entry: &CaFormatEntry, fd: RawFd) -> Result<(), Error> {
120
121 let mode = Mode::from_bits_truncate((entry.mode as u32) & 0o7777);
122
123 nix::sys::stat::fchmod(fd, mode)?;
124
125 Ok(())
126 }
127
128 fn restore_mode_at(&mut self, entry: &CaFormatEntry, dirfd: RawFd, filename: &OsStr) -> Result<(), Error> {
129
130 let mode = Mode::from_bits_truncate((entry.mode as u32) & 0o7777);
131
a7e37131
DM
132 // NOTE: we want :FchmodatFlags::NoFollowSymlink, but fchmodat does not support that
133 // on linux (see man fchmodat). Fortunately, we can simply avoid calling this on symlinks.
134 nix::sys::stat::fchmodat(Some(dirfd), filename, mode, nix::sys::stat::FchmodatFlags::FollowSymlink)?;
23f68e53
DM
135
136 Ok(())
137 }
138
139 fn restore_ugid(&mut self, entry: &CaFormatEntry, fd: RawFd) -> Result<(), Error> {
140
141 let uid = entry.uid as u32;
142 let gid = entry.gid as u32;
143
144 let res = unsafe { libc::fchown(fd, uid, gid) };
145 Errno::result(res)?;
146
147 Ok(())
148 }
149
150 fn restore_ugid_at(&mut self, entry: &CaFormatEntry, dirfd: RawFd, filename: &OsStr) -> Result<(), Error> {
151
152 let uid = entry.uid as u32;
153 let gid = entry.gid as u32;
154
155 let res = filename.with_nix_path(|cstr| unsafe {
156 libc::fchownat(dirfd, cstr.as_ptr(), uid, gid, libc::AT_SYMLINK_NOFOLLOW)
157 })?;
158 Errno::result(res)?;
159
160 Ok(())
161 }
162
163 fn restore_mtime(&mut self, entry: &CaFormatEntry, fd: RawFd) -> Result<(), Error> {
164
165 let times = nsec_to_update_timespec(entry.mtime);
166
167 let res = unsafe { libc::futimens(fd, &times[0]) };
168 Errno::result(res)?;
169
170 Ok(())
171 }
172
173 fn restore_mtime_at(&mut self, entry: &CaFormatEntry, dirfd: RawFd, filename: &OsStr) -> Result<(), Error> {
174
175 let times = nsec_to_update_timespec(entry.mtime);
176
177 let res = filename.with_nix_path(|cstr| unsafe {
178 libc::utimensat(dirfd, cstr.as_ptr(), &times[0], libc::AT_SYMLINK_NOFOLLOW)
179 })?;
180 Errno::result(res)?;
181
182 Ok(())
183 }
184
a7e37131
DM
185 fn restore_device_at(&mut self, entry: &CaFormatEntry, dirfd: RawFd, filename: &OsStr, device: &CaFormatDevice) -> Result<(), Error> {
186
187 let rdev = nix::sys::stat::makedev(device.major, device.minor);
20e2043a 188 let mode = ((entry.mode as u32) & libc::S_IFMT) | 0o0600;
a7e37131 189 let res = filename.with_nix_path(|cstr| unsafe {
20e2043a 190 libc::mknodat(dirfd, cstr.as_ptr(), mode, rdev)
a7e37131
DM
191 })?;
192 Errno::result(res)?;
193
194 Ok(())
195 }
196
490683ec
DM
197 fn restore_socket_at(&mut self, dirfd: RawFd, filename: &OsStr) -> Result<(), Error> {
198
199 let mode = libc::S_IFSOCK | 0o0600;
200 let res = filename.with_nix_path(|cstr| unsafe {
201 libc::mknodat(dirfd, cstr.as_ptr(), mode, 0)
202 })?;
203 Errno::result(res)?;
204
205 Ok(())
206 }
207
208 fn restore_fifo_at(&mut self, dirfd: RawFd, filename: &OsStr) -> Result<(), Error> {
209
210 let mode = libc::S_IFIFO | 0o0600;
211 let res = filename.with_nix_path(|cstr| unsafe {
212 libc::mkfifoat(dirfd, cstr.as_ptr(), mode)
213 })?;
214 Errno::result(res)?;
215
216 Ok(())
217 }
218
d5c34d98
DM
219 pub fn restore<F>(
220 &mut self,
221 path: &Path, // used for error reporting
222 callback: &F,
223 ) -> Result<(), Error>
224 where F: Fn(&Path) -> Result<(), Error>
225 {
226
227 let _ = std::fs::create_dir(path);
228
229 let dir = match nix::dir::Dir::open(path, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty()) {
230 Ok(dir) => dir,
231 Err(err) => bail!("unable to open target directory {:?} - {}", path, err),
232 };
233
234 self.restore_sequential(&mut path.to_owned(), &OsString::new(), &dir, callback)
235 }
236
237 fn restore_sequential<F>(
b6ebfb8d 238 &mut self,
e9c9409a 239 path: &mut PathBuf, // used for error reporting
9b1bb5a2
DM
240 filename: &OsStr, // repeats path last component
241 parent: &nix::dir::Dir,
b6ebfb8d 242 callback: &F,
fc2bf37e
DM
243 ) -> Result<(), Error>
244 where F: Fn(&Path) -> Result<(), Error>
245 {
b6ebfb8d 246
9b1bb5a2
DM
247 let parent_fd = parent.as_raw_fd();
248
b6ebfb8d
DM
249 // read ENTRY first
250 let head: CaFormatHeader = self.read_item()?;
251 check_ca_header::<CaFormatEntry>(&head, CA_FORMAT_ENTRY)?;
252 let entry: CaFormatEntry = self.read_item()?;
253
c6a0d3fc
DM
254 let mode = entry.mode as u32; //fixme: upper 32bits?
255
25f60394
DM
256 let ifmt = mode & libc::S_IFMT;
257
258 if ifmt == libc::S_IFDIR {
d5c34d98
DM
259 let dir;
260 if filename.is_empty() {
261 dir = nix::dir::Dir::openat(parent_fd, ".", OFlag::O_DIRECTORY, Mode::empty())?;
262 } else {
263 dir = match dir_mkdirat(parent_fd, filename, true) {
264 Ok(dir) => dir,
265 Err(err) => bail!("unable to open directory {:?} - {}", path, err),
266 };
267 }
9b1bb5a2 268
23f68e53 269 let mut head = self.restore_attributes(&entry)?;
9b1bb5a2 270
23f68e53
DM
271 while head.htype == CA_FORMAT_FILENAME {
272 let name = self.read_filename(head.size)?;
273 path.push(&name);
274 println!("NAME: {:?}", path);
656b23e1 275
d5c34d98 276 self.restore_sequential(path, &name, &dir, callback)?;
23f68e53
DM
277 path.pop();
278
279 head = self.read_item()?;
280 }
281
282 if head.htype != CA_FORMAT_GOODBYE {
283 bail!("got unknown header type inside directory entry {:016x}", head.htype);
9b1bb5a2 284 }
23f68e53
DM
285
286 println!("Skip Goodbye");
287 if head.size < HEADER_SIZE { bail!("detected short goodbye table"); }
e9c9409a
DM
288
289 // self.reader.seek(SeekFrom::Current((head.size - HEADER_SIZE) as i64))?;
290 let mut done = 0;
291 let skip = (head.size - HEADER_SIZE) as usize;
292 while done < skip {
293 let todo = skip - done;
294 let n = if todo > self.skip_buffer.len() { self.skip_buffer.len() } else { todo };
295 let data = &mut self.skip_buffer[..n];
296 self.reader.read_exact(data)?;
297 done += n;
298 }
299
23f68e53
DM
300
301 self.restore_mode(&entry, dir.as_raw_fd())?;
302 self.restore_mtime(&entry, dir.as_raw_fd())?;
303 self.restore_ugid(&entry, dir.as_raw_fd())?;
304
305 return Ok(());
9b1bb5a2 306 }
676bf71a 307
d5c34d98
DM
308 if filename.is_empty() {
309 bail!("got empty file name at {:?}", path)
310 }
311
25f60394 312 if ifmt == libc::S_IFLNK {
9b1bb5a2
DM
313 // fixme: create symlink
314 //fixme: restore permission, acls, xattr, ...
23f68e53 315
b6ebfb8d
DM
316 let head: CaFormatHeader = self.read_item()?;
317 match head.htype {
318 CA_FORMAT_SYMLINK => {
319 let target = self.read_symlink(head.size)?;
320 println!("TARGET: {:?}", target);
9b1bb5a2
DM
321 if let Err(err) = symlinkat(&target, parent_fd, filename) {
322 bail!("create symlink {:?} failed - {}", path, err);
c6a0d3fc 323 }
c6a0d3fc 324 }
9b1bb5a2
DM
325 _ => {
326 bail!("got unknown header type inside symlink entry {:016x}", head.htype);
327 }
328 }
23f68e53
DM
329
330 // self.restore_mode_at(&entry, parent_fd, filename)?; //not supported on symlinks
331 self.restore_ugid_at(&entry, parent_fd, filename)?;
490683ec
DM
332 self.restore_mtime_at(&entry, parent_fd, filename)?;
333
334 return Ok(());
335 }
336
337 if ifmt == libc::S_IFSOCK {
338
339 self.restore_socket_at(parent_fd, filename)?;
340
341 self.restore_mode_at(&entry, parent_fd, filename)?;
342 self.restore_ugid_at(&entry, parent_fd, filename)?;
343 self.restore_mtime_at(&entry, parent_fd, filename)?;
344
345 return Ok(());
346 }
347
348 if ifmt == libc::S_IFIFO {
349
350 self.restore_fifo_at(parent_fd, filename)?;
351
352 self.restore_mode_at(&entry, parent_fd, filename)?;
353 self.restore_ugid_at(&entry, parent_fd, filename)?;
354 self.restore_mtime_at(&entry, parent_fd, filename)?;
23f68e53 355
9b1bb5a2
DM
356 return Ok(());
357 }
358
a7e37131
DM
359 if (ifmt == libc::S_IFBLK) || (ifmt == libc::S_IFCHR) {
360
361 let head: CaFormatHeader = self.read_item()?;
362 match head.htype {
363 CA_FORMAT_DEVICE => {
364 let device: CaFormatDevice = self.read_item()?;
365 self.restore_device_at(&entry, parent_fd, filename, &device)?;
366 }
367 _ => {
368 bail!("got unknown header type inside device entry {:016x}", head.htype);
369 }
370 }
371
372 self.restore_mode_at(&entry, parent_fd, filename)?;
373 self.restore_ugid_at(&entry, parent_fd, filename)?;
490683ec 374 self.restore_mtime_at(&entry, parent_fd, filename)?;
a7e37131
DM
375
376 return Ok(());
377 }
378
25f60394 379 if ifmt == libc::S_IFREG {
9b1bb5a2
DM
380
381 let mut read_buffer: [u8; 64*1024] = unsafe { std::mem::uninitialized() };
382
383 let flags = OFlag::O_CREAT|OFlag::O_WRONLY|OFlag::O_EXCL;
384 let open_mode = Mode::from_bits_truncate(0o0600 | mode);
385
386 let mut file = match file_openat(parent_fd, filename, flags, open_mode) {
387 Ok(file) => file,
388 Err(err) => bail!("open file {:?} failed - {}", path, err),
389 };
390
ddbdf80d 391 let head = self.restore_attributes(&entry)?;
9b1bb5a2 392
23f68e53
DM
393 if head.htype != CA_FORMAT_PAYLOAD {
394 bail!("got unknown header type for file entry {:016x}", head.htype);
395 }
396
397 if head.size < HEADER_SIZE {
398 bail!("detected short payload");
399 }
400 let need = (head.size - HEADER_SIZE) as usize;
401 //self.reader.seek(SeekFrom::Current(need as i64))?;
402
403 let mut done = 0;
ddbdf80d 404 while done < need {
23f68e53
DM
405 let todo = need - done;
406 let n = if todo > read_buffer.len() { read_buffer.len() } else { todo };
407 let data = &mut read_buffer[..n];
408 self.reader.read_exact(data)?;
409 file.write_all(data)?;
410 done += n;
b6ebfb8d 411 }
9b1bb5a2 412
23f68e53
DM
413 self.restore_mode(&entry, file.as_raw_fd())?;
414 self.restore_mtime(&entry, file.as_raw_fd())?;
415 self.restore_ugid(&entry, file.as_raw_fd())?;
416
9b1bb5a2 417 return Ok(());
3d8c24ec
DM
418 }
419
b6ebfb8d
DM
420 Ok(())
421 }
3d8c24ec 422}
9b1bb5a2
DM
423
424fn file_openat(parent: RawFd, filename: &OsStr, flags: OFlag, mode: Mode) -> Result<std::fs::File, Error> {
425
ddbdf80d 426 let fd = filename.with_nix_path(|cstr| {
9b1bb5a2
DM
427 nix::fcntl::openat(parent, cstr.as_ref(), flags, mode)
428 })??;
429
430 let file = unsafe { std::fs::File::from_raw_fd(fd) };
431
432 Ok(file)
433}
434
656b23e1 435fn dir_mkdirat(parent: RawFd, filename: &OsStr, create_new: bool) -> Result<nix::dir::Dir, nix::Error> {
9b1bb5a2
DM
436
437 // call mkdirat first
438 let res = filename.with_nix_path(|cstr| unsafe {
439 libc::mkdirat(parent, cstr.as_ptr(), libc::S_IRWXU)
440 })?;
656b23e1
DM
441
442 match Errno::result(res) {
443 Ok(_) => {},
444 Err(err) => {
445 if err == nix::Error::Sys(nix::errno::Errno::EEXIST) {
446 if create_new { return Err(err); }
447 } else {
448 return Err(err);
449 }
450 }
451 }
9b1bb5a2
DM
452
453 let dir = nix::dir::Dir::openat(parent, filename, OFlag::O_DIRECTORY, Mode::empty())?;
454
455 Ok(dir)
456}
457
458fn symlinkat(target: &Path, parent: RawFd, linkname: &OsStr) -> Result<(), Error> {
459
460 target.with_nix_path(|target| {
461 linkname.with_nix_path(|linkname| {
462 let res = unsafe { libc::symlinkat(target.as_ptr(), parent, linkname.as_ptr()) };
463 Errno::result(res)?;
464 Ok(())
465 })?
466 })?
467}
23f68e53
DM
468
469fn nsec_to_update_timespec(mtime_nsec: u64) -> [libc::timespec; 2] {
470
471 // restore mtime
472 const UTIME_OMIT: i64 = ((1 << 30) - 2);
473 const NANOS_PER_SEC: i64 = 1_000_000_000;
474
475 let sec = (mtime_nsec as i64) / NANOS_PER_SEC;
476 let nsec = (mtime_nsec as i64) % NANOS_PER_SEC;
477
478 let times: [libc::timespec; 2] = [
479 libc::timespec { tv_sec: 0, tv_nsec: UTIME_OMIT },
480 libc::timespec { tv_sec: sec, tv_nsec: nsec },
481 ];
482
483 times
484}