src/pxar/encoder.rs: detect hardlinks
This commit is contained in:
		| @ -4,6 +4,7 @@ | ||||
|  | ||||
| use failure::*; | ||||
| use endian_trait::Endian; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use super::format_definition::*; | ||||
| use super::binary_search_tree::*; | ||||
| @ -27,8 +28,15 @@ use nix::sys::stat::FileStat; | ||||
| /// maximum memory usage. | ||||
| pub const MAX_DIRECTORY_ENTRIES: usize = 256*1024; | ||||
|  | ||||
| #[derive(Eq, PartialEq, Hash)] | ||||
| struct HardLinkInfo { | ||||
|     st_dev: u64, | ||||
|     st_ino: u64, | ||||
| } | ||||
|  | ||||
| pub struct Encoder<'a, W: Write> { | ||||
|     current_path: PathBuf, // used for error reporting | ||||
|     base_path: PathBuf, | ||||
|     relative_path: PathBuf, | ||||
|     writer: &'a mut W, | ||||
|     writer_pos: usize, | ||||
|     _size: usize, | ||||
| @ -36,10 +44,16 @@ pub struct Encoder<'a, W: Write> { | ||||
|     all_file_systems: bool, | ||||
|     root_st_dev: u64, | ||||
|     verbose: bool, | ||||
|     hardlinks: HashMap<HardLinkInfo, PathBuf>, | ||||
| } | ||||
|  | ||||
| impl <'a, W: Write> Encoder<'a, W> { | ||||
|  | ||||
|     // used for error reporting | ||||
|     fn full_path(&self) ->  PathBuf { | ||||
|         self.base_path.join(&self.relative_path) | ||||
|     } | ||||
|  | ||||
|     pub fn encode( | ||||
|         path: PathBuf, | ||||
|         dir: &mut nix::dir::Dir, | ||||
| @ -73,7 +87,8 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|         } | ||||
|  | ||||
|         let mut me = Self { | ||||
|             current_path: path, | ||||
|             base_path: path, | ||||
|             relative_path: PathBuf::new(), | ||||
|             writer: writer, | ||||
|             writer_pos: 0, | ||||
|             _size: 0, | ||||
| @ -81,9 +96,10 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|             all_file_systems, | ||||
|             root_st_dev: stat.st_dev, | ||||
|             verbose, | ||||
|             hardlinks: HashMap::new(), | ||||
|         }; | ||||
|  | ||||
|         if verbose { println!("{:?}", me.current_path); } | ||||
|         if verbose { println!("{:?}", me.full_path()); } | ||||
|  | ||||
|         me.encode_dir(dir, &stat, magic)?; | ||||
|  | ||||
| @ -143,7 +159,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|  | ||||
|         let mtime = stat.st_mtime * 1_000_000_000 + stat.st_mtime_nsec; | ||||
|         if mtime < 0 { | ||||
|             bail!("got strange mtime ({}) from fstat for {:?}.", mtime, self.current_path); | ||||
|             bail!("got strange mtime ({}) from fstat for {:?}.", mtime, self.full_path()); | ||||
|         } | ||||
|  | ||||
|  | ||||
| @ -168,7 +184,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|             if let nix::Error::Sys(errno) = err { | ||||
|                 if errno_is_unsupported(errno) { return Ok(()) }; | ||||
|             } | ||||
|             bail!("read_attr_fd failed for {:?} - {}", self.current_path, err); | ||||
|             bail!("read_attr_fd failed for {:?} - {}", self.full_path(), err); | ||||
|         } | ||||
|  | ||||
|         let flags = ca_feature_flags_from_chattr(attr as u32); | ||||
| @ -188,7 +204,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|             if let nix::Error::Sys(errno) = err { | ||||
|                 if errno_is_unsupported(errno) { return Ok(()) }; | ||||
|             } | ||||
|             bail!("read_fat_attr_fd failed for {:?} - {}", self.current_path, err); | ||||
|             bail!("read_fat_attr_fd failed for {:?} - {}", self.full_path(), err); | ||||
|         } | ||||
|  | ||||
|         let flags = ca_feature_flags_from_fat_attr(attr); | ||||
| @ -246,7 +262,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|  | ||||
|     fn encode_dir(&mut self, dir: &mut nix::dir::Dir, dir_stat: &FileStat, magic: i64)  -> Result<(), Error> { | ||||
|  | ||||
|         //println!("encode_dir: {:?} start {}", self.current_path, self.writer_pos); | ||||
|         //println!("encode_dir: {:?} start {}", self.full_path(), self.writer_pos); | ||||
|  | ||||
|         let mut name_list = vec![]; | ||||
|  | ||||
| @ -275,12 +291,12 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|                 dir_count += 1; | ||||
|                 if dir_count > MAX_DIRECTORY_ENTRIES { | ||||
|                     bail!("too many directory items in {:?} (> {})", | ||||
|                           self.current_path, MAX_DIRECTORY_ENTRIES); | ||||
|                           self.full_path(), MAX_DIRECTORY_ENTRIES); | ||||
|                 } | ||||
|  | ||||
|                 let entry = match entry { | ||||
|                     Ok(entry) => entry, | ||||
|                     Err(err) => bail!("readir {:?} failed - {}", self.current_path, err), | ||||
|                     Err(err) => bail!("readir {:?} failed - {}", self.full_path(), err), | ||||
|                 }; | ||||
|                 let filename = entry.file_name().to_owned(); | ||||
|  | ||||
| @ -292,7 +308,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|                 name_list.push(filename); | ||||
|             } | ||||
|         } else { | ||||
|             eprintln!("skip mount point: {:?}", self.current_path); | ||||
|             eprintln!("skip mount point: {:?}", self.full_path()); | ||||
|         } | ||||
|  | ||||
|         name_list.sort_unstable_by(|a, b| a.cmp(&b)); | ||||
| @ -300,17 +316,17 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|         let mut goodbye_items = vec![]; | ||||
|  | ||||
|         for filename in &name_list { | ||||
|             self.current_path.push(std::ffi::OsStr::from_bytes(filename.as_bytes())); | ||||
|             self.relative_path.push(std::ffi::OsStr::from_bytes(filename.as_bytes())); | ||||
|  | ||||
|             if self.verbose { println!("{:?}", self.current_path); } | ||||
|             if self.verbose { println!("{:?}", self.full_path()); } | ||||
|  | ||||
|             let stat = match nix::sys::stat::fstatat(rawfd, filename.as_ref(), nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW) { | ||||
|                 Ok(stat) => stat, | ||||
|                 Err(nix::Error::Sys(Errno::ENOENT)) => { | ||||
|                     self.report_vanished_file(&self.current_path)?; | ||||
|                     self.report_vanished_file(&self.full_path())?; | ||||
|                     continue; | ||||
|                 } | ||||
|                 Err(err) => bail!("fstat {:?} failed - {}", self.current_path, err), | ||||
|                 Err(err) => bail!("fstat {:?} failed - {}", self.full_path(), err), | ||||
|             }; | ||||
|  | ||||
|             let start_pos = self.writer_pos; | ||||
| @ -322,10 +338,10 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|                 let mut dir = match nix::dir::Dir::openat(rawfd, filename.as_ref(), OFlag::O_DIRECTORY|OFlag::O_NOFOLLOW, Mode::empty()) { | ||||
|                     Ok(dir) => dir, | ||||
|                     Err(nix::Error::Sys(Errno::ENOENT)) => { | ||||
|                         self.report_vanished_file(&self.current_path)?; | ||||
|                         self.report_vanished_file(&self.full_path())?; | ||||
|                         continue; // fixme!! | ||||
|                     }, | ||||
|                     Err(err) => bail!("open dir {:?} failed - {}", self.current_path, err), | ||||
|                     Err(err) => bail!("open dir {:?} failed - {}", self.full_path(), err), | ||||
|                 }; | ||||
|  | ||||
|                 let child_magic = if dir_stat.st_dev != stat.st_dev { | ||||
| @ -338,13 +354,24 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|                 self.encode_dir(&mut dir, &stat, child_magic)?; | ||||
|  | ||||
|             } else if ifmt == libc::S_IFREG { | ||||
|  | ||||
|                 if stat.st_nlink > 1 { | ||||
|                     let link_info = HardLinkInfo { st_dev: stat.st_dev, st_ino: stat.st_ino }; | ||||
|                     if let Some(target) = self.hardlinks.get(&link_info) { | ||||
|                         // fixme: store hardlink info somwhow? | ||||
|                         eprintln!("FOUND HARDLINK {:?}", target); | ||||
|                     } else { | ||||
|                         self.hardlinks.insert(link_info, self.relative_path.clone()); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 let filefd = match nix::fcntl::openat(rawfd, filename.as_ref(), OFlag::O_NOFOLLOW, Mode::empty()) { | ||||
|                     Ok(filefd) => filefd, | ||||
|                     Err(nix::Error::Sys(Errno::ENOENT)) => { | ||||
|                         self.report_vanished_file(&self.current_path)?; | ||||
|                         self.report_vanished_file(&self.full_path())?; | ||||
|                         continue; | ||||
|                     }, | ||||
|                     Err(err) => bail!("open file {:?} failed - {}", self.current_path, err), | ||||
|                     Err(err) => bail!("open file {:?} failed - {}", self.full_path(), err), | ||||
|                 }; | ||||
|  | ||||
|                 let child_magic = if dir_stat.st_dev != stat.st_dev { | ||||
| @ -372,10 +399,10 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|                         self.encode_symlink(&buffer[..((len+1) as usize)], &stat)? | ||||
|                     } | ||||
|                     Err(nix::Error::Sys(Errno::ENOENT)) => { | ||||
|                         self.report_vanished_file(&self.current_path)?; | ||||
|                         self.report_vanished_file(&self.full_path())?; | ||||
|                         continue; | ||||
|                     } | ||||
|                     Err(err) => bail!("readlink {:?} failed - {}", self.current_path, err), | ||||
|                     Err(err) => bail!("readlink {:?} failed - {}", self.full_path(), err), | ||||
|                 } | ||||
|             } else if (ifmt == libc::S_IFBLK) || (ifmt == libc::S_IFCHR) { | ||||
|                 self.write_filename(&filename)?; | ||||
| @ -384,7 +411,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|                 self.write_filename(&filename)?; | ||||
|                 self.encode_special(&stat)?; | ||||
|             } else { | ||||
|                 bail!("unsupported file type (mode {:o} {:?})", stat.st_mode, self.current_path); | ||||
|                 bail!("unsupported file type (mode {:o} {:?})", stat.st_mode, self.full_path()); | ||||
|             } | ||||
|  | ||||
|             let end_pos = self.writer_pos; | ||||
| @ -395,10 +422,10 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|                 hash: compute_goodbye_hash(filename.to_bytes()), | ||||
|             }); | ||||
|  | ||||
|             self.current_path.pop(); | ||||
|             self.relative_path.pop(); | ||||
|         } | ||||
|  | ||||
|         //println!("encode_dir: {:?} end {}", self.current_path, self.writer_pos); | ||||
|         //println!("encode_dir: {:?} end {}", self.full_path(), self.writer_pos); | ||||
|  | ||||
|         // fixup goodby item offsets | ||||
|         let goodbye_start = self.writer_pos as u64; | ||||
| @ -410,13 +437,13 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|  | ||||
|         self.write_goodbye_table(goodbye_offset, &mut goodbye_items)?; | ||||
|  | ||||
|         //println!("encode_dir: {:?} end1 {}", self.current_path, self.writer_pos); | ||||
|         //println!("encode_dir: {:?} end1 {}", self.full_path(), self.writer_pos); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn encode_file(&mut self, filefd: RawFd, stat: &FileStat, magic: i64)  -> Result<(), Error> { | ||||
|  | ||||
|         //println!("encode_file: {:?}", self.current_path); | ||||
|         //println!("encode_file: {:?}", self.full_path()); | ||||
|  | ||||
|         let mut entry = self.create_entry(&stat)?; | ||||
|  | ||||
| @ -433,7 +460,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|         } | ||||
|  | ||||
|         if !include_payload { | ||||
|             eprintln!("skip content: {:?}", self.current_path); | ||||
|             eprintln!("skip content: {:?}", self.full_path()); | ||||
|             self.write_header(CA_FORMAT_PAYLOAD, 0)?; | ||||
|             return Ok(()); | ||||
|         } | ||||
| @ -447,12 +474,12 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|             let n = match nix::unistd::read(filefd, &mut self.file_copy_buffer) { | ||||
|                 Ok(n) => n, | ||||
|                 Err(nix::Error::Sys(Errno::EINTR)) => continue /* try again */, | ||||
|                 Err(err) =>  bail!("read {:?} failed - {}", self.current_path, err), | ||||
|                 Err(err) =>  bail!("read {:?} failed - {}", self.full_path(), err), | ||||
|             }; | ||||
|             if n == 0 { // EOF | ||||
|                 if pos != size { | ||||
|                     // Note:: casync format cannot handle that | ||||
|                     bail!("detected shrinked file {:?} ({} < {})", self.current_path, pos, size); | ||||
|                     bail!("detected shrinked file {:?} ({} < {})", self.full_path(), pos, size); | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
| @ -482,7 +509,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|         let major = unsafe { libc::major(stat.st_rdev) } as u64; | ||||
|         let minor = unsafe { libc::minor(stat.st_rdev) } as u64; | ||||
|  | ||||
|         //println!("encode_device: {:?} {} {} {}", self.current_path, stat.st_rdev, major, minor); | ||||
|         //println!("encode_device: {:?} {} {} {}", self.full_path(), stat.st_rdev, major, minor); | ||||
|  | ||||
|         self.write_header(CA_FORMAT_DEVICE, std::mem::size_of::<CaFormatDevice>() as u64)?; | ||||
|         self.write_item(CaFormatDevice { major, minor })?; | ||||
| @ -502,7 +529,7 @@ impl <'a, W: Write> Encoder<'a, W> { | ||||
|  | ||||
|     fn encode_symlink(&mut self, target: &[u8], stat: &FileStat)  -> Result<(), Error> { | ||||
|  | ||||
|         //println!("encode_symlink: {:?} -> {:?}", self.current_path, target); | ||||
|         //println!("encode_symlink: {:?} -> {:?}", self.full_path(), target); | ||||
|  | ||||
|         let entry = self.create_entry(&stat)?; | ||||
|         self.write_entry(entry)?; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user