mirror of https://github.com/dswd/zvault
Mounting backups
This commit is contained in:
parent
eb23713875
commit
a94702991d
|
@ -8,6 +8,7 @@ dependencies = [
|
|||
"chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuse 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mmap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -22,6 +23,7 @@ dependencies = [
|
|||
"serde_yaml 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sodiumoxide 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"squash-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -107,6 +109,18 @@ dependencies = [
|
|||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuse"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread-scoped 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
|
@ -338,6 +352,11 @@ dependencies = [
|
|||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-scoped"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.3"
|
||||
|
@ -421,6 +440,7 @@ dependencies = [
|
|||
"checksum clap 2.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "58ad5e8142f3a5eab0c1cba5011aa383e009842936107fe4d94f1a8d380a1aec"
|
||||
"checksum constant_time_eq 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "07dcb7959f0f6f1cf662f9a7ff389bcb919924d99ac41cf31f10d611d8721323"
|
||||
"checksum filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5363ab8e4139b8568a6237db5248646e5a8a2f89bd5ccb02092182b11fd3e922"
|
||||
"checksum fuse 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5087262ce5b36fed6ccd4abf0a8224e48d055a2bb07fecb5605765de6f114a28"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "e32a70cf75e5846d53a673923498228bbec6a8624708a9ea5645f075d6276122"
|
||||
"checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135"
|
||||
|
@ -452,6 +472,7 @@ dependencies = [
|
|||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||
"checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a"
|
||||
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
|
||||
"checksum thread-scoped 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14387dce246d09efe184c8ebc34d9db5c0672a908b2f50efc53359ae13d5ae68"
|
||||
"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"
|
||||
"checksum time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "211b63c112206356ef1ff9b19355f43740fc3f85960c598a93d3a3d3ba7beade"
|
||||
"checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3"
|
||||
|
|
|
@ -23,6 +23,8 @@ ansi_term = "0.9"
|
|||
sodiumoxide = "0.0.14"
|
||||
filetime = "0.1"
|
||||
regex = "0.2"
|
||||
fuse = "0.3"
|
||||
time = "*"
|
||||
libc = "*"
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -98,10 +98,10 @@ Recommended: Brotli/2-7
|
|||
## TODO
|
||||
|
||||
### Core functionality
|
||||
- Default excludes in repository
|
||||
- Fix vacuum inconsistencies (either index related, or bundle syncing related)
|
||||
- Recompress & combine bundles
|
||||
- Allow to use tar files for backup and restore (--tar, http://alexcrichton.com/tar-rs/tar/index.html)
|
||||
- Allow to mount backups (inode id == position in index, lru cache)
|
||||
- File attributes
|
||||
- xattrs https://crates.io/crates/xattr
|
||||
|
||||
|
|
127
src/cli/args.rs
127
src/cli/args.rs
|
@ -65,6 +65,12 @@ pub enum Arguments {
|
|||
backup_name: Option<String>,
|
||||
inode: Option<String>
|
||||
},
|
||||
Mount {
|
||||
repo_path: String,
|
||||
backup_name: Option<String>,
|
||||
inode: Option<String>,
|
||||
mount_point: String
|
||||
},
|
||||
Analyze {
|
||||
repo_path: String
|
||||
},
|
||||
|
@ -115,6 +121,31 @@ pub fn split_repo_path(repo_path: &str) -> (&str, Option<&str>, Option<&str>) {
|
|||
(repo, backup, inode)
|
||||
}
|
||||
|
||||
pub fn parse_repo_path(repo_path: &str, backup_restr: Option<bool>, path_restr: Option<bool>) -> (&str, Option<&str>, Option<&str>) {
|
||||
let (repo, backup, path) = split_repo_path(repo_path);
|
||||
if let Some(restr) = backup_restr {
|
||||
if !restr && backup.is_some() {
|
||||
println!("No backup may be given here");
|
||||
exit(1);
|
||||
}
|
||||
if restr && backup.is_none() {
|
||||
println!("A backup must be specified");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if let Some(restr) = path_restr {
|
||||
if !restr && path.is_some() {
|
||||
println!("No subpath may be given here");
|
||||
exit(1);
|
||||
}
|
||||
if restr && path.is_none() {
|
||||
println!("A subpath must be specified");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
(repo, backup, path)
|
||||
}
|
||||
|
||||
fn parse_num(num: &str, name: &str) -> u64 {
|
||||
if let Ok(num) = num.parse::<u64>() {
|
||||
num
|
||||
|
@ -243,6 +274,11 @@ pub fn parse() -> Arguments {
|
|||
(about: "lists backups or backup contents")
|
||||
(@arg PATH: +required "repository[::backup[::subpath]] path")
|
||||
)
|
||||
(@subcommand mount =>
|
||||
(about: "mount a backup for inspection")
|
||||
(@arg PATH: +required "repository[::backup[::subpath]] path")
|
||||
(@arg MOUNTPOINT: +required "where to mount to backup")
|
||||
)
|
||||
(@subcommand bundlelist =>
|
||||
(about: "lists bundles in a repository")
|
||||
(@arg REPO: +required "path of the repository")
|
||||
|
@ -297,11 +333,7 @@ pub fn parse() -> Arguments {
|
|||
)
|
||||
).get_matches();
|
||||
if let Some(args) = args.subcommand_matches("init") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
return Arguments::Init {
|
||||
bundle_size: (parse_num(args.value_of("bundle_size").unwrap_or(&DEFAULT_BUNDLE_SIZE.to_string()), "Bundle size") * 1024 * 1024) as usize,
|
||||
chunker: parse_chunker(args.value_of("chunker").unwrap_or(DEFAULT_CHUNKER)),
|
||||
|
@ -313,15 +345,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("backup") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("BACKUP").unwrap());
|
||||
if backup.is_none() {
|
||||
println!("A backup must be specified");
|
||||
exit(1);
|
||||
}
|
||||
if inode.is_some() {
|
||||
println!("No subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, backup, _inode) = parse_repo_path(args.value_of("BACKUP").unwrap(), Some(true), Some(false));
|
||||
return Arguments::Backup {
|
||||
repo_path: repository.to_string(),
|
||||
backup_name: backup.unwrap().to_string(),
|
||||
|
@ -334,11 +358,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("restore") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("BACKUP").unwrap());
|
||||
if backup.is_none() {
|
||||
println!("A backup must be specified");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, backup, inode) = parse_repo_path(args.value_of("BACKUP").unwrap(), Some(true), None);
|
||||
return Arguments::Restore {
|
||||
repo_path: repository.to_string(),
|
||||
backup_name: backup.unwrap().to_string(),
|
||||
|
@ -347,11 +367,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("remove") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("BACKUP").unwrap());
|
||||
if backup.is_none() {
|
||||
println!("A backup must be specified");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, backup, inode) = parse_repo_path(args.value_of("BACKUP").unwrap(), Some(true), None);
|
||||
return Arguments::Remove {
|
||||
repo_path: repository.to_string(),
|
||||
backup_name: backup.unwrap().to_string(),
|
||||
|
@ -359,11 +375,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("prune") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
return Arguments::Prune {
|
||||
repo_path: repository.to_string(),
|
||||
prefix: args.value_of("prefix").unwrap_or("").to_string(),
|
||||
|
@ -375,11 +387,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("vacuum") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
return Arguments::Vacuum {
|
||||
repo_path: repository.to_string(),
|
||||
force: args.is_present("force"),
|
||||
|
@ -387,7 +395,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("check") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("PATH").unwrap());
|
||||
let (repository, backup, inode) = parse_repo_path(args.value_of("PATH").unwrap(), None, None);
|
||||
return Arguments::Check {
|
||||
repo_path: repository.to_string(),
|
||||
backup_name: backup.map(|v| v.to_string()),
|
||||
|
@ -396,7 +404,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("list") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("PATH").unwrap());
|
||||
let (repository, backup, inode) = parse_repo_path(args.value_of("PATH").unwrap(), None, None);
|
||||
return Arguments::List {
|
||||
repo_path: repository.to_string(),
|
||||
backup_name: backup.map(|v| v.to_string()),
|
||||
|
@ -404,50 +412,43 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("bundlelist") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
return Arguments::BundleList {
|
||||
repo_path: repository.to_string(),
|
||||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("bundleinfo") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
return Arguments::BundleInfo {
|
||||
repo_path: repository.to_string(),
|
||||
bundle_id: parse_bundle_id(args.value_of("BUNDLE").unwrap())
|
||||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("info") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("PATH").unwrap());
|
||||
let (repository, backup, inode) = parse_repo_path(args.value_of("PATH").unwrap(), None, None);
|
||||
return Arguments::Info {
|
||||
repo_path: repository.to_string(),
|
||||
backup_name: backup.map(|v| v.to_string()),
|
||||
inode: inode.map(|v| v.to_string())
|
||||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("analyze") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
if let Some(args) = args.subcommand_matches("mount") {
|
||||
let (repository, backup, inode) = parse_repo_path(args.value_of("PATH").unwrap(), None, None);
|
||||
return Arguments::Mount {
|
||||
repo_path: repository.to_string(),
|
||||
backup_name: backup.map(|v| v.to_string()),
|
||||
inode: inode.map(|v| v.to_string()),
|
||||
mount_point: args.value_of("MOUNTPOINT").unwrap().to_string()
|
||||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("analyze") {
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
return Arguments::Analyze {
|
||||
repo_path: repository.to_string()
|
||||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("import") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
return Arguments::Import {
|
||||
repo_path: repository.to_string(),
|
||||
remote_path: args.value_of("REMOTE").unwrap().to_string(),
|
||||
|
@ -455,11 +456,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("configure") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
return Arguments::Configure {
|
||||
bundle_size: args.value_of("bundle_size").map(|v| (parse_num(v, "Bundle size") * 1024 * 1024) as usize),
|
||||
chunker: args.value_of("chunker").map(|v| parse_chunker(v)),
|
||||
|
@ -481,11 +478,7 @@ pub fn parse() -> Arguments {
|
|||
}
|
||||
}
|
||||
if let Some(args) = args.subcommand_matches("addkey") {
|
||||
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
|
||||
if backup.is_some() || inode.is_some() {
|
||||
println!("No backups or subpaths may be given here");
|
||||
exit(1);
|
||||
}
|
||||
let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false));
|
||||
let generate = args.is_present("generate");
|
||||
if !generate && !args.is_present("FILE") {
|
||||
println!("Without --generate, a file containing the key pair must be given");
|
||||
|
|
|
@ -372,6 +372,21 @@ pub fn run() {
|
|||
print_repoinfo(&repo.info());
|
||||
}
|
||||
},
|
||||
Arguments::Mount{repo_path, backup_name, inode, mount_point} => {
|
||||
let mut repo = open_repository(&repo_path);
|
||||
let fs = if let Some(backup_name) = backup_name {
|
||||
let backup = get_backup(&repo, &backup_name);
|
||||
if let Some(inode) = inode {
|
||||
let inode = checked(repo.get_backup_inode(&backup, inode), "load subpath inode");
|
||||
checked(FuseFilesystem::from_inode(&mut repo, inode), "create fuse filesystem")
|
||||
} else {
|
||||
checked(FuseFilesystem::from_backup(&mut repo, &backup), "create fuse filesystem")
|
||||
}
|
||||
} else {
|
||||
checked(FuseFilesystem::from_repository(&mut repo), "create fuse filesystem")
|
||||
};
|
||||
checked(fs.mount(&mount_point), "mount filesystem");
|
||||
},
|
||||
Arguments::Analyze{repo_path} => {
|
||||
let mut repo = open_repository(&repo_path);
|
||||
print_analysis(&checked(repo.analyze_usage(), "analyze repository"));
|
||||
|
|
|
@ -17,6 +17,8 @@ extern crate sodiumoxide;
|
|||
extern crate ansi_term;
|
||||
extern crate filetime;
|
||||
extern crate regex;
|
||||
extern crate fuse;
|
||||
extern crate time;
|
||||
extern crate libc;
|
||||
|
||||
pub mod util;
|
||||
|
@ -26,6 +28,7 @@ mod chunker;
|
|||
mod repository;
|
||||
mod cli;
|
||||
mod prelude;
|
||||
mod mount;
|
||||
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -0,0 +1,561 @@
|
|||
use ::prelude::*;
|
||||
|
||||
use std::path::Path;
|
||||
use std::ffi::OsStr;
|
||||
use std::collections::HashMap;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::mem;
|
||||
use std::cmp::min;
|
||||
|
||||
use fuse;
|
||||
use time::Timespec;
|
||||
use libc;
|
||||
|
||||
|
||||
macro_rules! fuse_try(
|
||||
($val:expr, $reply:expr) => {
|
||||
match $val {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
info!("Error: {:?}", err);
|
||||
return $reply.error(libc::EIO);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! str(
|
||||
($val:expr, $reply:expr) => {
|
||||
match $val.to_str() {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
info!("Error: Name is not valid unicode");
|
||||
return $reply.error(libc::ENAMETOOLONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! inode(
|
||||
($slf:expr, $num:expr, $reply:expr) => {
|
||||
match $slf.get_inode($num) {
|
||||
Some(inode) => inode,
|
||||
None => {
|
||||
info!("Error: Inode not found: {}", $num);
|
||||
return $reply.error(libc::EBADF)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! lookup(
|
||||
($slf:expr, $parent:expr, $name:expr, $reply:expr) => {
|
||||
match fuse_try!($slf.get_child(&$parent, $name), $reply) {
|
||||
Some(inode) => inode,
|
||||
None => {
|
||||
info!("Error: Child node not found: {} -> {}", $parent.borrow().num, $name);
|
||||
return $reply.error(libc::ENOENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
#[inline]
|
||||
fn convert_file_type(kind: FileType) -> fuse::FileType {
|
||||
match kind {
|
||||
FileType::Directory => fuse::FileType::Directory,
|
||||
FileType::File => fuse::FileType::RegularFile,
|
||||
FileType::Symlink => fuse::FileType::Symlink,
|
||||
}
|
||||
}
|
||||
|
||||
type FuseInodeRef = Rc<RefCell<FuseInode>>;
|
||||
|
||||
pub struct FuseInode {
|
||||
num: u64,
|
||||
inode: Inode,
|
||||
parent: Option<FuseInodeRef>,
|
||||
children: HashMap<String, FuseInodeRef>,
|
||||
chunks: Option<ChunkList>
|
||||
}
|
||||
|
||||
impl FuseInode {
|
||||
pub fn to_attrs(&self) -> fuse::FileAttr {
|
||||
fuse::FileAttr {
|
||||
ino: self.num,
|
||||
size: self.inode.size,
|
||||
blocks: self.inode.size / 512,
|
||||
atime: Timespec::new(self.inode.access_time, 0),
|
||||
mtime: Timespec::new(self.inode.modify_time, 0),
|
||||
ctime: Timespec::new(0, 0),
|
||||
crtime: Timespec::new(0, 0),
|
||||
kind: convert_file_type(self.inode.file_type),
|
||||
perm: self.inode.mode as u16,
|
||||
nlink: 1,
|
||||
uid: self.inode.user,
|
||||
gid: self.inode.group,
|
||||
rdev: 0,
|
||||
flags: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dir_list(&self) -> Option<Vec<(u64, fuse::FileType, String)>> {
|
||||
if self.inode.file_type != FileType::Directory {
|
||||
return None
|
||||
}
|
||||
let mut list = Vec::with_capacity(self.children.len()+2);
|
||||
list.push((self.num, fuse::FileType::Directory, ".".to_string()));
|
||||
if let Some(ref parent) = self.parent {
|
||||
let parent = parent.borrow();
|
||||
list.push((parent.num, fuse::FileType::Directory, "..".to_string()));
|
||||
} else {
|
||||
list.push((self.num, fuse::FileType::Directory, "..".to_string()));
|
||||
}
|
||||
for ch in self.children.values() {
|
||||
let child = ch.borrow();
|
||||
list.push((child.num, convert_file_type(child.inode.file_type), child.inode.name.clone()));
|
||||
}
|
||||
Some(list)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct FuseFilesystem<'a> {
|
||||
next_id: u64,
|
||||
repository: &'a mut Repository,
|
||||
inodes: HashMap<u64, FuseInodeRef>
|
||||
}
|
||||
|
||||
impl<'a> FuseFilesystem<'a> {
|
||||
pub fn new(repository: &'a mut Repository) -> Result<Self, RepositoryError> {
|
||||
Ok(FuseFilesystem {
|
||||
next_id: 1,
|
||||
repository: repository,
|
||||
inodes: HashMap::new()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_repository(repository: &'a mut Repository) -> Result<Self, RepositoryError> {
|
||||
let mut backups = vec![];
|
||||
for (name, backup) in try!(repository.get_backups()) {
|
||||
let inode = try!(repository.get_inode(&backup.root));
|
||||
backups.push((name, inode));
|
||||
}
|
||||
let mut fs = try!(FuseFilesystem::new(repository));
|
||||
let root = fs.add_virtual_directory("".to_string(), None);
|
||||
for (name, mut backup) in backups {
|
||||
let mut parent = root.clone();
|
||||
for part in name.split('/') {
|
||||
parent = match fs.get_child(&parent, part).unwrap() {
|
||||
Some(child) => child,
|
||||
None => fs.add_virtual_directory(part.to_string(), Some(parent))
|
||||
};
|
||||
}
|
||||
let mut parent_mut = parent.borrow_mut();
|
||||
backup.name = parent_mut.inode.name.clone();
|
||||
parent_mut.inode = backup;
|
||||
}
|
||||
Ok(fs)
|
||||
}
|
||||
|
||||
pub fn from_backup(repository: &'a mut Repository, backup: &Backup) -> Result<Self, RepositoryError> {
|
||||
let inode = try!(repository.get_inode(&backup.root));
|
||||
let mut fs = try!(FuseFilesystem::new(repository));
|
||||
fs.add_inode(inode, None);
|
||||
Ok(fs)
|
||||
}
|
||||
|
||||
pub fn from_inode(repository: &'a mut Repository, inode: Inode) -> Result<Self, RepositoryError> {
|
||||
let mut fs = try!(FuseFilesystem::new(repository));
|
||||
fs.add_inode(inode, None);
|
||||
Ok(fs)
|
||||
}
|
||||
|
||||
pub fn add_virtual_directory(&mut self, name: String, parent: Option<FuseInodeRef>) -> FuseInodeRef {
|
||||
self.add_inode(Inode {
|
||||
name: name,
|
||||
file_type: FileType::Directory,
|
||||
..Default::default()
|
||||
}, parent)
|
||||
}
|
||||
|
||||
pub fn add_inode(&mut self, inode: Inode, parent: Option<FuseInodeRef>) -> FuseInodeRef {
|
||||
let inode = FuseInode {
|
||||
inode: inode,
|
||||
num: self.next_id,
|
||||
parent: parent.clone(),
|
||||
chunks: None,
|
||||
children: HashMap::new()
|
||||
};
|
||||
let name = inode.inode.name.clone();
|
||||
let inode = Rc::new(RefCell::new(inode));
|
||||
self.inodes.insert(self.next_id, inode.clone());
|
||||
if let Some(parent) = parent {
|
||||
parent.borrow_mut().children.insert(name, inode.clone());
|
||||
}
|
||||
self.next_id += 1;
|
||||
inode
|
||||
}
|
||||
|
||||
pub fn mount<P: AsRef<Path>>(self, mountpoint: P) -> Result<(), RepositoryError> {
|
||||
Ok(try!(fuse::mount(self, &mountpoint, &[
|
||||
OsStr::new("default_permissions"),
|
||||
OsStr::new("kernel_cache"),
|
||||
OsStr::new("auto_cache"),
|
||||
OsStr::new("readonly")
|
||||
])))
|
||||
}
|
||||
|
||||
pub fn get_inode(&mut self, num: u64) -> Option<FuseInodeRef> {
|
||||
self.inodes.get(&num).cloned()
|
||||
}
|
||||
|
||||
pub fn get_child(&mut self, parent: &FuseInodeRef, name: &str) -> Result<Option<FuseInodeRef>, RepositoryError> {
|
||||
let mut parent_mut = parent.borrow_mut();
|
||||
if let Some(child) = parent_mut.children.get(name) {
|
||||
return Ok(Some(child.clone()))
|
||||
}
|
||||
let child;
|
||||
if let Some(chunks) = parent_mut.inode.children.as_ref().and_then(|c| c.get(name)) {
|
||||
child = Rc::new(RefCell::new(FuseInode {
|
||||
num: self.next_id,
|
||||
inode: try!(self.repository.get_inode(chunks)),
|
||||
parent: Some(parent.clone()),
|
||||
children: HashMap::new(),
|
||||
chunks: None
|
||||
}));
|
||||
self.inodes.insert(self.next_id, child.clone());
|
||||
self.next_id +=1;
|
||||
} else {
|
||||
return Ok(None)
|
||||
}
|
||||
parent_mut.children.insert(name.to_string(), child.clone());
|
||||
Ok(Some(child))
|
||||
}
|
||||
|
||||
pub fn fetch_children(&mut self, parent: &FuseInodeRef) -> Result<(), RepositoryError> {
|
||||
let mut parent_mut = parent.borrow_mut();
|
||||
let mut parent_children = HashMap::new();
|
||||
mem::swap(&mut parent_children, &mut parent_mut.children);
|
||||
if let Some(ref children) = parent_mut.inode.children {
|
||||
for (name, chunks) in children {
|
||||
if !parent_mut.children.contains_key(name) {
|
||||
let child = Rc::new(RefCell::new(FuseInode {
|
||||
num: self.next_id,
|
||||
inode: try!(self.repository.get_inode(chunks)),
|
||||
parent: Some(parent.clone()),
|
||||
children: HashMap::new(),
|
||||
chunks: None
|
||||
}));
|
||||
self.inodes.insert(self.next_id, child.clone());
|
||||
self.next_id +=1;
|
||||
parent_children.insert(name.clone(), child);
|
||||
}
|
||||
}
|
||||
}
|
||||
mem::swap(&mut parent_children, &mut parent_mut.children);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fetch_chunks(&mut self, inode: &FuseInodeRef) -> Result<(), RepositoryError> {
|
||||
let mut inode = inode.borrow_mut();
|
||||
let mut chunks = None;
|
||||
match inode.inode.contents {
|
||||
None | Some(FileContents::Inline(_)) => (),
|
||||
Some(FileContents::ChunkedDirect(ref c)) => {
|
||||
chunks = Some(c.clone());
|
||||
},
|
||||
Some(FileContents::ChunkedIndirect(ref c)) => {
|
||||
let chunk_data = try!(self.repository.get_data(c));
|
||||
chunks = Some(ChunkList::read_from(&chunk_data));
|
||||
}
|
||||
}
|
||||
inode.chunks = chunks;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> fuse::Filesystem for FuseFilesystem<'a> {
|
||||
|
||||
/// Look up a directory entry by name and get its attributes.
|
||||
fn lookup (&mut self, _req: &fuse::Request, parent: u64, name: &OsStr, reply: fuse::ReplyEntry) {
|
||||
let sname = str!(name, reply);
|
||||
let parent = inode!(self, parent, reply);
|
||||
let child = lookup!(self, &parent, sname, reply);
|
||||
let ttl = Timespec::new(60, 0);
|
||||
let attrs = child.borrow().to_attrs();
|
||||
reply.entry(&ttl, &attrs, 0)
|
||||
}
|
||||
|
||||
fn destroy (&mut self, _req: &fuse::Request) {
|
||||
info!("destroy");
|
||||
}
|
||||
|
||||
/// Forget about an inode
|
||||
/// The nlookup parameter indicates the number of lookups previously performed on
|
||||
/// this inode. If the filesystem implements inode lifetimes, it is recommended that
|
||||
/// inodes acquire a single reference on each lookup, and lose nlookup references on
|
||||
/// each forget. The filesystem may ignore forget calls, if the inodes don't need to
|
||||
/// have a limited lifetime. On unmount it is not guaranteed, that all referenced
|
||||
/// inodes will receive a forget message.
|
||||
fn forget (&mut self, _req: &fuse::Request, ino: u64, _nlookup: u64) {
|
||||
info!("forget {:?}", ino);
|
||||
//self.fs.forget(ino).unwrap();
|
||||
}
|
||||
|
||||
/// Get file attributes
|
||||
fn getattr (&mut self, _req: &fuse::Request, ino: u64, reply: fuse::ReplyAttr) {
|
||||
let inode = inode!(self, ino, reply);
|
||||
let ttl = Timespec::new(60, 0);
|
||||
reply.attr(&ttl, &inode.borrow().to_attrs());
|
||||
}
|
||||
|
||||
/// Set file attributes
|
||||
fn setattr (&mut self, _req: &fuse::Request, _ino: u64, _mode: Option<u32>, _uid: Option<u32>, _gid: Option<u32>, _size: Option<u64>, _atime: Option<Timespec>, _mtime: Option<Timespec>, _fh: Option<u64>, _crtime: Option<Timespec>, _chgtime: Option<Timespec>, _bkuptime: Option<Timespec>, _flags: Option<u32>, reply: fuse::ReplyAttr) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Read symbolic link
|
||||
fn readlink (&mut self, _req: &fuse::Request, ino: u64, reply: fuse::ReplyData) {
|
||||
let inode = inode!(self, ino, reply);
|
||||
let inode = inode.borrow();
|
||||
match inode.inode.symlink_target {
|
||||
None => reply.error(libc::EINVAL),
|
||||
Some(ref link) => reply.data(link.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a hard link
|
||||
fn link (&mut self, _req: &fuse::Request, _ino: u64, _newparent: u64, _newname: &OsStr, reply: fuse::ReplyEntry) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Create file node
|
||||
/// Create a regular file, character device, block device, fifo or socket node.
|
||||
fn mknod (&mut self, _req: &fuse::Request, _parent: u64, _name: &OsStr, _mode: u32, _rdev: u32, reply: fuse::ReplyEntry) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Create a directory
|
||||
fn mkdir (&mut self, _req: &fuse::Request, _parent: u64, _name: &OsStr, _mode: u32, reply: fuse::ReplyEntry) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Remove a file
|
||||
fn unlink (&mut self, _req: &fuse::Request, _parent: u64, _name: &OsStr, reply: fuse::ReplyEmpty) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Remove a directory
|
||||
fn rmdir (&mut self, _req: &fuse::Request, _parent: u64, _name: &OsStr, reply: fuse::ReplyEmpty) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Create a symbolic link
|
||||
fn symlink (&mut self, _req: &fuse::Request, _parent: u64, _name: &OsStr, _link: &Path, reply: fuse::ReplyEntry) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Rename a file
|
||||
fn rename (&mut self, _req: &fuse::Request, _parent: u64, _name: &OsStr, _newparent: u64, _newname: &OsStr, reply: fuse::ReplyEmpty) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Open a file
|
||||
/// Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are
|
||||
/// available in flags. Filesystem may store an arbitrary file handle (pointer, index,
|
||||
/// etc) in fh, and use this in other all other file operations (read, write, flush,
|
||||
/// release, fsync). Filesystem may also implement stateless file I/O and not store
|
||||
/// anything in fh. There are also some flags (direct_io, keep_cache) which the
|
||||
/// filesystem may set, to change the way the file is opened. See fuse_file_info
|
||||
/// structure in <fuse_common.h> for more details.
|
||||
fn open (&mut self, _req: &fuse::Request, ino: u64, flags: u32, reply: fuse::ReplyOpen) {
|
||||
info!("open {:?}, flags: {:o}", ino, flags);
|
||||
if (flags & (libc::O_WRONLY | libc::O_RDWR | libc::O_TRUNC) as u32) != 0 {
|
||||
return reply.error(libc::EROFS);
|
||||
}
|
||||
let inode = inode!(self, ino, reply);
|
||||
fuse_try!(self.fetch_chunks(&inode), reply);
|
||||
reply.opened(ino, libc::O_RDONLY as u32);
|
||||
}
|
||||
|
||||
/// Read data
|
||||
/// Read should send exactly the number of bytes requested except on EOF or error,
|
||||
/// otherwise the rest of the data will be substituted with zeroes. An exception to
|
||||
/// this is when the file has been opened in 'direct_io' mode, in which case the
|
||||
/// return value of the read system call will reflect the return value of this
|
||||
/// operation. fh will contain the value set by the open method, or will be undefined
|
||||
/// if the open method didn't set any value.
|
||||
fn read (&mut self, _req: &fuse::Request, ino: u64, _fh: u64, mut offset: u64, mut size: u32, reply: fuse::ReplyData) {
|
||||
info!("read {:?}, offset {}, size {}", ino, offset, size);
|
||||
let inode = inode!(self, ino, reply);
|
||||
let inode = inode.borrow();
|
||||
match inode.inode.contents {
|
||||
None => return reply.data(&[]),
|
||||
Some(FileContents::Inline(ref data)) => return reply.data(&data[min(offset as usize, data.len())..min(offset as usize+size as usize, data.len())]),
|
||||
_ => ()
|
||||
}
|
||||
if let Some(ref chunks) = inode.chunks {
|
||||
let mut data = Vec::with_capacity(size as usize);
|
||||
for &(hash, len) in chunks.iter() {
|
||||
if len as u64 <= offset {
|
||||
offset -= len as u64;
|
||||
continue
|
||||
}
|
||||
let chunk = match fuse_try!(self.repository.get_chunk(hash), reply) {
|
||||
Some(chunk) => chunk,
|
||||
None => return reply.error(libc::EIO)
|
||||
};
|
||||
assert_eq!(chunk.len() as u32, len);
|
||||
data.extend_from_slice(&chunk[offset as usize..min(offset as usize + size as usize, len as usize)]);
|
||||
if len - offset as u32 >= size {
|
||||
break
|
||||
}
|
||||
size -= len - offset as u32;
|
||||
offset = 0;
|
||||
}
|
||||
reply.data(&data)
|
||||
} else {
|
||||
reply.error(libc::EBADF)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write data
|
||||
fn write (&mut self, _req: &fuse::Request, _ino: u64, _fh: u64, _offset: u64, _data: &[u8], _flags: u32, reply: fuse::ReplyWrite) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Flush method
|
||||
fn flush (&mut self, _req: &fuse::Request, _ino: u64, _fh: u64, _lock_owner: u64, reply: fuse::ReplyEmpty) {
|
||||
reply.ok()
|
||||
}
|
||||
|
||||
/// Release an open file
|
||||
/// Release is called when there are no more references to an open file: all file
|
||||
/// descriptors are closed and all memory mappings are unmapped. For every open
|
||||
/// call there will be exactly one release call. The filesystem may reply with an
|
||||
/// error, but error values are not returned to close() or munmap() which triggered
|
||||
/// the release. fh will contain the value set by the open method, or will be undefined
|
||||
/// if the open method didn't set any value. flags will contain the same flags as for
|
||||
/// open.
|
||||
fn release (&mut self, _req: &fuse::Request, _ino: u64, _fh: u64, _flags: u32, _lock_owner: u64, _flush: bool, reply: fuse::ReplyEmpty) {
|
||||
/*if self.read_fds.remove(&fh).is_some() || self.write_fds.remove(&fh).is_some() {
|
||||
reply.ok();
|
||||
} else {
|
||||
reply.error(libc::EBADF);
|
||||
}*/
|
||||
reply.error(libc::ENOSYS)
|
||||
}
|
||||
|
||||
/// Synchronize file contents
|
||||
fn fsync (&mut self, _req: &fuse::Request, _ino: u64, _fh: u64, _datasync: bool, reply: fuse::ReplyEmpty) {
|
||||
reply.ok()
|
||||
}
|
||||
|
||||
/// Open a directory, finished
|
||||
fn opendir (&mut self, _req: &fuse::Request, ino: u64, _flags: u32, reply: fuse::ReplyOpen) {
|
||||
let dir = inode!(self, ino, reply);
|
||||
fuse_try!(self.fetch_children(&dir), reply);
|
||||
reply.opened(ino, 0);
|
||||
}
|
||||
|
||||
/// Read directory, finished
|
||||
fn readdir (&mut self, _req: &fuse::Request, ino: u64, _fh: u64, offset: u64, mut reply: fuse::ReplyDirectory) {
|
||||
let dir = inode!(self, ino, reply);
|
||||
let dir = dir.borrow();
|
||||
if let Some(entries) = dir.dir_list() {
|
||||
for (i, (num, file_type, name)) in entries.into_iter().enumerate() {
|
||||
if i < offset as usize {
|
||||
continue
|
||||
}
|
||||
if reply.add(num, i as u64 +1, file_type, &Path::new(&name)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
reply.ok()
|
||||
} else {
|
||||
reply.error(libc::ENOTDIR)
|
||||
}
|
||||
}
|
||||
|
||||
/// Release an open directory, finished
|
||||
fn releasedir (&mut self, _req: &fuse::Request, _ino: u64, _fh: u64, _flags: u32, reply: fuse::ReplyEmpty) {
|
||||
reply.ok()
|
||||
}
|
||||
|
||||
/// Synchronize directory contents, finished
|
||||
fn fsyncdir (&mut self, _req: &fuse::Request, _ino: u64, _fh: u64, _datasync: bool, reply: fuse::ReplyEmpty) {
|
||||
reply.ok()
|
||||
}
|
||||
|
||||
/// Get file system statistics
|
||||
fn statfs (&mut self, _req: &fuse::Request, _ino: u64, reply: fuse::ReplyStatfs) {
|
||||
let info = self.repository.info();
|
||||
reply.statfs(
|
||||
info.raw_data_size/512 as u64, //total blocks
|
||||
0, //free blocks for admin
|
||||
0, //free blocks for users
|
||||
0,
|
||||
0,
|
||||
512 as u32, //block size
|
||||
255, //max name length
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/// Set an extended attribute
|
||||
fn setxattr (&mut self, _req: &fuse::Request, _ino: u64, _name: &OsStr, _value: &[u8], _flags: u32, _position: u32, reply: fuse::ReplyEmpty) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Get an extended attribute
|
||||
fn getxattr (&mut self, _req: &fuse::Request, _ino: u64, _name: &OsStr, _size: u32, reply: fuse::ReplyXattr) {
|
||||
// #FIXME:30 If arg.size is zero, the size of the value should be sent with fuse_getxattr_out
|
||||
// #FIXME:0 If arg.size is non-zero, send the value if it fits, or ERANGE otherwise
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
|
||||
/// List extended attribute names
|
||||
fn listxattr (&mut self, _req: &fuse::Request, _ino: u64, _size: u32, reply: fuse::ReplyXattr) {
|
||||
// #FIXME:20 If arg.size is zero, the size of the attribute list should be sent with fuse_getxattr_out
|
||||
// #FIXME:10 If arg.size is non-zero, send the attribute list if it fits, or ERANGE otherwise
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
|
||||
/// Remove an extended attribute
|
||||
fn removexattr (&mut self, _req: &fuse::Request, _ino: u64, _name: &OsStr, reply: fuse::ReplyEmpty) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Check file access permissions
|
||||
/// This will be called for the access() system call. If the 'default_permissions'
|
||||
/// mount option is given, this method is not called. This method is not called
|
||||
/// under Linux kernel versions 2.4.x
|
||||
fn access (&mut self, _req: &fuse::Request, _ino: u64, _mask: u32, reply: fuse::ReplyEmpty) {
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
|
||||
/// Create and open a file
|
||||
fn create (&mut self, _req: &fuse::Request, _parent: u64, _name: &OsStr, _mode: u32, _flags: u32, reply: fuse::ReplyCreate) {
|
||||
reply.error(libc::EROFS)
|
||||
}
|
||||
|
||||
/// Test for a POSIX file lock
|
||||
fn getlk (&mut self, _req: &fuse::Request, _ino: u64, _fh: u64, _lock_owner: u64, _start: u64, _end: u64, _typ: u32, _pid: u32, reply: fuse::ReplyLock) {
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
|
||||
/// Acquire, modify or release a POSIX file lock
|
||||
fn setlk (&mut self, _req: &fuse::Request, _ino: u64, _fh: u64, _lock_owner: u64, _start: u64, _end: u64, _typ: u32, _pid: u32, _sleep: bool, reply: fuse::ReplyEmpty) {
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
|
||||
/// Map block index within file to block index within device
|
||||
fn bmap (&mut self, _req: &fuse::Request, _ino: u64, _blocksize: u32, _idx: u64, reply: fuse::ReplyBmap) {
|
||||
reply.error(libc::ENOSYS);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
pub use ::util::*;
|
||||
pub use ::bundledb::{BundleReader, BundleMode, BundleWriter, BundleInfo, BundleId, BundleDbError, BundleDb, BundleWriterError};
|
||||
pub use ::chunker::{ChunkerType, Chunker, ChunkerStatus, IChunker, ChunkerError};
|
||||
pub use ::repository::{Repository, Backup, Config, RepositoryError, RepositoryInfo, Inode, FileType, RepositoryIntegrityError, BackupFileError, BackupError, BackupOptions, BundleAnalysis};
|
||||
pub use ::repository::{Repository, Backup, Config, RepositoryError, RepositoryInfo, Inode, FileType, RepositoryIntegrityError, BackupFileError, BackupError, BackupOptions, BundleAnalysis, FileContents};
|
||||
pub use ::index::{Index, Location, IndexError};
|
||||
pub use ::mount::FuseFilesystem;
|
||||
|
||||
pub use serde::{Serialize, Deserialize};
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ quick_error!{
|
|||
}
|
||||
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum FileType {
|
||||
File,
|
||||
Directory,
|
||||
|
|
|
@ -20,7 +20,7 @@ use std::os::unix::fs::symlink;
|
|||
|
||||
pub use self::error::RepositoryError;
|
||||
pub use self::config::Config;
|
||||
pub use self::metadata::{Inode, FileType};
|
||||
pub use self::metadata::{Inode, FileType, FileContents};
|
||||
pub use self::backup::{BackupError, BackupOptions};
|
||||
pub use self::backup_file::{Backup, BackupFileError};
|
||||
pub use self::integrity::RepositoryIntegrityError;
|
||||
|
|
Loading…
Reference in New Issue