mirror of
https://github.com/house-of-vanity/furumi.git
synced 2025-07-06 21:24:08 +00:00
init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
12
.idea/http-fuse.iml
generated
Normal file
12
.idea/http-fuse.iml
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" project-jdk-name="14" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/http-fuse.iml" filepath="$PROJECT_DIR$/.idea/http-fuse.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
16
.idea/runConfigurations/Run_furumi.xml
generated
Normal file
16
.idea/runConfigurations/Run_furumi.xml
generated
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run furumi" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
|
<option name="channel" value="STABLE" />
|
||||||
|
<option name="command" value="run --package furumi --bin furumi -- --conf /etc/mus-fuse.yml" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="nocapture" value="false" />
|
||||||
|
<option name="emulateTerminal" value="false" />
|
||||||
|
<option name="backtrace" value="SHORT" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<envs />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
<option name="ToolBeforeRunTask" enabled="true" actionId="Tool_External Tools_fusermount -u mnt" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
46
.vscode/launch.json
vendored
Normal file
46
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"env": {
|
||||||
|
"RUST_BACKTRACE": "1",
|
||||||
|
},
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug executable 'furumi'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"--",
|
||||||
|
"--conf",
|
||||||
|
"/etc/mus-fuse.yml"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"postDebugTask": "Umount FUSE",
|
||||||
|
"preLaunchTask": "Umount FUSE",
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'furumi'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--bin=furumi",
|
||||||
|
"--package=furumi"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "furumi",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"lldb.displayFormat": "auto",
|
||||||
|
"lldb.showDisassembly": "never",
|
||||||
|
"lldb.dereferencePointers": true
|
||||||
|
}
|
14
.vscode/tasks.json
vendored
Normal file
14
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Umount FUSE",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "fusermount -u mnt",
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1845
Cargo.lock
generated
Normal file
1845
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
Normal file
40
Cargo.toml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[package]
|
||||||
|
name = "furumi"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["AB <ultradesu@hexor.ru>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
polyfuse = "0.3.3"
|
||||||
|
polyfuse-tokio = "0.2"
|
||||||
|
tracing-subscriber = "0.2.5"
|
||||||
|
libc = "0.2.69"
|
||||||
|
anyhow = "1"
|
||||||
|
slab = "0.4"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-futures = "0.2"
|
||||||
|
futures = "0.3"
|
||||||
|
futures-intrusive = "0.2"
|
||||||
|
|
||||||
|
# config deps
|
||||||
|
reqwest = { version = "0.10", features = ["json", "blocking"] }
|
||||||
|
tokio = { version = "0.2", features = ["full"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
clap = {version = "2.33", features = ["yaml"]}
|
||||||
|
serde_json = "1.0"
|
||||||
|
percent-encoding = "2.1"
|
||||||
|
time = "0.1"
|
||||||
|
chrono = "0.4"
|
||||||
|
env_logger = "0.7"
|
||||||
|
log = { version = "^0.4.5", features = ["std"] }
|
||||||
|
size_format = "1.0"
|
||||||
|
base64 = "0.12"
|
||||||
|
ctrlc = "3.1"
|
||||||
|
config = "0.9"
|
||||||
|
|
||||||
|
[dev-dependencies.tokio]
|
||||||
|
version = "0.2"
|
||||||
|
features = [ "full" ]
|
||||||
|
|
3
assets/umount.sh
Normal file
3
assets/umount.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
fusermount -u "$1" || true
|
87
src/config.rs
Normal file
87
src/config.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use clap::{App, Arg};
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
extern crate chrono;
|
||||||
|
extern crate config;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, PartialEq)]
|
||||||
|
pub struct Config {
|
||||||
|
pub server: String,
|
||||||
|
pub mountpoint: String,
|
||||||
|
pub username: Option<String>,
|
||||||
|
pub password: Option<String>,
|
||||||
|
pub conf_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn read() -> Config {
|
||||||
|
// Parse opts and args
|
||||||
|
let cli_args = App::new(env!("CARGO_PKG_NAME"))
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.author(env!("CARGO_PKG_AUTHORS"))
|
||||||
|
.about(env!("CARGO_PKG_DESCRIPTION"))
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("conf")
|
||||||
|
.short("c")
|
||||||
|
.long("conf")
|
||||||
|
.help("Config file to use")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
info!("Logger initialized. Set RUST_LOG=[debug,error,info,warn,trace] Default: info");
|
||||||
|
info!("Starting {} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
|
// Read config file and env vars
|
||||||
|
let config_file = cli_args.value_of("conf").unwrap();
|
||||||
|
let mut settings = config::Config::default();
|
||||||
|
settings = match settings.merge(config::File::with_name(config_file)) {
|
||||||
|
Ok(conf_content) => {
|
||||||
|
info!("Using config file {}", config_file);
|
||||||
|
conf_content.to_owned()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Can't read config file - {}", e);
|
||||||
|
process::exit(0x0001);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let server = match settings.get_str("server") {
|
||||||
|
Ok(server) => server,
|
||||||
|
Err(_) => {
|
||||||
|
error!("Server is not set in config. Set `server` directive.");
|
||||||
|
process::exit(0x0002);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mountpoint = match settings.get_str("mountpoint") {
|
||||||
|
Ok(mountpoint) => mountpoint,
|
||||||
|
Err(_) => {
|
||||||
|
error!("Mountpoint is not set in config. Set `mountpoint` directive.");
|
||||||
|
process::exit(0x0003);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let username = match settings.get_str("username") {
|
||||||
|
Ok(username) => Some(username),
|
||||||
|
Err(_) => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let password = match settings.get_str("password") {
|
||||||
|
Ok(password) => Some(password),
|
||||||
|
Err(_) => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if password == None || username == None {
|
||||||
|
warn!("Insecure server detected. Set `username` and `password` directives to use auth.");
|
||||||
|
}
|
||||||
|
Config {
|
||||||
|
server: server,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
mountpoint: mountpoint,
|
||||||
|
conf_file: config_file.to_string(),
|
||||||
|
}
|
||||||
|
}
|
945
src/filesystem.rs
Normal file
945
src/filesystem.rs
Normal file
@ -0,0 +1,945 @@
|
|||||||
|
#![allow(clippy::unnecessary_mut_passed)]
|
||||||
|
#![deny(clippy::unimplemented)]
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
|
use crate::http;
|
||||||
|
|
||||||
|
|
||||||
|
use polyfuse::{
|
||||||
|
io::{Reader, Writer},
|
||||||
|
op,
|
||||||
|
reply::{Collector, Reply, ReplyAttr, ReplyEntry, ReplyOpen, ReplyWrite, ReplyXattr},
|
||||||
|
Context, DirEntry, FileAttr, Filesystem, Forget, Operation,
|
||||||
|
};
|
||||||
|
use slab::Slab;
|
||||||
|
use std::{
|
||||||
|
collections::hash_map::{Entry, HashMap},
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
fmt::Debug,
|
||||||
|
io,
|
||||||
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tracing_futures::Instrument;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
||||||
|
type Ino = u64;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct INodeTable {
|
||||||
|
map: HashMap<Ino, Arc<Mutex<INode>>>,
|
||||||
|
next_ino: Ino,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl INodeTable {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
map: HashMap::new(),
|
||||||
|
next_ino: 1, // a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vacant_entry(&mut self) -> VacantEntry<'_> {
|
||||||
|
let ino = self.next_ino;
|
||||||
|
VacantEntry { table: self, ino }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, ino: Ino) -> Option<Arc<Mutex<INode>>> {
|
||||||
|
self.map.get(&ino).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, ino: Ino) -> Option<Arc<Mutex<INode>>> {
|
||||||
|
self.map.remove(&ino)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct VacantEntry<'a> {
|
||||||
|
table: &'a mut INodeTable,
|
||||||
|
ino: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VacantEntry<'_> {
|
||||||
|
fn ino(&self) -> Ino {
|
||||||
|
self.ino
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(self, inode: INode) {
|
||||||
|
let Self { table, ino } = self;
|
||||||
|
table.map.insert(ino, Arc::new(Mutex::new(inode)));
|
||||||
|
table.next_ino += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct INode {
|
||||||
|
attr: FileAttr,
|
||||||
|
xattrs: HashMap<OsString, Arc<Vec<u8>>>,
|
||||||
|
refcount: u64,
|
||||||
|
links: u64,
|
||||||
|
kind: INodeKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum INodeKind {
|
||||||
|
RegularFile(Vec<u8>),
|
||||||
|
Directory(Directory),
|
||||||
|
Symlink(Arc<OsString>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Directory {
|
||||||
|
children: HashMap<OsString, Ino>,
|
||||||
|
parent: Option<Ino>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Directory {
|
||||||
|
fn collect_entries(&self, attr: &FileAttr) -> Vec<Arc<DirEntry>> {
|
||||||
|
let mut entries = Vec::with_capacity(self.children.len() + 2);
|
||||||
|
let mut offset: u64 = 1;
|
||||||
|
|
||||||
|
entries.push(Arc::new(DirEntry::dir(".", attr.ino(), offset)));
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
entries.push(Arc::new(DirEntry::dir(
|
||||||
|
"..",
|
||||||
|
self.parent.unwrap_or_else(|| attr.ino()),
|
||||||
|
offset,
|
||||||
|
)));
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
for (name, &ino) in &self.children {
|
||||||
|
entries.push(Arc::new(DirEntry::new(name, ino, offset)));
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DirHandle {
|
||||||
|
entries: Vec<Arc<DirEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MemFS {
|
||||||
|
inodes: Mutex<INodeTable>,
|
||||||
|
ttl: Duration,
|
||||||
|
dir_handles: Mutex<Slab<Arc<Mutex<DirHandle>>>>,
|
||||||
|
cfg: config::Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemFS {
|
||||||
|
pub fn new(cfg: &config::Config) -> Self {
|
||||||
|
let mut inodes = INodeTable::new();
|
||||||
|
//let self.cfg = cfg;
|
||||||
|
//let entries = http::list_directory(&cfg.server, &cfg.username, &cfg.password, "/").await;
|
||||||
|
inodes.vacant_entry().insert(INode {
|
||||||
|
attr: {
|
||||||
|
let mut attr = FileAttr::default();
|
||||||
|
attr.set_ino(1);
|
||||||
|
attr.set_nlink(2);
|
||||||
|
attr.set_mode(libc::S_IFDIR | 0o755);
|
||||||
|
attr
|
||||||
|
},
|
||||||
|
xattrs: HashMap::new(),
|
||||||
|
refcount: u64::max_value() / 2,
|
||||||
|
links: u64::max_value() / 2,
|
||||||
|
kind: INodeKind::Directory(Directory {
|
||||||
|
children: HashMap::new(),
|
||||||
|
parent: None,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inodes: Mutex::new(inodes),
|
||||||
|
dir_handles: Mutex::default(),
|
||||||
|
ttl: Duration::from_secs(60 * 60 * 24),
|
||||||
|
cfg: cfg.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_entry_reply(&self, ino: Ino, attr: FileAttr) -> ReplyEntry {
|
||||||
|
let mut reply = ReplyEntry::default();
|
||||||
|
reply.ino(ino);
|
||||||
|
reply.attr(attr);
|
||||||
|
reply.ttl_entry(self.ttl);
|
||||||
|
reply
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn lookup_inode(&self, parent: Ino, name: &OsStr) -> io::Result<ReplyEntry> {
|
||||||
|
// warn!("==> lookup_inode: parent: {:?}, name: {:?}", parent, name);
|
||||||
|
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let parent = inodes.get(parent).ok_or_else(no_entry)?;
|
||||||
|
let parent = parent.lock().await;
|
||||||
|
|
||||||
|
let parent = match parent.kind {
|
||||||
|
INodeKind::Directory(ref dir) => dir,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let child_ino = parent.children.get(&*name).copied().ok_or_else(no_entry)?;
|
||||||
|
let child = inodes.get(child_ino).unwrap_or_else(|| unreachable!());
|
||||||
|
let mut child = child.lock().await;
|
||||||
|
child.refcount += 1;
|
||||||
|
// warn!("==> lookup_inode: child_ino: {:?}, child.attr: {:?}", child_ino, child.attr);
|
||||||
|
Ok(self.make_entry_reply(child_ino, child.attr))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn make_node<F>(&self, parent: Ino, name: &OsStr, f: F) -> io::Result<ReplyEntry>
|
||||||
|
where
|
||||||
|
F: FnOnce(&VacantEntry<'_>) -> INode,
|
||||||
|
{
|
||||||
|
// debug!("make_node: parent: {:?}, name: {:?}", parent, name);
|
||||||
|
let mut inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let parent = inodes.get(parent).ok_or_else(no_entry)?;
|
||||||
|
let mut parent = parent.lock().await;
|
||||||
|
|
||||||
|
let parent = match parent.kind {
|
||||||
|
INodeKind::Directory(ref mut dir) => dir,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
|
||||||
|
};
|
||||||
|
match parent.children.entry(name.into()) {
|
||||||
|
Entry::Occupied(..) => Err(io::Error::from_raw_os_error(libc::EEXIST)),
|
||||||
|
Entry::Vacant(map_entry) => {
|
||||||
|
let inode_entry = inodes.vacant_entry();
|
||||||
|
let inode = f(&inode_entry);
|
||||||
|
|
||||||
|
let reply = self.make_entry_reply(inode_entry.ino(), inode.attr);
|
||||||
|
map_entry.insert(inode_entry.ino());
|
||||||
|
inode_entry.insert(inode);
|
||||||
|
|
||||||
|
Ok(reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn link_node(&self, ino: u64, newparent: Ino, newname: &OsStr) -> io::Result<ReplyEntry> {
|
||||||
|
{
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let inode = inodes.get(ino).ok_or_else(no_entry)?;
|
||||||
|
|
||||||
|
let newparent = inodes.get(newparent).ok_or_else(no_entry)?;
|
||||||
|
let mut newparent = newparent.lock().await;
|
||||||
|
let newparent = match newparent.kind {
|
||||||
|
INodeKind::Directory(ref mut dir) => dir,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match newparent.children.entry(newname.into()) {
|
||||||
|
Entry::Occupied(..) => return Err(io::Error::from_raw_os_error(libc::EEXIST)),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(ino);
|
||||||
|
let mut inode = inode.lock().await;
|
||||||
|
let inode = &mut *inode;
|
||||||
|
inode.links += 1;
|
||||||
|
inode.attr.set_nlink(inode.attr.nlink() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lookup_inode(newparent, newname).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unlink_node(&self, parent: Ino, name: &OsStr) -> io::Result<()> {
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let parent = inodes.get(parent).ok_or_else(no_entry)?;
|
||||||
|
let mut parent = parent.lock().await;
|
||||||
|
let parent = match parent.kind {
|
||||||
|
INodeKind::Directory(ref mut dir) => dir,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let &ino = parent.children.get(name).ok_or_else(no_entry)?;
|
||||||
|
|
||||||
|
let inode = inodes.get(ino).unwrap_or_else(|| unreachable!());
|
||||||
|
let mut inode = inode.lock().await;
|
||||||
|
let inode = &mut *inode;
|
||||||
|
|
||||||
|
match inode.kind {
|
||||||
|
INodeKind::Directory(ref dir) if !dir.children.is_empty() => {
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::ENOTEMPTY));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
parent
|
||||||
|
.children
|
||||||
|
.remove(name)
|
||||||
|
.unwrap_or_else(|| unreachable!());
|
||||||
|
|
||||||
|
inode.links = inode.links.saturating_sub(1);
|
||||||
|
inode.attr.set_nlink(inode.attr.nlink().saturating_sub(1));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn rename_node(&self, parent: Ino, name: &OsStr, newname: &OsStr) -> io::Result<()> {
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let parent = inodes.get(parent).ok_or_else(no_entry)?;
|
||||||
|
let mut parent = parent.lock().await;
|
||||||
|
let parent = match parent.kind {
|
||||||
|
INodeKind::Directory(ref mut dir) => dir,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let &ino = parent.children.get(name).ok_or_else(no_entry)?;
|
||||||
|
|
||||||
|
match parent.children.entry(newname.into()) {
|
||||||
|
Entry::Occupied(..) => Err(io::Error::from_raw_os_error(libc::EEXIST)),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(ino);
|
||||||
|
parent
|
||||||
|
.children
|
||||||
|
.remove(name)
|
||||||
|
.unwrap_or_else(|| unreachable!());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn graft_node(
|
||||||
|
&self,
|
||||||
|
parent: Ino,
|
||||||
|
name: &OsStr,
|
||||||
|
newparent: Ino,
|
||||||
|
newname: &OsStr,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
debug_assert_ne!(parent, newparent);
|
||||||
|
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let parent = inodes.get(parent).ok_or_else(no_entry)?;
|
||||||
|
let mut parent = parent.lock().await;
|
||||||
|
let parent = match parent.kind {
|
||||||
|
INodeKind::Directory(ref mut dir) => dir,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let newparent = inodes.get(newparent).ok_or_else(no_entry)?;
|
||||||
|
let mut newparent = newparent.lock().await;
|
||||||
|
let newparent = match newparent.kind {
|
||||||
|
INodeKind::Directory(ref mut dir) => dir,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match newparent.children.entry(newname.into()) {
|
||||||
|
Entry::Occupied(..) => Err(io::Error::from_raw_os_error(libc::EEXIST)),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
let ino = parent.children.remove(name).ok_or_else(no_entry)?;
|
||||||
|
entry.insert(ino);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_lookup(&self, op: &op::Lookup<'_>) -> io::Result<ReplyEntry> {
|
||||||
|
// warn!("do_lookup: op: {:?}", op);
|
||||||
|
let gg = self.lookup_inode(op.parent(), op.name()).await;
|
||||||
|
// warn!("===> do_lookup: lookup_inode: {:?}", gg);
|
||||||
|
gg
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_forget(&self, forgets: &[Forget]) {
|
||||||
|
let mut inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
for forget in forgets {
|
||||||
|
if let Some(inode) = inodes.get(forget.ino()) {
|
||||||
|
let mut inode = inode.lock().await;
|
||||||
|
inode.refcount = inode.refcount.saturating_sub(forget.nlookup());
|
||||||
|
if inode.refcount == 0 && inode.links == 0 {
|
||||||
|
inodes.remove(forget.ino());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_getattr(&self, op: &op::Getattr<'_>) -> io::Result<ReplyAttr> {
|
||||||
|
// warn!("do_getattr: op: {:?}", op);
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let inode = inode.lock().await;
|
||||||
|
|
||||||
|
let mut reply = ReplyAttr::new(inode.attr);
|
||||||
|
reply.ttl_attr(self.ttl);
|
||||||
|
|
||||||
|
Ok(reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_setattr(&self, op: &op::Setattr<'_>) -> io::Result<ReplyAttr> {
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let mut inode = inode.lock().await;
|
||||||
|
|
||||||
|
if let Some(mode) = op.mode() {
|
||||||
|
inode.attr.set_mode(mode);
|
||||||
|
}
|
||||||
|
if let Some(uid) = op.uid() {
|
||||||
|
inode.attr.set_uid(uid);
|
||||||
|
}
|
||||||
|
if let Some(gid) = op.gid() {
|
||||||
|
inode.attr.set_gid(gid);
|
||||||
|
}
|
||||||
|
if let Some(size) = op.size() {
|
||||||
|
inode.attr.set_size(size);
|
||||||
|
}
|
||||||
|
if let Some(atime) = op.atime() {
|
||||||
|
inode.attr.set_atime(atime);
|
||||||
|
}
|
||||||
|
if let Some(mtime) = op.mtime() {
|
||||||
|
inode.attr.set_mtime(mtime);
|
||||||
|
}
|
||||||
|
if let Some(ctime) = op.ctime() {
|
||||||
|
inode.attr.set_ctime(ctime);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reply = ReplyAttr::new(inode.attr);
|
||||||
|
reply.ttl_attr(self.ttl);
|
||||||
|
|
||||||
|
Ok(reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_readlink(&self, op: &op::Readlink<'_>) -> io::Result<Arc<OsString>> {
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let inode = inode.lock().await;
|
||||||
|
|
||||||
|
match inode.kind {
|
||||||
|
INodeKind::Symlink(ref link) => Ok(link.clone()),
|
||||||
|
_ => Err(io::Error::from_raw_os_error(libc::EINVAL)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_remote(&self, path: &Path, parent: u64) -> io::Result<()> {
|
||||||
|
let remote_entries = http::list_directory(
|
||||||
|
&self.cfg.server, &self.cfg.username, &self.cfg.password, path).await.unwrap();
|
||||||
|
for r_entry in remote_entries.iter(){
|
||||||
|
match &r_entry.r#type {
|
||||||
|
Some(r#type) => {
|
||||||
|
match r#type.as_str() {
|
||||||
|
"file" => {
|
||||||
|
let f_name = r_entry.name.as_ref().unwrap();
|
||||||
|
self.make_node(
|
||||||
|
parent, OsStr::new(f_name.as_str()), |entry| {
|
||||||
|
INode {
|
||||||
|
attr: {
|
||||||
|
// debug!("Adding file {:?} - {:?}", f_name, entry);
|
||||||
|
let mut attr = FileAttr::default();
|
||||||
|
attr.set_ino(entry.ino());
|
||||||
|
attr.set_mtime(r_entry.parse_rfc2822());
|
||||||
|
attr.set_size(r_entry.size.unwrap());
|
||||||
|
attr.set_nlink(1);
|
||||||
|
attr.set_mode(libc::S_IFREG | 0o444);
|
||||||
|
attr
|
||||||
|
},
|
||||||
|
xattrs: HashMap::new(),
|
||||||
|
refcount: 1,
|
||||||
|
links: 1,
|
||||||
|
kind: INodeKind::RegularFile(vec![]),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
"directory" => {
|
||||||
|
let f_name = r_entry.name.as_ref().unwrap();
|
||||||
|
self.make_node(
|
||||||
|
parent, OsStr::new(f_name.as_str()), |entry| {
|
||||||
|
INode {
|
||||||
|
attr: {
|
||||||
|
// debug!("Adding directory {:?} - {:?}", f_name, entry);
|
||||||
|
let mut attr = FileAttr::default();
|
||||||
|
attr.set_ino(entry.ino());
|
||||||
|
attr.set_mtime(r_entry.parse_rfc2822());
|
||||||
|
attr.set_nlink(1);
|
||||||
|
attr.set_mode(libc::S_IFDIR | 0o755);
|
||||||
|
attr
|
||||||
|
},
|
||||||
|
xattrs: HashMap::new(),
|
||||||
|
refcount: u64::max_value() / 2,
|
||||||
|
links: u64::max_value() / 2,
|
||||||
|
kind: INodeKind::Directory(Directory {
|
||||||
|
children: HashMap::new(),
|
||||||
|
parent: Some(parent),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}).await;
|
||||||
|
}
|
||||||
|
&_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn full_path(&self, inode: u64) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_opendir(&self, op: &op::Opendir<'_>) -> io::Result<ReplyOpen> {
|
||||||
|
// debug!("do_opendir: op: {:?}", op);
|
||||||
|
|
||||||
|
let mut dirs = self.dir_handles.lock().await;
|
||||||
|
info!("dirs: {:?}", dirs);
|
||||||
|
for x in dirs {
|
||||||
|
info!("x: {:?}", x);
|
||||||
|
|
||||||
|
}
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
|
||||||
|
let inode = inode.lock().await;
|
||||||
|
|
||||||
|
let children = match &inode.kind {
|
||||||
|
INodeKind::Directory(dir) => {
|
||||||
|
match dir.parent {
|
||||||
|
Some(parent) => {
|
||||||
|
let par_inode = inodes.get(parent).ok_or_else(no_entry)?;
|
||||||
|
let par_inode = par_inode.lock().await;
|
||||||
|
|
||||||
|
let _uri = match &par_inode.kind {
|
||||||
|
INodeKind::Directory(dir) => {
|
||||||
|
dir.children.clone()
|
||||||
|
}
|
||||||
|
_ => HashMap::new()
|
||||||
|
};
|
||||||
|
_uri
|
||||||
|
}
|
||||||
|
None => HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {HashMap::new()}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut uri = Path::new("/");
|
||||||
|
for (name, inode) in &children {
|
||||||
|
if inode == &op.ino() {
|
||||||
|
uri = Path::new(name.as_os_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(dirs);
|
||||||
|
drop(inodes);
|
||||||
|
drop(inode);
|
||||||
|
self.fetch_remote(uri, op.ino()).await;
|
||||||
|
|
||||||
|
let mut dirs = self.dir_handles.lock().await;
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
|
||||||
|
let inode = inode.lock().await;
|
||||||
|
|
||||||
|
|
||||||
|
if inode.attr.nlink() == 0 {
|
||||||
|
return Err(no_entry());
|
||||||
|
}
|
||||||
|
let dir = match inode.kind {
|
||||||
|
INodeKind::Directory(ref dir) => dir,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = dirs.insert(Arc::new(Mutex::new(DirHandle {
|
||||||
|
entries: dir.collect_entries(&inode.attr),
|
||||||
|
})));
|
||||||
|
|
||||||
|
Ok(ReplyOpen::new(key as u64))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_readdir(&self, op: &op::Readdir<'_>) -> io::Result<impl Reply + Debug> {
|
||||||
|
// warn!("do_readdir: op: {:?}", op);
|
||||||
|
let dirs = self.dir_handles.lock().await;
|
||||||
|
|
||||||
|
let dir = dirs
|
||||||
|
.get(op.fh() as usize)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(unknown_error)?;
|
||||||
|
let dir = dir.lock().await;
|
||||||
|
|
||||||
|
let mut total_len = 0;
|
||||||
|
let entries: Vec<_> = dir
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.skip(op.offset() as usize)
|
||||||
|
.take_while(|entry| {
|
||||||
|
let entry: &DirEntry = &*entry;
|
||||||
|
total_len += entry.as_ref().len() as u32;
|
||||||
|
total_len < op.size()
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_releasedir(&self, op: &op::Releasedir<'_>) -> io::Result<()> {
|
||||||
|
let mut dirs = self.dir_handles.lock().await;
|
||||||
|
|
||||||
|
let dir = dirs.remove(op.fh() as usize);
|
||||||
|
drop(dir);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_mknod(&self, op: &op::Mknod<'_>) -> io::Result<ReplyEntry> {
|
||||||
|
warn!("do_mknod: op: {:?}", op);
|
||||||
|
match op.mode() & libc::S_IFMT {
|
||||||
|
libc::S_IFREG => (),
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::ENOTSUP)),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.make_node(op.parent(), op.name(), |entry| INode {
|
||||||
|
attr: {
|
||||||
|
let mut attr = FileAttr::default();
|
||||||
|
attr.set_ino(entry.ino());
|
||||||
|
attr.set_nlink(1);
|
||||||
|
attr.set_mode(op.mode());
|
||||||
|
attr
|
||||||
|
},
|
||||||
|
xattrs: HashMap::new(),
|
||||||
|
refcount: 1,
|
||||||
|
links: 1,
|
||||||
|
kind: INodeKind::RegularFile(vec![]),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_mkdir(&self, op: &op::Mkdir<'_>) -> io::Result<ReplyEntry> {
|
||||||
|
// warn!("do_mkdir: op: {:?}", op);
|
||||||
|
self.make_node(op.parent(), op.name(), |entry| INode {
|
||||||
|
attr: {
|
||||||
|
let mut attr = FileAttr::default();
|
||||||
|
attr.set_ino(entry.ino());
|
||||||
|
attr.set_nlink(2);
|
||||||
|
attr.set_mode(op.mode() | libc::S_IFDIR);
|
||||||
|
attr
|
||||||
|
},
|
||||||
|
xattrs: HashMap::new(),
|
||||||
|
refcount: 1,
|
||||||
|
links: 1,
|
||||||
|
kind: INodeKind::Directory(Directory {
|
||||||
|
children: HashMap::new(),
|
||||||
|
parent: Some(op.parent()),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_symlink(&self, op: &op::Symlink<'_>) -> io::Result<ReplyEntry> {
|
||||||
|
self.make_node(op.parent(), op.name(), |entry| INode {
|
||||||
|
attr: {
|
||||||
|
let mut attr = FileAttr::default();
|
||||||
|
attr.set_ino(entry.ino());
|
||||||
|
attr.set_nlink(1);
|
||||||
|
attr.set_mode(libc::S_IFLNK | 0o777);
|
||||||
|
attr
|
||||||
|
},
|
||||||
|
xattrs: HashMap::new(),
|
||||||
|
refcount: 1,
|
||||||
|
links: 1,
|
||||||
|
kind: INodeKind::Symlink(Arc::new(op.link().into())),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_link(&self, op: &op::Link<'_>) -> io::Result<ReplyEntry> {
|
||||||
|
self.link_node(op.ino(), op.newparent(), op.newname()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_unlink(&self, op: &op::Unlink<'_>) -> io::Result<()> {
|
||||||
|
self.unlink_node(op.parent(), op.name()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_rmdir(&self, op: &op::Rmdir<'_>) -> io::Result<()> {
|
||||||
|
self.unlink_node(op.parent(), op.name()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_rename(&self, op: &op::Rename<'_>) -> io::Result<()> {
|
||||||
|
if op.flags() != 0 {
|
||||||
|
// TODO: handle RENAME_NOREPLACE and RENAME_EXCHANGE.
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
match (op.parent(), op.newparent()) {
|
||||||
|
(parent, newparent) if parent == newparent => {
|
||||||
|
self.rename_node(parent, op.name(), op.newname()).await
|
||||||
|
}
|
||||||
|
(parent, newparent) => {
|
||||||
|
self.graft_node(parent, op.name(), newparent, op.newname())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_getxattr(&self, op: &op::Getxattr<'_>) -> io::Result<impl Reply + Debug> {
|
||||||
|
// warn!("do_getxattr: op: {:?}", op);
|
||||||
|
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let inode = inode.lock().await;
|
||||||
|
|
||||||
|
let value = inode
|
||||||
|
.xattrs
|
||||||
|
.get(op.name())
|
||||||
|
.ok_or_else(|| io::Error::from_raw_os_error(libc::ENODATA))?;
|
||||||
|
|
||||||
|
match op.size() {
|
||||||
|
0 => Ok(Either::Left(ReplyXattr::new(value.len() as u32))),
|
||||||
|
size => {
|
||||||
|
if value.len() as u32 > size {
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::ERANGE));
|
||||||
|
}
|
||||||
|
Ok(Either::Right(value.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_setxattr(&self, op: &op::Setxattr<'_>) -> io::Result<()> {
|
||||||
|
// warn!("do_setxattr: op: {:?}", op);
|
||||||
|
|
||||||
|
let create = op.flags() as i32 & libc::XATTR_CREATE != 0;
|
||||||
|
let replace = op.flags() as i32 & libc::XATTR_REPLACE != 0;
|
||||||
|
if create && replace {
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let mut inode = inode.lock().await;
|
||||||
|
|
||||||
|
match inode.xattrs.entry(op.name().into()) {
|
||||||
|
Entry::Occupied(..) if create => Err(io::Error::from_raw_os_error(libc::EEXIST)),
|
||||||
|
Entry::Occupied(entry) => {
|
||||||
|
let value = Arc::make_mut(entry.into_mut());
|
||||||
|
*value = op.value().into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Entry::Vacant(..) if replace => Err(io::Error::from_raw_os_error(libc::ENODATA)),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
if create {
|
||||||
|
entry.insert(Arc::default());
|
||||||
|
} else {
|
||||||
|
entry.insert(Arc::new(op.value().into()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_listxattr(&self, op: &op::Listxattr<'_>) -> io::Result<impl Reply + Debug> {
|
||||||
|
// warn!("do_listxattr: op: {:?}", op);
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let inode = inode.lock().await;
|
||||||
|
|
||||||
|
match op.size() {
|
||||||
|
0 => {
|
||||||
|
let total_len = inode.xattrs.keys().map(|name| name.len() as u32 + 1).sum();
|
||||||
|
Ok(Either::Left(ReplyXattr::new(total_len)))
|
||||||
|
}
|
||||||
|
size => {
|
||||||
|
let mut total_len = 0;
|
||||||
|
let names = inode.xattrs.keys().fold(OsString::new(), |mut acc, name| {
|
||||||
|
acc.push(name);
|
||||||
|
acc.push("\0");
|
||||||
|
total_len += name.len() as u32 + 1;
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
|
if total_len > size {
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::ERANGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Either::Right(names))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_removexattr(&self, op: &op::Removexattr<'_>) -> io::Result<()> {
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let mut inode = inode.lock().await;
|
||||||
|
|
||||||
|
match inode.xattrs.entry(op.name().into()) {
|
||||||
|
Entry::Occupied(entry) => {
|
||||||
|
entry.remove();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Entry::Vacant(..) => Err(io::Error::from_raw_os_error(libc::ENODATA)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_read(&self, op: &op::Read<'_>) -> io::Result<impl Reply + Debug> {
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let inode = inode.lock().await;
|
||||||
|
|
||||||
|
let content = match inode.kind {
|
||||||
|
INodeKind::RegularFile(ref content) => content,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let offset = op.offset() as usize;
|
||||||
|
let size = op.size() as usize;
|
||||||
|
|
||||||
|
let content = content.get(offset..).unwrap_or(&[]);
|
||||||
|
let content = &content[..std::cmp::min(content.len(), size)];
|
||||||
|
|
||||||
|
Ok(content.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_write<R: ?Sized>(
|
||||||
|
&self,
|
||||||
|
op: &op::Write<'_>,
|
||||||
|
reader: &mut R,
|
||||||
|
) -> io::Result<ReplyWrite>
|
||||||
|
where
|
||||||
|
R: Reader + Unpin,
|
||||||
|
{
|
||||||
|
let inodes = self.inodes.lock().await;
|
||||||
|
|
||||||
|
let inode = inodes.get(op.ino()).ok_or_else(no_entry)?;
|
||||||
|
let mut inode = inode.lock().await;
|
||||||
|
let inode = &mut *inode;
|
||||||
|
|
||||||
|
let content = match inode.kind {
|
||||||
|
INodeKind::RegularFile(ref mut content) => content,
|
||||||
|
_ => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let offset = op.offset() as usize;
|
||||||
|
let size = op.size() as usize;
|
||||||
|
|
||||||
|
content.resize(offset + size, 0);
|
||||||
|
|
||||||
|
use futures::io::AsyncReadExt;
|
||||||
|
reader
|
||||||
|
.read_exact(&mut content[offset..offset + size])
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
inode.attr.set_size(content.len() as u64);
|
||||||
|
|
||||||
|
Ok(ReplyWrite::new(op.size()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[polyfuse::async_trait]
|
||||||
|
impl Filesystem for MemFS {
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
|
async fn call<'a, 'cx, T: ?Sized>(
|
||||||
|
&'a self,
|
||||||
|
cx: &'a mut Context<'cx, T>,
|
||||||
|
op: Operation<'cx>,
|
||||||
|
) -> io::Result<()>
|
||||||
|
where
|
||||||
|
T: Reader + Writer + Send + Unpin,
|
||||||
|
{
|
||||||
|
let span = tracing::debug_span!("MemFS::call", unique = cx.unique());
|
||||||
|
span.in_scope(|| tracing::debug!(?op));
|
||||||
|
|
||||||
|
macro_rules! try_reply {
|
||||||
|
($e:expr) => {
|
||||||
|
match ($e).instrument(span.clone()).await {
|
||||||
|
Ok(reply) => {
|
||||||
|
span.in_scope(|| tracing::debug!(reply=?reply));
|
||||||
|
cx.reply(reply).await
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let errno = err.raw_os_error().unwrap_or(libc::EIO);
|
||||||
|
span.in_scope(|| tracing::debug!(errno=errno));
|
||||||
|
cx.reply_err(errno).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match op {
|
||||||
|
Operation::Lookup(op) => try_reply!(self.do_lookup(&op)),
|
||||||
|
Operation::Forget(forgets) => {
|
||||||
|
self.do_forget(forgets.as_ref()).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Operation::Getattr(op) => try_reply!(self.do_getattr(&op)),
|
||||||
|
Operation::Setattr(op) => try_reply!(self.do_setattr(&op)),
|
||||||
|
Operation::Readlink(op) => try_reply!(self.do_readlink(&op)),
|
||||||
|
|
||||||
|
Operation::Opendir(op) => try_reply!(self.do_opendir(&op)),
|
||||||
|
Operation::Readdir(op) => try_reply!(self.do_readdir(&op)),
|
||||||
|
Operation::Releasedir(op) => try_reply!(self.do_releasedir(&op)),
|
||||||
|
|
||||||
|
Operation::Mknod(op) => try_reply!(self.do_mknod(&op)),
|
||||||
|
Operation::Mkdir(op) => try_reply!(self.do_mkdir(&op)),
|
||||||
|
Operation::Symlink(op) => try_reply!(self.do_symlink(&op)),
|
||||||
|
Operation::Unlink(op) => try_reply!(self.do_unlink(&op)),
|
||||||
|
Operation::Rmdir(op) => try_reply!(self.do_rmdir(&op)),
|
||||||
|
Operation::Link(op) => try_reply!(self.do_link(&op)),
|
||||||
|
Operation::Rename(op) => try_reply!(self.do_rename(&op)),
|
||||||
|
|
||||||
|
Operation::Getxattr(op) => try_reply!(self.do_getxattr(&op)),
|
||||||
|
Operation::Setxattr(op) => try_reply!(self.do_setxattr(&op)),
|
||||||
|
Operation::Listxattr(op) => try_reply!(self.do_listxattr(&op)),
|
||||||
|
Operation::Removexattr(op) => try_reply!(self.do_removexattr(&op)),
|
||||||
|
|
||||||
|
Operation::Read(op) => try_reply!(self.do_read(&op)),
|
||||||
|
Operation::Write(op) => {
|
||||||
|
let res = self
|
||||||
|
.do_write(&op, &mut cx.reader())
|
||||||
|
.instrument(span.clone())
|
||||||
|
.await;
|
||||||
|
try_reply!(async { res })
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
span.in_scope(|| tracing::debug!("NOSYS"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn no_entry() -> io::Error {
|
||||||
|
io::Error::from_raw_os_error(libc::ENOENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unknown_error() -> io::Error {
|
||||||
|
io::Error::from_raw_os_error(libc::EIO)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: use either crate.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Either<L, R> {
|
||||||
|
Left(L),
|
||||||
|
Right(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> Reply for Either<L, R>
|
||||||
|
where
|
||||||
|
L: Reply,
|
||||||
|
R: Reply,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn collect_bytes<'a, C: ?Sized>(&'a self, collector: &mut C)
|
||||||
|
where
|
||||||
|
C: Collector<'a>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Either::Left(l) => l.collect_bytes(collector),
|
||||||
|
Either::Right(r) => r.collect_bytes(collector),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
src/http.rs
Normal file
69
src/http.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
extern crate base64;
|
||||||
|
use reqwest::{blocking::Client, header::CONTENT_LENGTH};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
|
env,
|
||||||
|
ffi::OsStr,
|
||||||
|
fmt,
|
||||||
|
path::Path,
|
||||||
|
process,
|
||||||
|
thread::sleep,
|
||||||
|
time::{Duration,SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
pub struct RemoteEntry {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub r#type: Option<String>,
|
||||||
|
pub mtime: Option<String>,
|
||||||
|
pub size: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteEntry{
|
||||||
|
pub fn parse_rfc2822(&self) -> SystemTime{
|
||||||
|
let rfc2822 = DateTime::parse_from_rfc2822(&self.mtime.as_ref().unwrap()).unwrap();
|
||||||
|
SystemTime::from(rfc2822)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime};
|
||||||
|
use chrono::format::ParseError;
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn list_directory(
|
||||||
|
server: &std::string::String,
|
||||||
|
username: &Option<String>,
|
||||||
|
password: &Option<String>,
|
||||||
|
path: &Path) ->
|
||||||
|
Result<Vec<RemoteEntry>, reqwest::Error> {
|
||||||
|
info!("Fetching path '{}{}'", server, path.display());
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let http_auth = match username {
|
||||||
|
Some(username) => {
|
||||||
|
// info!("Using Basic Auth");
|
||||||
|
let mut _buf = String::new();
|
||||||
|
_buf.push_str(
|
||||||
|
format!(
|
||||||
|
"{}:{}",
|
||||||
|
username,
|
||||||
|
password.as_ref().unwrap()
|
||||||
|
).as_str()
|
||||||
|
);
|
||||||
|
|
||||||
|
base64::encode(_buf)
|
||||||
|
}
|
||||||
|
None => {String::new()}
|
||||||
|
};
|
||||||
|
//info!("AUTH: {:?}", http_auth);
|
||||||
|
let resp = client
|
||||||
|
.get(format!("{}/{}", server, path.display()).as_str())
|
||||||
|
.header("Authorization", format!("Basic {}", http_auth))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json::<Vec<RemoteEntry>>()
|
||||||
|
.await?;
|
||||||
|
info!("Found {} entries into '{}'", resp.len(), path.display());
|
||||||
|
Ok(resp)
|
||||||
|
}
|
36
src/main.rs
Normal file
36
src/main.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
use env_logger::Env;
|
||||||
|
use std::{process,path::Path};
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod filesystem;
|
||||||
|
mod http;
|
||||||
|
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), std::io::Error> {
|
||||||
|
env_logger::from_env(Env::default().default_filter_or("info")).init();
|
||||||
|
let cfg = config::read();
|
||||||
|
//let http::list_directory(&cfg.server, &cfg.username, &cfg.password, "/").await;
|
||||||
|
warn!("{:?}", cfg);
|
||||||
|
|
||||||
|
//let mut args = pico_args::Arguments::from_env();
|
||||||
|
|
||||||
|
let mountpoint: PathBuf = PathBuf::from(&cfg.mountpoint);
|
||||||
|
if !mountpoint.is_dir() {
|
||||||
|
error!("The mountpoint must be a directory");
|
||||||
|
process::exit(0x0004);
|
||||||
|
}
|
||||||
|
|
||||||
|
let memfs = filesystem::MemFS::new(&cfg);
|
||||||
|
memfs.fetch_remote(Path::new("/"), 1).await;
|
||||||
|
polyfuse_tokio::mount(memfs, mountpoint, &[]).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Mkdir { parent: 1, name: "123", mode: 493, umask: 18 }
|
||||||
|
Mknod { parent: 1, name: "123233", mode: 33188, rdev: 0, umask: 18 }
|
||||||
|
*/
|
Reference in New Issue
Block a user