]>
Commit | Line | Data |
---|---|---|
e75eac73 | 1 | //! *catar* format decoder. |
3d8c24ec DM |
2 | //! |
3 | //! This module contain the code to decode *catar* archive files. | |
0866748d | 4 | |
3d8c24ec | 5 | use failure::*; |
b6ebfb8d | 6 | use endian_trait::Endian; |
0866748d | 7 | |
3d8c24ec | 8 | use super::format_definition::*; |
3d8c24ec | 9 | |
e9c9409a | 10 | use std::io::{Read, Write}; |
b6ebfb8d | 11 | use std::path::{Path, PathBuf}; |
3d8c24ec | 12 | |
9b1bb5a2 DM |
13 | use std::os::unix::io::AsRawFd; |
14 | use std::os::unix::io::RawFd; | |
15 | use std::os::unix::io::FromRawFd; | |
e9c9409a | 16 | use std::os::unix::ffi::{OsStringExt}; |
3d8c24ec DM |
17 | use std::ffi::{OsStr, OsString}; |
18 | ||
9b1bb5a2 DM |
19 | use nix::fcntl::OFlag; |
20 | use nix::sys::stat::Mode; | |
21 | use nix::errno::Errno; | |
22 | use nix::NixPath; | |
23 | ||
e9c9409a DM |
24 | // This one need Read, but works without Seek |
25 | pub struct CaTarDecoder<'a, R: Read> { | |
3d8c24ec | 26 | reader: &'a mut R, |
e9c9409a | 27 | skip_buffer: Vec<u8>, |
3d8c24ec DM |
28 | } |
29 | ||
30 | const HEADER_SIZE: u64 = std::mem::size_of::<CaFormatHeader>() as u64; | |
31 | ||
e9c9409a | 32 | impl <'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, ×[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(), ×[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 | |
424 | fn 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 | 435 | fn 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 | ||
458 | fn 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 | |
469 | fn 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 | } |