Skip to content

Commit

Permalink
Add mount command
Browse files Browse the repository at this point in the history
  • Loading branch information
aawsome committed Dec 26, 2023
1 parent 16ea6de commit 7adcdfe
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ merge = { workspace = true }
bytesize = { workspace = true }
comfy-table = { workspace = true }
dialoguer = { workspace = true }
fuse_mt = { workspace = true }
directories = { workspace = true }
gethostname = { workspace = true }
humantime = { workspace = true }
Expand Down Expand Up @@ -128,6 +129,7 @@ comfy-table = "7.1.0"
merge = "0.1"
directories = "5"
dialoguer = "0.11.0"
fuse_mt = "0.6"
indicatif = "0.17"
gethostname = "0.4"
bytesize = "1"
Expand Down
11 changes: 8 additions & 3 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub(crate) mod key;
pub(crate) mod list;
pub(crate) mod ls;
pub(crate) mod merge;
pub(crate) mod mount;
pub(crate) mod prune;
pub(crate) mod repair;
pub(crate) mod repoinfo;
Expand All @@ -32,9 +33,10 @@ use crate::{
commands::{
backup::BackupCmd, cat::CatCmd, check::CheckCmd, completions::CompletionsCmd,
config::ConfigCmd, copy::CopyCmd, diff::DiffCmd, dump::DumpCmd, forget::ForgetCmd,
init::InitCmd, key::KeyCmd, list::ListCmd, ls::LsCmd, merge::MergeCmd, prune::PruneCmd,
repair::RepairCmd, repoinfo::RepoInfoCmd, restore::RestoreCmd, self_update::SelfUpdateCmd,
show_config::ShowConfigCmd, snapshots::SnapshotCmd, tag::TagCmd,
init::InitCmd, key::KeyCmd, list::ListCmd, ls::LsCmd, merge::MergeCmd, mount::MountCmd,
prune::PruneCmd, repair::RepairCmd, repoinfo::RepoInfoCmd, restore::RestoreCmd,
self_update::SelfUpdateCmd, show_config::ShowConfigCmd, snapshots::SnapshotCmd,
tag::TagCmd,
},
config::{progress_options::ProgressOptions, RusticConfig},
{Application, RUSTIC_APP},
Expand Down Expand Up @@ -92,6 +94,9 @@ enum RusticCmd {
/// Manage keys
Key(KeyCmd),

/// Mount repository
Mount(MountCmd),

/// List repository files
List(ListCmd),

Expand Down
47 changes: 47 additions & 0 deletions src/commands/mount.rs
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(())
}
}
172 changes: 172 additions & 0 deletions src/commands/mount/fs.rs
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(())
}
}

0 comments on commit 7adcdfe

Please sign in to comment.