use ::prelude::*; use filetime::{self, FileTime}; use xattr; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::fs::{self, File, Permissions}; use std::os::linux::fs::MetadataExt; use std::os::unix::fs::{PermissionsExt, symlink}; use std::io::{self, Read, Write}; use std::fmt; quick_error!{ #[derive(Debug)] pub enum InodeError { UnsupportedFiletype(path: PathBuf) { description("Unsupported file type") display("Inode error: file {:?} has an unsupported type", path) } ReadMetadata(err: io::Error, path: PathBuf) { cause(err) description("Failed to obtain metadata for file") display("Inode error: failed to obtain metadata for file {:?}\n\tcaused by: {}", path, err) } ReadXattr(err: io::Error, path: PathBuf) { cause(err) description("Failed to obtain xattr for file") display("Inode error: failed to obtain xattr for file {:?}\n\tcaused by: {}", path, err) } ReadLinkTarget(err: io::Error, path: PathBuf) { cause(err) description("Failed to obtain link target for file") display("Inode error: failed to obtain link target for file {:?}\n\tcaused by: {}", path, err) } Create(err: io::Error, path: PathBuf) { cause(err) description("Failed to create entity") display("Inode error: failed to create entity {:?}\n\tcaused by: {}", path, err) } Integrity(reason: &'static str) { description("Integrity error") display("Inode error: inode integrity error: {}", reason) } Decode(err: msgpack::DecodeError) { from() cause(err) description("Failed to decode metadata") display("Inode error: failed to decode metadata\n\tcaused by: {}", err) } Encode(err: msgpack::EncodeError) { from() cause(err) description("Failed to encode metadata") display("Inode error: failed to encode metadata\n\tcaused by: {}", err) } } } #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] pub enum FileType { File, Directory, Symlink } serde_impl!(FileType(u8) { File => 0, Directory => 1, Symlink => 2 }); impl fmt::Display for FileType { fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { FileType::File => write!(format, "file"), FileType::Directory => write!(format, "directory"), FileType::Symlink => write!(format, "symlink") } } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum FileData { Inline(msgpack::Bytes), ChunkedDirect(ChunkList), ChunkedIndirect(ChunkList) } serde_impl!(FileData(u8) { Inline(ByteBuf) => 0, ChunkedDirect(ChunkList) => 1, ChunkedIndirect(ChunkList) => 2 }); #[derive(Debug, Hash, Eq, PartialEq)] pub struct Inode { pub name: String, pub size: u64, pub file_type: FileType, pub mode: u32, pub user: u32, pub group: u32, pub timestamp: i64, pub symlink_target: Option, pub data: Option, pub children: Option>, pub cum_size: u64, pub cum_dirs: usize, pub cum_files: usize, pub xattrs: BTreeMap } impl Default for Inode { fn default() -> Self { Inode { name: "".to_string(), size: 0, file_type: FileType::File, mode: 0o644, user: 1000, group: 1000, timestamp: 0, symlink_target: None, data: None, children: None, cum_size: 0, cum_dirs: 0, cum_files: 0, xattrs: BTreeMap::new() } } } serde_impl!(Inode(u8?) { name: String => 0, size: u64 => 1, file_type: FileType => 2, mode: u32 => 3, user: u32 => 4, group: u32 => 5, timestamp: i64 => 7, symlink_target: Option => 9, data: Option => 10, children: Option> => 11, cum_size: u64 => 12, cum_dirs: usize => 13, cum_files: usize => 14, xattrs: BTreeMap => 15 }); impl Inode { pub fn get_from>(path: P) -> Result { let path = path.as_ref(); let name = path.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| "_".to_string()); let meta = try!(fs::symlink_metadata(path).map_err(|e| InodeError::ReadMetadata(e, path.to_owned()))); let mut inode = Inode::default(); inode.name = name; if meta.is_file() { inode.size = meta.len(); } inode.file_type = if meta.is_file() { FileType::File } else if meta.is_dir() { FileType::Directory } else if meta.file_type().is_symlink() { FileType::Symlink } else { return Err(InodeError::UnsupportedFiletype(path.to_owned())); }; if meta.file_type().is_symlink() { inode.symlink_target = Some(try!(fs::read_link(path).map_err(|e| InodeError::ReadLinkTarget(e, path.to_owned()))).to_string_lossy().to_string()); } inode.mode = meta.st_mode(); inode.user = meta.st_uid(); inode.group = meta.st_gid(); inode.timestamp = meta.st_mtime(); if xattr::SUPPORTED_PLATFORM { if let Ok(attrs) = xattr::list(path) { for name in attrs { let data = try!(xattr::get(path, &name).map_err(|e| InodeError::ReadXattr(e, path.to_owned()))); inode.xattrs.insert(name.to_string_lossy().to_string(), data.into()); } } } Ok(inode) } pub fn create_at>(&self, path: P) -> Result, InodeError> { let full_path = path.as_ref().join(&self.name); let mut file = None; match self.file_type { FileType::File => { file = Some(try!(File::create(&full_path).map_err(|e| InodeError::Create(e, full_path.clone())))); }, FileType::Directory => { try!(fs::create_dir(&full_path).map_err(|e| InodeError::Create(e, full_path.clone()))); }, FileType::Symlink => { if let Some(ref src) = self.symlink_target { try!(symlink(src, &full_path).map_err(|e| InodeError::Create(e, full_path.clone()))); } else { return Err(InodeError::Integrity("Symlink without target")) } } } let time = FileTime::from_seconds_since_1970(self.timestamp as u64, 0); if let Err(err) = filetime::set_file_times(&full_path, time, time) { warn!("Failed to set file time on {:?}: {}", full_path, err); } if !self.xattrs.is_empty() { if xattr::SUPPORTED_PLATFORM { for (name, data) in &self.xattrs { if let Err(err) = xattr::set(&full_path, name, data) { warn!("Failed to set xattr {} on {:?}: {}", name, full_path, err); } } } else { warn!("Not setting xattr on {:?}", full_path); } } if let Err(err) = fs::set_permissions(&full_path, Permissions::from_mode(self.mode)) { warn!("Failed to set permissions {:o} on {:?}: {}", self.mode, full_path, err); } if let Err(err) = chown(&full_path, self.user, self.group) { warn!("Failed to set user {} and group {} on {:?}: {}", self.user, self.group, full_path, err); } Ok(file) } #[inline] pub fn is_same_meta(&self, other: &Inode) -> bool { self.file_type == other.file_type && self.size == other.size && self.mode == other.mode && self.user == other.user && self.group == other.group && self.name == other.name && self.timestamp == other.timestamp && self.symlink_target == other.symlink_target } #[inline] pub fn is_same_meta_quick(&self, other: &Inode) -> bool { self.timestamp == other.timestamp && self.file_type == other.file_type && self.size == other.size } #[inline] pub fn encode(&self) -> Result, InodeError> { Ok(try!(msgpack::encode(&self))) } #[inline] pub fn decode(data: &[u8]) -> Result { Ok(try!(msgpack::decode(&data))) } } impl Repository { pub fn create_inode>(&mut self, path: P, reference: Option<&Inode>) -> Result { let mut inode = try!(Inode::get_from(path.as_ref())); if inode.file_type == FileType::File && inode.size > 0 { if let Some(reference) = reference { if reference.is_same_meta_quick(&inode) { inode.data = reference.data.clone(); return Ok(inode) } } let mut file = try!(File::open(path)); if inode.size < 100 { let mut data = Vec::with_capacity(inode.size as usize); try!(file.read_to_end(&mut data)); inode.data = Some(FileData::Inline(data.into())); } else { let mut chunks = try!(self.put_stream(BundleMode::Data, &mut file)); if chunks.len() < 10 { inode.data = Some(FileData::ChunkedDirect(chunks)); } else { let mut chunk_data = Vec::with_capacity(chunks.encoded_size()); chunks.write_to(&mut chunk_data).unwrap(); chunks = try!(self.put_data(BundleMode::Meta, &chunk_data)); inode.data = Some(FileData::ChunkedIndirect(chunks)); } } } Ok(inode) } #[inline] pub fn put_inode(&mut self, inode: &Inode) -> Result { self.put_data(BundleMode::Meta, &try!(inode.encode())) } #[inline] pub fn get_inode(&mut self, chunks: &[Chunk]) -> Result { Ok(try!(Inode::decode(&try!(self.get_data(chunks))))) } pub fn save_inode_at>(&mut self, inode: &Inode, path: P) -> Result<(), RepositoryError> { if let Some(mut file) = try!(inode.create_at(path.as_ref())) { if let Some(ref contents) = inode.data { match *contents { FileData::Inline(ref data) => { try!(file.write_all(&data)); }, FileData::ChunkedDirect(ref chunks) => { try!(self.get_stream(chunks, &mut file)); }, FileData::ChunkedIndirect(ref chunks) => { let chunk_data = try!(self.get_data(chunks)); let chunks = ChunkList::read_from(&chunk_data); try!(self.get_stream(&chunks, &mut file)); } } } } Ok(()) } }