From a94702991de9ddf757ddc68c99f724c249214b7f Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Sun, 26 Mar 2017 11:34:16 +0200 Subject: [PATCH] Mounting backups --- Cargo.lock | 21 ++ Cargo.toml | 2 + README.md | 2 +- src/cli/args.rs | 127 ++++----- src/cli/mod.rs | 15 + src/main.rs | 3 + src/mount.rs | 561 +++++++++++++++++++++++++++++++++++++ src/prelude.rs | 3 +- src/repository/metadata.rs | 2 +- src/repository/mod.rs | 2 +- 10 files changed, 667 insertions(+), 71 deletions(-) create mode 100644 src/mount.rs diff --git a/Cargo.lock b/Cargo.lock index ead6281..4a79239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 44e131b..18e2382 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/README.md b/README.md index b067bcd..e61d592 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/cli/args.rs b/src/cli/args.rs index cde15df..0a363f1 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -65,6 +65,12 @@ pub enum Arguments { backup_name: Option, inode: Option }, + Mount { + repo_path: String, + backup_name: Option, + inode: Option, + 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, path_restr: Option) -> (&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::() { 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"); diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 1d423fb..5de6229 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -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")); diff --git a/src/main.rs b/src/main.rs index 44ebca2..c964ae7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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() { diff --git a/src/mount.rs b/src/mount.rs new file mode 100644 index 0000000..c406498 --- /dev/null +++ b/src/mount.rs @@ -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>; + +pub struct FuseInode { + num: u64, + inode: Inode, + parent: Option, + children: HashMap, + chunks: Option +} + +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> { + 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 +} + +impl<'a> FuseFilesystem<'a> { + pub fn new(repository: &'a mut Repository) -> Result { + Ok(FuseFilesystem { + next_id: 1, + repository: repository, + inodes: HashMap::new() + }) + } + + pub fn from_repository(repository: &'a mut Repository) -> Result { + 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 { + 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 { + 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 { + self.add_inode(Inode { + name: name, + file_type: FileType::Directory, + ..Default::default() + }, parent) + } + + pub fn add_inode(&mut self, inode: Inode, parent: Option) -> 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>(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 { + self.inodes.get(&num).cloned() + } + + pub fn get_child(&mut self, parent: &FuseInodeRef, name: &str) -> Result, 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, _uid: Option, _gid: Option, _size: Option, _atime: Option, _mtime: Option, _fh: Option, _crtime: Option, _chgtime: Option, _bkuptime: Option, _flags: Option, 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 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); + } + +} diff --git a/src/prelude.rs b/src/prelude.rs index 9978ded..cc63c92 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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}; diff --git a/src/repository/metadata.rs b/src/repository/metadata.rs index 6e52a64..346938f 100644 --- a/src/repository/metadata.rs +++ b/src/repository/metadata.rs @@ -68,7 +68,7 @@ quick_error!{ } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum FileType { File, Directory, diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 3c08e39..f8a5b53 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -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;