-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
229 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
//! `mount` subcommand | ||
mod fs; | ||
use std::{ffi::OsStr, path::PathBuf}; | ||
|
||
use fs::HelloFS; | ||
|
||
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP}; | ||
|
||
use abscissa_core::{Command, Runnable, Shutdown}; | ||
use anyhow::Result; | ||
use fuse_mt::{mount, FuseMT}; | ||
|
||
/// `dump` subcommand | ||
#[derive(clap::Parser, Command, Debug)] | ||
pub(crate) struct MountCmd { | ||
/// file from snapshot to dump | ||
#[clap(value_name = "SNAPSHOT[:PATH]")] | ||
snap: String, | ||
|
||
mountpoint: PathBuf, | ||
} | ||
|
||
impl Runnable for MountCmd { | ||
fn run(&self) { | ||
if let Err(err) = self.inner_run() { | ||
status_err!("{}", err); | ||
RUSTIC_APP.shutdown(Shutdown::Crash); | ||
}; | ||
} | ||
} | ||
|
||
impl MountCmd { | ||
fn inner_run(&self) -> Result<()> { | ||
let config = RUSTIC_APP.config(); | ||
|
||
let repo = open_repository(&config)?.to_indexed()?; | ||
let node = | ||
repo.node_from_snapshot_path(&self.snap, |sn| config.snapshot_filter.matches(sn))?; | ||
|
||
let options = [OsStr::new("-o"), OsStr::new("fsname=rusticfs")]; | ||
|
||
let fs = FuseMT::new(HelloFS::from_node(repo, node)?, 1); | ||
mount(fs, &self.mountpoint, &options)?; | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
use fuse_mt::{ | ||
CallbackResult, DirectoryEntry, FileAttr, FileType, FilesystemMT, RequestInfo, ResultEmpty, | ||
ResultEntry, ResultOpen, ResultReaddir, ResultSlice, | ||
}; | ||
use rustic_core::{ | ||
repofile::{Node, NodeType}, | ||
OpenFile, | ||
}; | ||
use rustic_core::{Id, IndexedFull, Repository}; | ||
use std::{collections::BTreeMap, path::Path, time::Duration}; | ||
use std::{sync::RwLock, time::SystemTime}; | ||
|
||
pub(super) struct HelloFS<P, S> { | ||
repo: Repository<P, S>, | ||
root: Id, | ||
open_files: RwLock<BTreeMap<u64, OpenFile>>, | ||
} | ||
|
||
impl<P, S: IndexedFull> HelloFS<P, S> { | ||
pub(crate) fn from_node(repo: Repository<P, S>, node: Node) -> anyhow::Result<Self> { | ||
let open_files = RwLock::new(BTreeMap::new()); | ||
|
||
Ok(Self { | ||
repo, | ||
root: node.subtree.unwrap(), | ||
open_files, | ||
}) | ||
} | ||
|
||
fn node_from_path(&self, path: &Path) -> Result<Node, i32> { | ||
Ok(self | ||
.repo | ||
.node_from_path(self.root, path) | ||
.map_err(|_| libc::ENOSYS)?) | ||
} | ||
} | ||
|
||
fn node_to_filetype(node: &Node) -> FileType { | ||
match node.node_type { | ||
NodeType::File => FileType::RegularFile, | ||
NodeType::Dir => FileType::Directory, | ||
NodeType::Symlink { .. } => FileType::Symlink, | ||
NodeType::Chardev { .. } => FileType::CharDevice, | ||
NodeType::Dev { .. } => FileType::BlockDevice, | ||
NodeType::Fifo => FileType::NamedPipe, | ||
NodeType::Socket => FileType::Socket, | ||
} | ||
} | ||
|
||
fn node_type_to_rdev(tpe: &NodeType) -> u32 { | ||
u32::try_from(match tpe { | ||
NodeType::Dev { device } => *device, | ||
NodeType::Chardev { device } => *device, | ||
_ => 0, | ||
}) | ||
.unwrap() | ||
} | ||
|
||
impl<P, S: IndexedFull> FilesystemMT for HelloFS<P, S> { | ||
fn getattr(&self, _req: RequestInfo, path: &Path, _fh: Option<u64>) -> ResultEntry { | ||
let node = self.node_from_path(path)?; | ||
Ok(( | ||
Duration::from_secs(1), | ||
FileAttr { | ||
/// Size in bytes | ||
size: node.meta.size, | ||
/// Size in blocks | ||
blocks: 0, | ||
// Time of last access | ||
atime: SystemTime::now(), | ||
/// Time of last modification | ||
mtime: SystemTime::now(), | ||
/// Time of last metadata change | ||
ctime: SystemTime::now(), | ||
/// Time of creation (macOS only) | ||
crtime: SystemTime::now(), | ||
/// Kind of file (directory, file, pipe, etc.) | ||
kind: node_to_filetype(&node), | ||
/// Permissions | ||
perm: node.meta.mode.unwrap_or(0) as u16, | ||
/// Number of hard links | ||
nlink: node.meta.links.try_into().unwrap_or_default(), | ||
/// User ID | ||
uid: node.meta.uid.unwrap_or(0), | ||
/// Group ID | ||
gid: node.meta.gid.unwrap_or(0), | ||
/// Device ID (if special file) | ||
rdev: node_type_to_rdev(&node.node_type), | ||
/// Flags (macOS only; see chflags(2)) | ||
flags: 0, | ||
}, | ||
)) | ||
} | ||
|
||
fn open(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen { | ||
let node = self.node_from_path(path)?; | ||
let open = self.repo.open_file(&node).unwrap(); | ||
let fh = self | ||
.open_files | ||
.read() | ||
.unwrap() | ||
.first_key_value() | ||
.map(|(fh, _)| *fh) | ||
.unwrap_or(0); | ||
_ = self.open_files.write().unwrap().insert(fh, open); | ||
Ok((fh, 0)) | ||
} | ||
|
||
fn release( | ||
&self, | ||
_req: RequestInfo, | ||
_path: &Path, | ||
fh: u64, | ||
_flags: u32, | ||
_lock_owner: u64, | ||
_flush: bool, | ||
) -> ResultEmpty { | ||
_ = self.open_files.write().unwrap().remove(&fh); | ||
Ok(()) | ||
} | ||
|
||
fn read( | ||
&self, | ||
_req: RequestInfo, | ||
_path: &Path, | ||
fh: u64, | ||
offset: u64, | ||
size: u32, | ||
|
||
callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult, | ||
) -> CallbackResult { | ||
if let Some(open_file) = self.open_files.read().unwrap().get(&fh) { | ||
if let Ok(data) = | ||
self.repo | ||
.read_file_at(open_file, offset.try_into().unwrap(), size as usize) | ||
{ | ||
return callback(Ok(&data)); | ||
} | ||
} | ||
callback(Err(libc::ENOSYS)) | ||
} | ||
|
||
fn opendir(&self, _req: RequestInfo, _path: &Path, _flags: u32) -> ResultOpen { | ||
Ok((0, 0)) | ||
} | ||
|
||
fn readdir(&self, _req: RequestInfo, path: &Path, _fh: u64) -> ResultReaddir { | ||
let node = self | ||
.repo | ||
.node_from_path(self.root, path) | ||
.map_err(|_| libc::ENOSYS)?; | ||
|
||
let tree = self | ||
.repo | ||
.get_tree(&node.subtree.unwrap()) | ||
.map_err(|_| libc::ENOSYS)?; | ||
|
||
let result = tree | ||
.nodes | ||
.into_iter() | ||
.map(|node| DirectoryEntry { | ||
name: node.name(), | ||
kind: node_to_filetype(&node), | ||
}) | ||
.collect(); | ||
Ok(result) | ||
} | ||
|
||
fn releasedir(&self, _req: RequestInfo, _path: &Path, _fh: u64, _flags: u32) -> ResultEmpty { | ||
Ok(()) | ||
} | ||
} |