zvault/src/repository/metadata.rs

333 lines
12 KiB
Rust
Raw Normal View History

2017-03-21 10:28:11 +00:00
use ::prelude::*;
2017-03-22 18:58:56 +00:00
use filetime::{self, FileTime};
2017-04-04 11:59:57 +00:00
use xattr;
2017-03-22 18:58:56 +00:00
2017-03-22 20:42:43 +00:00
use std::collections::BTreeMap;
2017-03-22 08:19:16 +00:00
use std::path::{Path, PathBuf};
2017-03-22 20:42:43 +00:00
use std::fs::{self, File, Permissions};
2017-03-15 11:32:44 +00:00
use std::os::linux::fs::MetadataExt;
2017-03-15 20:53:05 +00:00
use std::os::unix::fs::{PermissionsExt, symlink};
2017-03-22 08:19:16 +00:00
use std::io::{self, Read, Write};
2017-03-23 08:31:23 +00:00
use std::fmt;
2017-03-22 08:19:16 +00:00
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)
}
2017-04-04 11:59:57 +00:00
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)
}
2017-03-22 08:19:16 +00:00
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)
}
SetPermissions(err: io::Error, path: PathBuf, mode: u32) {
cause(err)
description("Failed to set permissions")
display("Inode error: failed to set permissions to {:3o} on {:?}\n\tcaused by: {}", mode, path, err)
}
2017-03-22 18:58:56 +00:00
SetTimes(err: io::Error, path: PathBuf) {
cause(err)
description("Failed to set file times")
display("Inode error: failed to set file times on {:?}\n\tcaused by: {}", path, err)
}
SetOwnership(err: io::Error, path: PathBuf) {
cause(err)
description("Failed to set file ownership")
display("Inode error: failed to set file ownership on {:?}\n\tcaused by: {}", path, err)
}
2017-04-04 11:59:57 +00:00
SetXattr(err: io::Error, path: PathBuf) {
cause(err)
description("Failed to set xattr")
display("Inode error: failed to set xattr on {:?}\n\tcaused by: {}", path, err)
}
2017-03-22 08:19:16 +00:00
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)
}
}
}
2017-03-15 11:32:44 +00:00
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
2017-03-15 11:32:44 +00:00
pub enum FileType {
File,
Directory,
Symlink
}
serde_impl!(FileType(u8) {
File => 0,
Directory => 1,
Symlink => 2
});
2017-03-23 08:31:23 +00:00
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")
}
}
}
2017-03-15 11:32:44 +00:00
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
2017-04-03 05:35:00 +00:00
pub enum FileData {
2017-03-16 08:42:30 +00:00
Inline(msgpack::Bytes),
2017-03-18 14:41:59 +00:00
ChunkedDirect(ChunkList),
ChunkedIndirect(ChunkList)
2017-03-15 11:32:44 +00:00
}
2017-04-03 05:35:00 +00:00
serde_impl!(FileData(u8) {
2017-03-15 11:32:44 +00:00
Inline(ByteBuf) => 0,
2017-03-23 06:03:48 +00:00
ChunkedDirect(ChunkList) => 1,
ChunkedIndirect(ChunkList) => 2
2017-03-15 11:32:44 +00:00
});
#[derive(Debug, Hash, Eq, PartialEq)]
2017-03-15 11:32:44 +00:00
pub struct Inode {
pub name: String,
pub size: u64,
pub file_type: FileType,
pub mode: u32,
pub user: u32,
pub group: u32,
2017-04-02 16:55:53 +00:00
pub timestamp: i64,
2017-03-15 11:32:44 +00:00
pub symlink_target: Option<String>,
2017-04-03 05:35:00 +00:00
pub data: Option<FileData>,
2017-04-02 18:37:34 +00:00
pub children: Option<BTreeMap<String, ChunkList>>,
pub cum_size: u64,
pub cum_dirs: usize,
2017-04-04 11:59:57 +00:00
pub cum_files: usize,
pub xattrs: BTreeMap<String, msgpack::Bytes>
2017-03-15 11:32:44 +00:00
}
impl Default for Inode {
fn default() -> Self {
Inode {
name: "".to_string(),
size: 0,
file_type: FileType::File,
mode: 0o644,
user: 1000,
group: 1000,
2017-04-02 16:55:53 +00:00
timestamp: 0,
2017-03-15 11:32:44 +00:00
symlink_target: None,
2017-04-03 05:35:00 +00:00
data: None,
2017-04-02 18:37:34 +00:00
children: None,
cum_size: 0,
cum_dirs: 0,
2017-04-04 11:59:57 +00:00
cum_files: 0,
xattrs: BTreeMap::new()
2017-03-15 11:32:44 +00:00
}
}
}
2017-04-02 16:55:53 +00:00
serde_impl!(Inode(u8?) {
2017-03-15 11:32:44 +00:00
name: String => 0,
size: u64 => 1,
file_type: FileType => 2,
mode: u32 => 3,
user: u32 => 4,
group: u32 => 5,
2017-04-02 16:55:53 +00:00
timestamp: i64 => 7,
2017-03-15 11:32:44 +00:00
symlink_target: Option<String> => 9,
2017-04-03 05:35:00 +00:00
data: Option<FileData> => 10,
2017-04-03 12:05:16 +00:00
children: Option<BTreeMap<String, ChunkList>> => 11,
2017-04-02 18:37:34 +00:00
cum_size: u64 => 12,
cum_dirs: usize => 13,
2017-04-04 11:59:57 +00:00
cum_files: usize => 14,
xattrs: BTreeMap<String, msgpack::Bytes> => 15
2017-03-15 11:32:44 +00:00
});
2017-03-20 21:24:53 +00:00
2017-03-15 11:32:44 +00:00
impl Inode {
2017-03-22 08:19:16 +00:00
pub fn get_from<P: AsRef<Path>>(path: P) -> Result<Self, InodeError> {
let path = path.as_ref();
2017-03-26 18:33:32 +00:00
let name = path.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| "_".to_string());
2017-03-22 08:19:16 +00:00
let meta = try!(fs::symlink_metadata(path).map_err(|e| InodeError::ReadMetadata(e, path.to_owned())));
2017-03-15 11:32:44 +00:00
let mut inode = Inode::default();
inode.name = name;
2017-03-22 20:42:43 +00:00
if meta.is_file() {
inode.size = meta.len();
}
2017-03-15 11:32:44 +00:00
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 {
2017-03-22 08:19:16 +00:00
return Err(InodeError::UnsupportedFiletype(path.to_owned()));
2017-03-15 11:32:44 +00:00
};
if meta.file_type().is_symlink() {
2017-03-22 08:19:16 +00:00
inode.symlink_target = Some(try!(fs::read_link(path).map_err(|e| InodeError::ReadLinkTarget(e, path.to_owned()))).to_string_lossy().to_string());
2017-03-15 11:32:44 +00:00
}
2017-03-22 20:42:43 +00:00
inode.mode = meta.st_mode();
inode.user = meta.st_uid();
inode.group = meta.st_gid();
2017-04-02 16:55:53 +00:00
inode.timestamp = meta.st_mtime();
2017-04-04 11:59:57 +00:00
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());
}
}
}
2017-03-15 11:32:44 +00:00
Ok(inode)
}
2017-03-22 08:19:16 +00:00
pub fn create_at<P: AsRef<Path>>(&self, path: P) -> Result<Option<File>, InodeError> {
2017-03-15 11:32:44 +00:00
let full_path = path.as_ref().join(&self.name);
2017-03-15 20:53:05 +00:00
let mut file = None;
2017-03-15 11:32:44 +00:00
match self.file_type {
FileType::File => {
2017-03-22 08:19:16 +00:00
file = Some(try!(File::create(&full_path).map_err(|e| InodeError::Create(e, full_path.clone()))));
2017-03-15 11:32:44 +00:00
},
FileType::Directory => {
2017-03-22 08:19:16 +00:00
try!(fs::create_dir(&full_path).map_err(|e| InodeError::Create(e, full_path.clone())));
2017-03-15 11:32:44 +00:00
},
FileType::Symlink => {
if let Some(ref src) = self.symlink_target {
2017-03-22 08:19:16 +00:00
try!(symlink(src, &full_path).map_err(|e| InodeError::Create(e, full_path.clone())));
2017-03-15 11:32:44 +00:00
} else {
2017-03-22 08:19:16 +00:00
return Err(InodeError::Integrity("Symlink without target"))
2017-03-15 11:32:44 +00:00
}
}
}
2017-04-04 11:59:57 +00:00
let time = FileTime::from_seconds_since_1970(self.timestamp as u64, 0);
try!(filetime::set_file_times(&full_path, time, time).map_err(|e| InodeError::SetTimes(e, full_path.clone())));
if !self.xattrs.is_empty() {
if xattr::SUPPORTED_PLATFORM {
for (name, data) in &self.xattrs {
try!(xattr::set(&full_path, name, data).map_err(|e| InodeError::SetXattr(e, full_path.clone())))
}
} else {
warn!("Not setting xattr on {:?}", full_path);
}
}
2017-03-22 18:58:56 +00:00
try!(fs::set_permissions(
&full_path,
Permissions::from_mode(self.mode)
).map_err(|e| InodeError::SetPermissions(e, full_path.clone(), self.mode)));
try!(chown(&full_path, self.user, self.group).map_err(|e| InodeError::SetOwnership(e, full_path.clone())));
2017-03-15 20:53:05 +00:00
Ok(file)
2017-03-15 11:32:44 +00:00
}
2017-03-20 21:24:53 +00:00
2017-04-10 18:35:28 +00:00
#[inline]
2017-03-29 21:24:26 +00:00
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
2017-04-02 16:55:53 +00:00
&& self.timestamp == other.timestamp && self.symlink_target == other.symlink_target
2017-03-29 21:24:26 +00:00
}
2017-04-10 18:35:28 +00:00
#[inline]
2017-03-29 21:24:26 +00:00
pub fn is_same_meta_quick(&self, other: &Inode) -> bool {
2017-04-02 16:55:53 +00:00
self.timestamp == other.timestamp
2017-03-20 21:24:53 +00:00
&& self.file_type == other.file_type
2017-03-26 18:33:32 +00:00
&& self.size == other.size
2017-03-20 21:24:53 +00:00
}
2017-03-22 08:19:16 +00:00
#[inline]
pub fn encode(&self) -> Result<Vec<u8>, InodeError> {
Ok(try!(msgpack::encode(&self)))
}
#[inline]
pub fn decode(data: &[u8]) -> Result<Self, InodeError> {
Ok(try!(msgpack::decode(&data)))
}
2017-03-15 11:32:44 +00:00
}
impl Repository {
2017-03-20 21:24:53 +00:00
pub fn create_inode<P: AsRef<Path>>(&mut self, path: P, reference: Option<&Inode>) -> Result<Inode, RepositoryError> {
2017-03-15 11:32:44 +00:00
let mut inode = try!(Inode::get_from(path.as_ref()));
if inode.file_type == FileType::File && inode.size > 0 {
2017-03-20 21:24:53 +00:00
if let Some(reference) = reference {
2017-03-29 21:24:26 +00:00
if reference.is_same_meta_quick(&inode) {
2017-04-03 05:35:00 +00:00
inode.data = reference.data.clone();
2017-03-20 21:24:53 +00:00
return Ok(inode)
}
}
2017-03-16 08:42:30 +00:00
let mut file = try!(File::open(path));
2017-03-15 11:32:44 +00:00
if inode.size < 100 {
let mut data = Vec::with_capacity(inode.size as usize);
2017-03-16 08:42:30 +00:00
try!(file.read_to_end(&mut data));
2017-04-03 05:35:00 +00:00
inode.data = Some(FileData::Inline(data.into()));
2017-03-15 11:32:44 +00:00
} else {
2017-04-03 12:05:16 +00:00
let mut chunks = try!(self.put_stream(BundleMode::Data, &mut file));
2017-03-16 08:42:30 +00:00
if chunks.len() < 10 {
2017-04-03 05:35:00 +00:00
inode.data = Some(FileData::ChunkedDirect(chunks));
2017-03-16 08:42:30 +00:00
} else {
2017-03-18 14:41:59 +00:00
let mut chunk_data = Vec::with_capacity(chunks.encoded_size());
chunks.write_to(&mut chunk_data).unwrap();
2017-03-20 21:24:53 +00:00
chunks = try!(self.put_data(BundleMode::Meta, &chunk_data));
2017-04-03 05:35:00 +00:00
inode.data = Some(FileData::ChunkedIndirect(chunks));
2017-03-16 08:42:30 +00:00
}
2017-03-15 11:32:44 +00:00
}
}
2017-03-16 11:33:10 +00:00
Ok(inode)
}
2017-03-17 06:15:19 +00:00
#[inline]
2017-03-18 14:41:59 +00:00
pub fn put_inode(&mut self, inode: &Inode) -> Result<ChunkList, RepositoryError> {
2017-03-22 08:19:16 +00:00
self.put_data(BundleMode::Meta, &try!(inode.encode()))
2017-03-15 11:32:44 +00:00
}
#[inline]
2017-03-16 08:42:30 +00:00
pub fn get_inode(&mut self, chunks: &[Chunk]) -> Result<Inode, RepositoryError> {
2017-03-22 08:19:16 +00:00
Ok(try!(Inode::decode(&try!(self.get_data(chunks)))))
2017-03-15 11:32:44 +00:00
}
2017-03-15 20:53:05 +00:00
2017-03-16 08:42:30 +00:00
pub fn save_inode_at<P: AsRef<Path>>(&mut self, inode: &Inode, path: P) -> Result<(), RepositoryError> {
2017-03-15 20:53:05 +00:00
if let Some(mut file) = try!(inode.create_at(path.as_ref())) {
2017-04-03 05:35:00 +00:00
if let Some(ref contents) = inode.data {
2017-03-15 20:53:05 +00:00
match *contents {
2017-04-03 05:35:00 +00:00
FileData::Inline(ref data) => {
2017-03-16 08:42:30 +00:00
try!(file.write_all(&data));
2017-03-15 20:53:05 +00:00
},
2017-04-03 05:35:00 +00:00
FileData::ChunkedDirect(ref chunks) => {
2017-03-15 20:53:05 +00:00
try!(self.get_stream(chunks, &mut file));
2017-03-16 08:42:30 +00:00
},
2017-04-03 05:35:00 +00:00
FileData::ChunkedIndirect(ref chunks) => {
2017-03-16 08:42:30 +00:00
let chunk_data = try!(self.get_data(chunks));
2017-03-18 14:41:59 +00:00
let chunks = ChunkList::read_from(&chunk_data);
2017-03-16 08:42:30 +00:00
try!(self.get_stream(&chunks, &mut file));
2017-03-15 20:53:05 +00:00
}
}
}
}
Ok(())
}
2017-03-15 11:32:44 +00:00
}