mirror of
https://github.com/rustic-rs/rustic.git
synced 2025-10-26 11:18:51 +00:00
Merge branch 'main' into feat/add-cross-toml
This commit is contained in:
commit
31005d4339
1
.github/workflows/nightly.yml
vendored
1
.github/workflows/nightly.yml
vendored
@ -112,7 +112,6 @@ jobs:
|
||||
sign-release: true
|
||||
hash-release: true
|
||||
use-project-version: true
|
||||
extra-cargo-build-args: --features release
|
||||
|
||||
publish-nightly:
|
||||
if: ${{ github.repository_owner == 'rustic-rs' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
2
.github/workflows/release-cd.yml
vendored
2
.github/workflows/release-cd.yml
vendored
@ -91,7 +91,6 @@ jobs:
|
||||
architecture: armv7
|
||||
binary-postfix: ""
|
||||
use-cross: true
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
@ -117,7 +116,6 @@ jobs:
|
||||
hash-release: true
|
||||
use-project-version: true
|
||||
use-tag-version: true # IMPORTANT: this is being used to make sure the tag that is built is in the archive filename, so automation can download the correct version
|
||||
extra-cargo-build-args: --features release
|
||||
|
||||
create-release:
|
||||
name: Creating release with artifacts
|
||||
|
||||
74
Cargo.lock
generated
74
Cargo.lock
generated
@ -109,7 +109,7 @@ dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1591,6 +1591,33 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fuse_mt"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e098b8dc4cd32e9ba31d9c8cdfef11271d8191233c64c2a671432ff19d354948"
|
||||
dependencies = [
|
||||
"fuser",
|
||||
"libc",
|
||||
"log",
|
||||
"threadpool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuser"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21370f84640642c8ea36dfb2a6bfc4c55941f476fcf431f6fef25a5ddcf0169b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"page_size",
|
||||
"pkg-config",
|
||||
"smallvec",
|
||||
"zerocopy 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
@ -2966,6 +2993,16 @@ version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b7663cbd190cfd818d08efa8497f6cd383076688c49a391ef7c0d03cd12b561"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pariter"
|
||||
version = "0.5.1"
|
||||
@ -3193,7 +3230,7 @@ version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3850,6 +3887,7 @@ dependencies = [
|
||||
"dircmp",
|
||||
"directories",
|
||||
"displaydoc",
|
||||
"fuse_mt",
|
||||
"gethostname",
|
||||
"globset",
|
||||
"human-panic",
|
||||
@ -4775,6 +4813,15 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpool"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
|
||||
dependencies = [
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
@ -5698,6 +5745,16 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
@ -5705,7 +5762,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -29,6 +29,7 @@ jemallocator = ["dep:jemallocator-global"]
|
||||
self-update = ["dep:self_update", "dep:semver"]
|
||||
tui = ["dep:ratatui", "dep:crossterm", "dep:tui-textarea"]
|
||||
webdav = ["dep:dav-server", "dep:warp", "dep:tokio", "rustic_core/webdav"]
|
||||
mount = ["dep:fuse_mt"]
|
||||
|
||||
[[bin]]
|
||||
name = "rustic"
|
||||
@ -96,6 +97,7 @@ dateparser = "0.2.1"
|
||||
derive_more = { version = "1", features = ["debug"] }
|
||||
dialoguer = "0.11.0"
|
||||
directories = "5"
|
||||
fuse_mt = { version = "0.6", optional = true }
|
||||
gethostname = "0.5"
|
||||
globset = "0.4.15"
|
||||
human-panic = "2"
|
||||
@ -123,6 +125,7 @@ toml = "0.8"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
libc = "0.2.159"
|
||||
|
||||
# cargo-binstall support
|
||||
# https://github.com/cargo-bins/cargo-binstall/blob/HEAD/SUPPORT.md
|
||||
[package.metadata.binstall]
|
||||
|
||||
40
build-dependencies.just
Normal file
40
build-dependencies.just
Normal file
@ -0,0 +1,40 @@
|
||||
### DEFAULT ###
|
||||
|
||||
# Install dependencies for the default feature on x86_64-unknown-linux-musl
|
||||
install-default-x86_64-unknown-linux-musl:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y musl-tools
|
||||
|
||||
### MOUNT ###
|
||||
|
||||
# Install dependencies for the mount feature on x86_64-unknown-linux-gnu
|
||||
install-mount-x86_64-unknown-linux-gnu:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y fuse3 libfuse3-dev pkg-config
|
||||
|
||||
# Install dependencies for the mount feature on aarch64-unknown-linux-gnu
|
||||
install-mount-aarch64-unknown-linux-gnu:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y fuse3 libfuse3-dev pkg-config
|
||||
|
||||
# Install dependencies for the mount feature on i686-unknown-linux-gnu
|
||||
install-mount-i686-unknown-linux-gnu:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y fuse3 libfuse3-dev pkg-config
|
||||
|
||||
# Install dependencies for the mount feature on x86_64-unknown-linux-musl
|
||||
install-mount-x86_64-unknown-linux-musl:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y fuse3 libfuse3-dev pkg-config
|
||||
|
||||
# Install dependencies for the mount feature on x86_64-apple-darwin
|
||||
install-mount-x86_64-apple-darwin:
|
||||
brew install macfuse
|
||||
|
||||
# Install dependencies for the mount feature on aarch64-apple-darwin
|
||||
install-mount-aarch64-apple-darwin:
|
||||
brew install macfuse
|
||||
|
||||
# Install dependencies for the mount feature on x86_64-pc-windows-msvc
|
||||
install-mount-x86_64-pc-windows-msvc:
|
||||
winget install winfsp
|
||||
@ -205,3 +205,11 @@ time-template = "%Y-%m-%d_%H-%M-%S" # only relevant if no snapshot-path is given
|
||||
symlinks = false
|
||||
file-access = "read" # Default: "forbidden" for hot/cold repos, else "read"
|
||||
snapshot-path = "latest:/dir" # Default: not set - if not set, generate a virtual tree with all snapshots using path-template
|
||||
|
||||
[mount]
|
||||
path-template = "[{hostname}]/[{label}]/{time}" # The path template to use for snapshots. {id}, {id_long}, {time}, {username}, {hostname}, {label}, {tags}, {backup_start}, {backup_end} are replaced. [default: "[{hostname}]/[{label}]/{time}"]. Only relevant if no snapshot-path is given.
|
||||
time-template = "%Y-%m-%d_%H-%M-%S" # only relevant if no snapshot-path is given
|
||||
no-allow-other = true
|
||||
file-access = "read" # Default: "forbidden" for hot/cold repos, else "read"
|
||||
mountpoint = "~/mnt"
|
||||
snapshot-path = "latest:/dir" # Default: not set - if not set, generate a virtual tree with all snapshots using path-template
|
||||
|
||||
@ -101,6 +101,7 @@ allow = [
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"ISC",
|
||||
"Unicode-DFS-2016",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"MPL-2.0",
|
||||
"OpenSSL",
|
||||
|
||||
10
platform-settings.toml
Normal file
10
platform-settings.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[platforms.defaults]
|
||||
release-features = [
|
||||
"release",
|
||||
]
|
||||
|
||||
# Check if 'build-dependencies.just' needs to be updated
|
||||
[platforms.x86_64-unknown-linux-gnu]
|
||||
additional-features = [
|
||||
"mount",
|
||||
]
|
||||
@ -16,6 +16,8 @@ pub(crate) mod key;
|
||||
pub(crate) mod list;
|
||||
pub(crate) mod ls;
|
||||
pub(crate) mod merge;
|
||||
#[cfg(feature = "mount")]
|
||||
pub(crate) mod mount;
|
||||
pub(crate) mod prune;
|
||||
pub(crate) mod repair;
|
||||
pub(crate) mod repoinfo;
|
||||
@ -34,6 +36,8 @@ use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "mount")]
|
||||
use crate::commands::mount::MountCmd;
|
||||
#[cfg(feature = "webdav")]
|
||||
use crate::commands::webdav::WebDavCmd;
|
||||
use crate::{
|
||||
@ -111,6 +115,10 @@ enum RusticCmd {
|
||||
/// List repository files by file type
|
||||
List(Box<ListCmd>),
|
||||
|
||||
#[cfg(feature = "mount")]
|
||||
/// Mount a repository as read-only filesystem
|
||||
Mount(Box<MountCmd>),
|
||||
|
||||
/// List file contents of a snapshot
|
||||
Ls(Box<LsCmd>),
|
||||
|
||||
@ -278,6 +286,8 @@ impl Configurable<RusticConfig> for EntryPoint {
|
||||
RusticCmd::Copy(cmd) => cmd.override_config(config),
|
||||
#[cfg(feature = "webdav")]
|
||||
RusticCmd::Webdav(cmd) => cmd.override_config(config),
|
||||
#[cfg(feature = "mount")]
|
||||
RusticCmd::Mount(cmd) => cmd.override_config(config),
|
||||
|
||||
// subcommands that don't need special overrides use a catch all
|
||||
_ => Ok(config),
|
||||
|
||||
148
src/commands/mount.rs
Normal file
148
src/commands/mount.rs
Normal file
@ -0,0 +1,148 @@
|
||||
//! `mount` subcommand
|
||||
|
||||
// ignore markdown clippy lints as we use doc-comments to generate clap help texts
|
||||
#![allow(clippy::doc_markdown)]
|
||||
|
||||
mod fusefs;
|
||||
use fusefs::FuseFS;
|
||||
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
|
||||
use crate::{repository::CliIndexedRepo, status_err, Application, RusticConfig, RUSTIC_APP};
|
||||
|
||||
use abscissa_core::{config::Override, Command, FrameworkError, Runnable, Shutdown};
|
||||
use anyhow::{anyhow, Result};
|
||||
use conflate::Merge;
|
||||
use fuse_mt::{mount, FuseMT};
|
||||
use rustic_core::vfs::{FilePolicy, IdenticalSnapshot, Latest, Vfs};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Command, Default, Debug, clap::Parser, Serialize, Deserialize, Merge)]
|
||||
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct MountCmd {
|
||||
/// The path template to use for snapshots. {id}, {id_long}, {time}, {username}, {hostname}, {label}, {tags}, {backup_start}, {backup_end} are replaced. [default: "[{hostname}]/[{label}]/{time}"]
|
||||
#[clap(long)]
|
||||
#[merge(strategy=conflate::option::overwrite_none)]
|
||||
path_template: Option<String>,
|
||||
|
||||
/// The time template to use to display times in the path template. See https://docs.rs/chrono/latest/chrono/format/strftime/index.html for format options. [default: "%Y-%m-%d_%H-%M-%S"]
|
||||
#[clap(long)]
|
||||
#[merge(strategy=conflate::option::overwrite_none)]
|
||||
time_template: Option<String>,
|
||||
|
||||
/// Don't allow other users to access the mount point
|
||||
#[clap(long)]
|
||||
#[merge(strategy=conflate::bool::overwrite_false)]
|
||||
no_allow_other: bool,
|
||||
|
||||
/// How to handle access to files. [default: "forbidden" for hot/cold repositories, else "read"]
|
||||
#[clap(long)]
|
||||
#[merge(strategy=conflate::option::overwrite_none)]
|
||||
file_access: Option<String>,
|
||||
|
||||
/// The mount point to use
|
||||
#[clap(value_name = "PATH")]
|
||||
#[merge(strategy=conflate::option::overwrite_none)]
|
||||
mountpoint: Option<PathBuf>,
|
||||
|
||||
/// Specify directly which snapshot/path to mount
|
||||
#[clap(value_name = "SNAPSHOT[:PATH]")]
|
||||
#[merge(strategy=conflate::option::overwrite_none)]
|
||||
snapshot_path: Option<String>,
|
||||
}
|
||||
|
||||
impl Override<RusticConfig> for MountCmd {
|
||||
// Process the given command line options, overriding settings from
|
||||
// a configuration file using explicit flags taken from command-line
|
||||
// arguments.
|
||||
fn override_config(&self, mut config: RusticConfig) -> Result<RusticConfig, FrameworkError> {
|
||||
let mut self_config = self.clone();
|
||||
// merge "mount" section from config file, if given
|
||||
self_config.merge(config.mount);
|
||||
config.mount = self_config;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
impl Runnable for MountCmd {
|
||||
fn run(&self) {
|
||||
if let Err(err) = RUSTIC_APP
|
||||
.config()
|
||||
.repository
|
||||
.run_indexed(|repo| self.inner_run(repo))
|
||||
{
|
||||
status_err!("{}", err);
|
||||
RUSTIC_APP.shutdown(Shutdown::Crash);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl MountCmd {
|
||||
fn inner_run(&self, repo: CliIndexedRepo) -> Result<()> {
|
||||
let config = RUSTIC_APP.config();
|
||||
let mountpoint = config
|
||||
.mount
|
||||
.mountpoint
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("please specify a mountpoint"))?;
|
||||
|
||||
let path_template = config
|
||||
.mount
|
||||
.path_template
|
||||
.clone()
|
||||
.unwrap_or_else(|| "[{hostname}]/[{label}]/{time}".to_string());
|
||||
let time_template = config
|
||||
.mount
|
||||
.time_template
|
||||
.clone()
|
||||
.unwrap_or_else(|| "%Y-%m-%d_%H-%M-%S".to_string());
|
||||
|
||||
let sn_filter = |sn: &_| config.snapshot_filter.matches(sn);
|
||||
let vfs = if let Some(snap_path) = &config.mount.snapshot_path {
|
||||
let node = repo.node_from_snapshot_path(snap_path, sn_filter)?;
|
||||
Vfs::from_dir_node(&node)
|
||||
} else {
|
||||
let snapshots = repo.get_matching_snapshots(sn_filter)?;
|
||||
Vfs::from_snapshots(
|
||||
snapshots,
|
||||
&path_template,
|
||||
&time_template,
|
||||
Latest::AsLink,
|
||||
IdenticalSnapshot::AsLink,
|
||||
)?
|
||||
};
|
||||
|
||||
let name_opt = format!("fsname=rusticfs:{}", repo.config().id);
|
||||
let mut options = vec![
|
||||
OsStr::new("-o"),
|
||||
OsStr::new(&name_opt),
|
||||
OsStr::new("-o"),
|
||||
OsStr::new("kernel_cache"),
|
||||
];
|
||||
|
||||
if !config.mount.no_allow_other {
|
||||
options.extend_from_slice(&[
|
||||
OsStr::new("-o"),
|
||||
OsStr::new("allow_other"),
|
||||
OsStr::new("-o"),
|
||||
OsStr::new("default_permissions"),
|
||||
]);
|
||||
}
|
||||
|
||||
let file_access = config.mount.file_access.as_ref().map_or_else(
|
||||
|| {
|
||||
if repo.config().is_hot == Some(true) {
|
||||
Ok(FilePolicy::Forbidden)
|
||||
} else {
|
||||
Ok(FilePolicy::Read)
|
||||
}
|
||||
},
|
||||
|s| s.parse(),
|
||||
)?;
|
||||
|
||||
let fs = FuseMT::new(FuseFS::new(repo, vfs, file_access), 1);
|
||||
mount(fs, mountpoint, &options)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
238
src/commands/mount/fusefs.rs
Normal file
238
src/commands/mount/fusefs.rs
Normal file
@ -0,0 +1,238 @@
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::prelude::OsStrExt;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
ffi::{CString, OsStr},
|
||||
path::Path,
|
||||
sync::RwLock,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use rustic_core::{
|
||||
repofile::{Node, NodeType},
|
||||
vfs::{FilePolicy, OpenFile, Vfs},
|
||||
IndexedFull, Repository,
|
||||
};
|
||||
|
||||
use fuse_mt::{
|
||||
CallbackResult, DirectoryEntry, FileAttr, FileType, FilesystemMT, RequestInfo, ResultData,
|
||||
ResultEmpty, ResultEntry, ResultOpen, ResultReaddir, ResultSlice, ResultXattr, Xattr,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub struct FuseFS<P, S> {
|
||||
repo: Repository<P, S>,
|
||||
vfs: Vfs,
|
||||
open_files: RwLock<BTreeMap<u64, OpenFile>>,
|
||||
now: SystemTime,
|
||||
file_policy: FilePolicy,
|
||||
}
|
||||
|
||||
impl<P, S: IndexedFull> FuseFS<P, S> {
|
||||
pub(crate) fn new(repo: Repository<P, S>, vfs: Vfs, file_policy: FilePolicy) -> Self {
|
||||
let open_files = RwLock::new(BTreeMap::new());
|
||||
|
||||
Self {
|
||||
repo,
|
||||
vfs,
|
||||
open_files,
|
||||
now: SystemTime::now(),
|
||||
file_policy,
|
||||
}
|
||||
}
|
||||
|
||||
fn node_from_path(&self, path: &Path) -> Result<Node, i32> {
|
||||
self.vfs
|
||||
.node_from_path(&self.repo, path)
|
||||
.map_err(|_| libc::ENOENT)
|
||||
}
|
||||
|
||||
fn dir_entries_from_path(&self, path: &Path) -> Result<Vec<Node>, i32> {
|
||||
self.vfs
|
||||
.dir_entries_from_path(&self.repo, path)
|
||||
.map_err(|_| libc::ENOENT)
|
||||
}
|
||||
}
|
||||
|
||||
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 } | NodeType::Chardev { device } => *device,
|
||||
_ => 0,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn node_to_linktarget(node: &Node) -> Option<&OsStr> {
|
||||
if node.is_symlink() {
|
||||
Some(node.node_type.to_link().as_os_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn node_to_file_attr(node: &Node, now: SystemTime) -> FileAttr {
|
||||
FileAttr {
|
||||
// Size in bytes
|
||||
size: node.meta.size,
|
||||
// Size in blocks
|
||||
blocks: 0,
|
||||
// Time of last access
|
||||
atime: node.meta.atime.map(SystemTime::from).unwrap_or(now),
|
||||
// Time of last modification
|
||||
mtime: node.meta.mtime.map(SystemTime::from).unwrap_or(now),
|
||||
// Time of last metadata change
|
||||
ctime: node.meta.ctime.map(SystemTime::from).unwrap_or(now),
|
||||
// Time of creation (macOS only)
|
||||
crtime: now,
|
||||
// Kind of file (directory, file, pipe, etc.)
|
||||
kind: node_to_filetype(node),
|
||||
// Permissions
|
||||
perm: node.meta.mode.unwrap_or(0o755) as u16,
|
||||
// Number of hard links
|
||||
nlink: node.meta.links.try_into().unwrap_or(1),
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, S: IndexedFull> FilesystemMT for FuseFS<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), node_to_file_attr(&node, self.now)))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn readlink(&self, _req: RequestInfo, path: &Path) -> ResultData {
|
||||
let target = node_to_linktarget(&self.node_from_path(path)?)
|
||||
.ok_or(libc::ENOSYS)?
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
fn open(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen {
|
||||
if matches!(self.file_policy, FilePolicy::Forbidden) {
|
||||
return Err(libc::ENOTSUP);
|
||||
}
|
||||
let node = self.node_from_path(path)?;
|
||||
let open = self.repo.open_file(&node).map_err(|_| libc::ENOSYS)?;
|
||||
let fh = {
|
||||
let mut open_files = self.open_files.write().unwrap();
|
||||
let fh = open_files.last_key_value().map_or(0, |(fh, _)| *fh + 1);
|
||||
_ = open_files.insert(fh, open);
|
||||
fh
|
||||
};
|
||||
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 nodes = self.dir_entries_from_path(path)?;
|
||||
|
||||
let result = 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(())
|
||||
}
|
||||
|
||||
fn listxattr(&self, _req: RequestInfo, path: &Path, size: u32) -> ResultXattr {
|
||||
let node = self.node_from_path(path)?;
|
||||
let xattrs = node
|
||||
.meta
|
||||
.extended_attributes
|
||||
.into_iter()
|
||||
// convert into null-terminated [u8]
|
||||
.map(|a| CString::new(a.name).unwrap().into_bytes_with_nul())
|
||||
.concat();
|
||||
|
||||
if size == 0 {
|
||||
Ok(Xattr::Size(u32::try_from(xattrs.len()).unwrap()))
|
||||
} else {
|
||||
Ok(Xattr::Data(xattrs))
|
||||
}
|
||||
}
|
||||
|
||||
fn getxattr(&self, _req: RequestInfo, path: &Path, name: &OsStr, size: u32) -> ResultXattr {
|
||||
let node = self.node_from_path(path)?;
|
||||
match node
|
||||
.meta
|
||||
.extended_attributes
|
||||
.into_iter()
|
||||
.find(|a| name == OsStr::new(&a.name))
|
||||
{
|
||||
None => Err(libc::ENOSYS),
|
||||
Some(attr) => {
|
||||
let value = attr.value.unwrap_or_default();
|
||||
if size == 0 {
|
||||
Ok(Xattr::Size(u32::try_from(value.len()).unwrap()))
|
||||
} else {
|
||||
Ok(Xattr::Data(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
// ignore markdown clippy lints as we use doc-comments to generate clap help texts
|
||||
#![allow(clippy::doc_markdown)]
|
||||
|
||||
use std::{net::ToSocketAddrs, str::FromStr};
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
use crate::{repository::CliIndexedRepo, status_err, Application, RusticConfig, RUSTIC_APP};
|
||||
use abscissa_core::{config::Override, Command, FrameworkError, Runnable, Shutdown};
|
||||
@ -124,7 +124,7 @@ impl WebDavCmd {
|
||||
Ok(FilePolicy::Read)
|
||||
}
|
||||
},
|
||||
|s| FilePolicy::from_str(s),
|
||||
|s| s.parse(),
|
||||
)?;
|
||||
|
||||
let dav_server = DavHandler::builder()
|
||||
|
||||
@ -18,7 +18,11 @@ use directories::ProjectDirs;
|
||||
use itertools::Itertools;
|
||||
use log::Level;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(all(feature = "mount", feature = "webdav")))]
|
||||
use toml::Value;
|
||||
|
||||
#[cfg(feature = "mount")]
|
||||
use crate::commands::mount::MountCmd;
|
||||
#[cfg(feature = "webdav")]
|
||||
use crate::commands::webdav::WebDavCmd;
|
||||
|
||||
@ -62,10 +66,21 @@ pub struct RusticConfig {
|
||||
#[clap(skip)]
|
||||
pub forget: ForgetOptions,
|
||||
|
||||
#[cfg(feature = "webdav")]
|
||||
/// mount options
|
||||
#[clap(skip)]
|
||||
#[cfg(feature = "mount")]
|
||||
pub mount: MountCmd,
|
||||
#[cfg(not(feature = "mount"))]
|
||||
#[merge(skip)]
|
||||
pub mount: Option<Value>,
|
||||
|
||||
/// webdav options
|
||||
#[clap(skip)]
|
||||
#[cfg(feature = "webdav")]
|
||||
pub webdav: WebDavCmd,
|
||||
#[cfg(not(feature = "webdav"))]
|
||||
#[merge(skip)]
|
||||
pub webdav: Option<Value>,
|
||||
}
|
||||
|
||||
impl RusticConfig {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user