diff --git a/src/main.rs b/src/main.rs index d61ecbd..0e12145 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ mod repository; mod algotest; use chunker::ChunkerType; -use repository::{Repository, Config, Mode}; +use repository::{Repository, Config, Mode, Inode}; use util::{ChecksumType, Compression, HashMethod, to_file_size}; use std::fs::File; @@ -37,6 +37,8 @@ Usage: zvault check [--full] zvault algotest zvault test + zvault stat + zvault put Options: --full Whether to verify the repository by loading all bundles @@ -53,8 +55,10 @@ struct Args { cmd_info: bool, cmd_algotest: bool, cmd_test: bool, + cmd_stat: bool, cmd_check: bool, cmd_bundles: bool, + cmd_put: bool, arg_repo: Option, arg_path: Option, flag_full: bool, @@ -91,6 +95,11 @@ fn main() { return } + if args.cmd_stat { + println!("{:?}", Inode::get_from(&args.arg_path.unwrap()).unwrap()); + return + } + let mut repo = Repository::open(&args.arg_repo.unwrap()).unwrap(); if args.cmd_check { @@ -127,6 +136,12 @@ fn main() { return } + if args.cmd_put { + let chunks = repo.put_inode(&args.arg_path.unwrap()).unwrap(); + println!("done. {} chunks, total size: {}", chunks.len(), to_file_size(chunks.iter().map(|&(_,s)| s).sum::() as u64)); + return + } + if args.cmd_test { print!("Integrity check before..."); repo.check(true).unwrap(); diff --git a/src/repository/basic_io.rs b/src/repository/basic_io.rs index 06ca321..606c73a 100644 --- a/src/repository/basic_io.rs +++ b/src/repository/basic_io.rs @@ -8,6 +8,9 @@ use ::util::Hash; use ::chunker::{IChunker, ChunkerStatus}; +pub type Chunk = (Hash, usize); + + impl Repository { pub fn get_chunk(&mut self, hash: Hash) -> Result>, &'static str> { // Find bundle and chunk id in index @@ -81,12 +84,12 @@ impl Repository { } #[inline] - pub fn put_data(&mut self, mode: Mode, data: &[u8]) -> Result, &'static str> { + pub fn put_data(&mut self, mode: Mode, data: &[u8]) -> Result, &'static str> { let mut input = Cursor::new(data); self.put_stream(mode, &mut input) } - pub fn put_stream(&mut self, mode: Mode, data: &mut R) -> Result, &'static str> { + pub fn put_stream(&mut self, mode: Mode, data: &mut R) -> Result, &'static str> { let avg_size = self.config.chunker.avg_size(); let mut chunks = Vec::new(); let mut chunk = Vec::with_capacity(avg_size * 2); @@ -106,14 +109,14 @@ impl Repository { } #[inline] - pub fn get_data(&mut self, chunks: &[(Hash, usize)]) -> Result, &'static str> { + pub fn get_data(&mut self, chunks: &[Chunk]) -> Result, &'static str> { let mut data = Vec::with_capacity(chunks.iter().map(|&(_, size)| size).sum()); try!(self.get_stream(chunks, &mut data)); Ok(data) } #[inline] - pub fn get_stream(&mut self, chunks: &[(Hash, usize)], w: &mut W) -> Result<(), &'static str> { + pub fn get_stream(&mut self, chunks: &[Chunk], w: &mut W) -> Result<(), &'static str> { for &(ref hash, len) in chunks { let data = try!(try!(self.get_chunk(*hash).map_err(|_| "Failed to load chunk")).ok_or("Chunk missing")); debug_assert_eq!(data.len(), len); diff --git a/src/repository/metadata.rs b/src/repository/metadata.rs new file mode 100644 index 0000000..d7fe254 --- /dev/null +++ b/src/repository/metadata.rs @@ -0,0 +1,172 @@ +use serde::bytes::ByteBuf; +use serde::{Serialize, Deserialize}; +use rmp_serde; + +use std::collections::HashMap; +use std::path::Path; +use std::fs::{self, Metadata, File}; +use std::os::linux::fs::MetadataExt; +use std::io::{Cursor, Read}; + +use ::util::Hash; +use super::{Repository, Mode, Chunk}; + + +#[derive(Debug, Eq, PartialEq)] +pub enum FileType { + File, + Directory, + Symlink +} +serde_impl!(FileType(u8) { + File => 0, + Directory => 1, + Symlink => 2 +}); + + +#[derive(Debug)] +pub enum FileContents { + Inline(ByteBuf), + Chunked(Vec) +} +serde_impl!(FileContents(u8) { + Inline(ByteBuf) => 0, + Chunked(Vec) => 1 +}); + + +#[derive(Debug)] +pub struct Inode { + pub name: String, + pub size: u64, + pub file_type: FileType, + pub mode: u32, + pub user: u32, + pub group: u32, + pub access_time: i64, + pub modify_time: i64, + pub create_time: i64, + pub symlink_target: Option, + pub contents: Option, + pub children: Option>> +} +impl Default for Inode { + fn default() -> Self { + Inode { + name: "".to_string(), + size: 0, + file_type: FileType::File, + mode: 0o644, + user: 1000, + group: 1000, + access_time: 0, + modify_time: 0, + create_time: 0, + symlink_target: None, + contents: None, + children: None + } + } +} +serde_impl!(Inode(u8) { + name: String => 0, + size: u64 => 1, + file_type: FileType => 2, + mode: u32 => 3, + user: u32 => 4, + group: u32 => 5, + access_time: i64 => 6, + modify_time: i64 => 7, + create_time: i64 => 8, + symlink_target: Option => 9, + contents: Option => 10, + children: HashMap> => 11 +}); + +impl Inode { + fn get_extended_attrs_from(&mut self, meta: &Metadata) -> Result<(), &'static str> { + self.mode = meta.st_mode(); + self.user = meta.st_uid(); + self.group = meta.st_gid(); + self.access_time = meta.st_atime(); + self.modify_time = meta.st_mtime(); + self.create_time = meta.st_ctime(); + Ok(()) + } + + pub fn get_from>(path: P) -> Result { + let name = try!(path.as_ref().file_name().ok_or("Not a file")).to_string_lossy().to_string(); + let meta = try!(fs::symlink_metadata(path.as_ref()).map_err(|_| "Failed to get metadata")); + let mut inode = Inode::default(); + inode.name = name; + 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("Unsupported file type"); + }; + if meta.file_type().is_symlink() { + inode.symlink_target = Some(try!(fs::read_link(path).map_err(|_| "Failed to read symlink")).to_string_lossy().to_string()); + } + try!(inode.get_extended_attrs_from(&meta)); + Ok(inode) + } + + #[allow(dead_code)] + pub fn create_at>(&self, path: P) -> Result<(), &'static str> { + let full_path = path.as_ref().join(&self.name); + match self.file_type { + FileType::File => { + try!(File::create(&full_path).map_err(|_| "Failed to create file")); + }, + FileType::Directory => { + try!(fs::create_dir(&full_path).map_err(|_| "Failed to create directory")); + }, + FileType::Symlink => { + if let Some(ref src) = self.symlink_target { + try!(fs::soft_link(src, &full_path).map_err(|_| "Failed to create symlink")); + } else { + return Err("Symlink without destination") + } + } + } + //FIXME: set times and permissions + Ok(()) + } +} + + +impl Repository { + pub fn put_inode>(&mut self, path: P) -> Result, &'static str> { + let mut inode = try!(Inode::get_from(path.as_ref())); + if inode.file_type == FileType::File && inode.size > 0 { + let mut file = try!(File::open(path).map_err(|_| "Failed to open file")); + if inode.size < 100 { + let mut data = Vec::with_capacity(inode.size as usize); + try!(file.read_to_end(&mut data).map_err(|_| "Failed to read file contents")); + inode.contents = Some(FileContents::Inline(data.into())); + } else { + let chunks = try!(self.put_stream(Mode::Content, &mut file)); + inode.contents = Some(FileContents::Chunked(chunks)); + } + } + let mut inode_data = Vec::new(); + { + let mut writer = rmp_serde::Serializer::new(&mut inode_data); + inode.serialize(&mut writer).map_err(|_| "Failed to write inode data"); + } + self.put_data(Mode::Meta, &inode_data) + } + + #[inline] + pub fn get_inode(&mut self, chunks: &[Chunk]) -> Result { + let data = Cursor::new(try!(self.get_data(chunks))); + let mut reader = rmp_serde::Deserializer::new(data); + Inode::deserialize(&mut reader).map_err(|_| "Failed to read inode data") + } +} diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 997783c..58b6592 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -3,6 +3,7 @@ mod bundle_map; mod integrity; mod basic_io; mod info; +mod metadata; use std::mem; use std::cmp::max; @@ -14,6 +15,8 @@ use super::bundle::{BundleDb, BundleWriter}; use super::chunker::Chunker; pub use self::config::Config; +pub use self::metadata::Inode; +pub use self::basic_io::Chunk; use self::bundle_map::BundleMap; diff --git a/src/util/hash.rs b/src/util/hash.rs index 64fb41e..7fa8789 100644 --- a/src/util/hash.rs +++ b/src/util/hash.rs @@ -12,7 +12,7 @@ use std::u64; #[repr(packed)] -#[derive(Clone, Copy, PartialEq, Debug, Hash, Eq)] +#[derive(Clone, Copy, PartialEq, Hash, Eq)] pub struct Hash { pub high: u64, pub low: u64 @@ -37,6 +37,14 @@ impl fmt::Display for Hash { } } +impl fmt::Debug for Hash { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(fmt, "{:16x}{:16x}", self.high, self.low) + } +} + + impl Serialize for Hash { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { let hash = Hash{high: u64::to_le(self.high), low: u64::to_le(self.low)};