-
Notifications
You must be signed in to change notification settings - Fork 0
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
8 changed files
with
1,386 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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,14 @@ | ||
[package] | ||
name = "pna-fs" | ||
version = "0.0.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
clap = { version = "4.4.18", features = ["derive"] } | ||
fuser = "0.14.0" | ||
id_tree = "1.8.0" | ||
log = "0.4.20" | ||
pna = "0.6.0" | ||
simple_logger = "4.3.3" |
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,13 @@ | ||
use crate::command::mount::MountArgs; | ||
use clap::{Parser, Subcommand}; | ||
|
||
#[derive(Parser)] | ||
pub(crate) struct CLI { | ||
#[clap(subcommand)] | ||
pub(crate) subcommand: SubCommand, | ||
} | ||
|
||
#[derive(Subcommand)] | ||
pub(crate) enum SubCommand { | ||
Mount(MountArgs), | ||
} |
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,7 @@ | ||
pub(crate) mod mount; | ||
|
||
use std::io; | ||
|
||
pub(crate) trait Command { | ||
fn execute(&self) -> io::Result<()>; | ||
} |
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,39 @@ | ||
use crate::command::Command; | ||
use crate::filesystem::PnaFS; | ||
use clap::Args; | ||
use fuser::{mount2, MountOption}; | ||
use std::fs::create_dir_all; | ||
use std::io; | ||
use std::path::{Path, PathBuf}; | ||
|
||
#[derive(Args)] | ||
pub(crate) struct MountArgs { | ||
#[arg()] | ||
archive: PathBuf, | ||
#[arg()] | ||
mount_point: PathBuf, | ||
} | ||
|
||
impl Command for MountArgs { | ||
fn execute(&self) -> io::Result<()> { | ||
mount_archive(&self.mount_point, &self.archive) | ||
} | ||
} | ||
|
||
fn mount_archive<MountPoint: AsRef<Path>, Archive: AsRef<Path>>( | ||
mount_point: MountPoint, | ||
archive: Archive, | ||
) -> io::Result<()> { | ||
let fs = PnaFS::new(archive.as_ref().into()); | ||
create_dir_all(&mount_point)?; | ||
mount2( | ||
fs, | ||
mount_point, | ||
&[ | ||
MountOption::FSName("pnafs".to_owned()), | ||
MountOption::AllowRoot, | ||
MountOption::RO, | ||
], | ||
)?; | ||
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,187 @@ | ||
use fuser::{FileAttr, FileType}; | ||
use id_tree::{InsertBehavior, Node, NodeId, Tree, TreeBuilder}; | ||
use pna::{Archive, DataKind}; | ||
use std::collections::HashMap; | ||
use std::ops::Add; | ||
use std::path::PathBuf; | ||
use std::time::SystemTime; | ||
use std::{fs, io}; | ||
|
||
pub type Inode = u64; | ||
|
||
pub(crate) struct File { | ||
pub(crate) name: String, | ||
pub(crate) attr: FileAttr, | ||
} | ||
|
||
impl File { | ||
fn dir(inode: Inode, name: String) -> Self { | ||
let now = SystemTime::now(); | ||
Self { | ||
name, | ||
attr: FileAttr { | ||
ino: inode, | ||
size: 512, | ||
blocks: 1, | ||
atime: now, | ||
mtime: now, | ||
ctime: now, | ||
crtime: now, | ||
kind: FileType::Directory, | ||
perm: 0o775, | ||
nlink: 2, | ||
uid: 0, | ||
gid: 0, | ||
rdev: 0, | ||
blksize: 0, | ||
flags: 0, | ||
}, | ||
} | ||
} | ||
fn root(inode: Inode) -> Self { | ||
Self::dir(inode, ".".into()) | ||
} | ||
|
||
fn from_entry(inode: Inode, entry: pna::RegularEntry) -> Self { | ||
let now = SystemTime::now(); | ||
let header = entry.header(); | ||
let metadata = entry.metadata(); | ||
Self { | ||
name: header | ||
.path() | ||
.as_path() | ||
.components() | ||
.last() | ||
.unwrap() | ||
.as_os_str() | ||
.to_string_lossy() | ||
.into(), | ||
attr: FileAttr { | ||
ino: inode, | ||
size: metadata.compressed_size() as u64, | ||
blocks: 1, | ||
atime: now, | ||
mtime: metadata | ||
.modified() | ||
.map(|it| SystemTime::UNIX_EPOCH.add(it)) | ||
.unwrap_or(now), | ||
ctime: metadata | ||
.modified() | ||
.map(|it| SystemTime::UNIX_EPOCH.add(it)) | ||
.unwrap_or(now), | ||
crtime: metadata | ||
.created() | ||
.map(|it| SystemTime::UNIX_EPOCH.add(it)) | ||
.unwrap_or(now), | ||
kind: match header.data_kind() { | ||
DataKind::File => FileType::RegularFile, | ||
DataKind::Directory => FileType::Directory, | ||
DataKind::SymbolicLink => FileType::Symlink, | ||
DataKind::HardLink => FileType::RegularFile, | ||
}, | ||
perm: 0o775, | ||
nlink: 1, | ||
uid: 0, | ||
gid: 0, | ||
rdev: 0, | ||
blksize: 512, | ||
flags: 0, | ||
}, | ||
} | ||
} | ||
} | ||
|
||
const ROOT_INODE: Inode = 1; | ||
|
||
pub(crate) struct FileManager { | ||
archive_path: PathBuf, | ||
password: Option<String>, | ||
tree: Tree<Inode>, | ||
files: HashMap<Inode, File>, | ||
node_ids: HashMap<Inode, NodeId>, | ||
last_inode: Inode, | ||
} | ||
|
||
impl FileManager { | ||
pub(crate) fn new(archive_path: PathBuf) -> Self { | ||
let mut mamager = Self { | ||
archive_path, | ||
password: None, | ||
tree: TreeBuilder::new().build(), | ||
files: HashMap::with_capacity(0), | ||
node_ids: HashMap::with_capacity(0), | ||
last_inode: ROOT_INODE, | ||
}; | ||
mamager.populate().unwrap(); | ||
mamager | ||
} | ||
|
||
fn populate(&mut self) -> io::Result<()> { | ||
self.add_root_file(File::root(ROOT_INODE)).unwrap(); | ||
let file = fs::File::open(&self.archive_path).unwrap(); | ||
let mut archive = Archive::read_header(file).unwrap(); | ||
let password = self.password.clone(); | ||
for entry in archive.entries_with_password(password.as_deref()) { | ||
let entry = entry.unwrap(); | ||
let mut parents = entry | ||
.header() | ||
.path() | ||
.as_path() | ||
.components() | ||
.collect::<Vec<_>>(); | ||
parents.pop(); | ||
let mut parent = ROOT_INODE; | ||
for component in parents { | ||
let name = component.as_os_str().to_string_lossy().to_string(); | ||
let children = self.get_children(parent).unwrap(); | ||
let it = children.iter().find(|it| name == it.name); | ||
if let Some(it) = it { | ||
parent = it.attr.ino; | ||
} else { | ||
let ino = self.next_inode(); | ||
self.add_file(File::dir(ino, name), parent)?; | ||
parent = ino; | ||
} | ||
} | ||
let file = File::from_entry(self.next_inode(), entry); | ||
self.add_file(file, parent)?; | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn next_inode(&mut self) -> Inode { | ||
self.last_inode += 1; | ||
self.last_inode | ||
} | ||
|
||
fn add_root_file(&mut self, file: File) -> io::Result<()> { | ||
self._add_file(file, InsertBehavior::AsRoot) | ||
} | ||
|
||
fn add_file(&mut self, file: File, parent: Inode) -> io::Result<()> { | ||
let node_id = self.node_ids.get(&parent).unwrap().clone(); | ||
self._add_file(file, InsertBehavior::UnderNode(&node_id)) | ||
} | ||
|
||
fn _add_file(&mut self, file: File, insert_behavior: InsertBehavior) -> io::Result<()> { | ||
let node_id = self | ||
.tree | ||
.insert(Node::new(file.attr.ino), insert_behavior) | ||
.map_err(|err| io::Error::other(err))?; | ||
self.node_ids.insert(file.attr.ino, node_id); | ||
self.files.insert(file.attr.ino, file); | ||
Ok(()) | ||
} | ||
|
||
pub(crate) fn get_file(&self, ino: Inode) -> Option<&File> { | ||
self.files.get(&ino) | ||
} | ||
|
||
pub(crate) fn get_children(&self, parent: Inode) -> Option<Vec<&File>> { | ||
let node_id = self.node_ids.get(&parent)?; | ||
let children = self.tree.children(node_id).ok()?; | ||
children | ||
.map(|ino| self.files.get(ino.data())) | ||
.collect::<Option<Vec<_>>>() | ||
} | ||
} |
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,57 @@ | ||
use crate::file_manager::FileManager; | ||
use fuser::{Filesystem, ReplyAttr, ReplyDirectory, Request}; | ||
use log::info; | ||
use std::path::PathBuf; | ||
use std::time::Duration; | ||
|
||
pub(crate) struct PnaFS { | ||
manager: FileManager, | ||
} | ||
|
||
impl PnaFS { | ||
pub(crate) fn new(archive: PathBuf) -> Self { | ||
Self { | ||
manager: FileManager::new(archive), | ||
} | ||
} | ||
} | ||
|
||
impl Filesystem for PnaFS { | ||
fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) { | ||
info!("[Implemented] getattr(ino: {:#x?})", ino); | ||
let ttl = Duration::from_secs(1); | ||
let file = self.manager.get_file(ino).unwrap(); | ||
reply.attr(&ttl, &file.attr); | ||
} | ||
|
||
fn readdir( | ||
&mut self, | ||
_req: &Request<'_>, | ||
ino: u64, | ||
fh: u64, | ||
offset: i64, | ||
mut reply: ReplyDirectory, | ||
) { | ||
info!( | ||
"[Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", | ||
ino, fh, offset | ||
); | ||
let mut children = self.manager.get_children(ino).unwrap(); | ||
|
||
let mut current_offset = offset + 1; | ||
for entry in children.into_iter().skip(offset as usize) { | ||
let is_full = reply.add( | ||
current_offset as u64, | ||
current_offset, | ||
entry.attr.kind, | ||
entry.name.as_str(), | ||
); | ||
if is_full { | ||
break; | ||
} else { | ||
current_offset += 1; | ||
} | ||
} | ||
reply.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,21 @@ | ||
use crate::cli::SubCommand; | ||
use crate::command::Command; | ||
use clap::Parser; | ||
use std::io; | ||
|
||
mod cli; | ||
mod command; | ||
mod file_manager; | ||
mod filesystem; | ||
|
||
fn main() -> io::Result<()> { | ||
simple_logger::init_with_level(log::Level::Trace).unwrap(); | ||
entry() | ||
} | ||
|
||
fn entry() -> io::Result<()> { | ||
let cli = cli::CLI::parse(); | ||
match cli.subcommand { | ||
SubCommand::Mount(args) => args.execute(), | ||
} | ||
} |