Skip to content

Commit

Permalink
🎉 Start project
Browse files Browse the repository at this point in the history
  • Loading branch information
ChanTsune committed Jan 25, 2024
1 parent 2b10d6c commit 3040397
Show file tree
Hide file tree
Showing 8 changed files with 1,386 additions and 0 deletions.
1,048 changes: 1,048 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions Cargo.toml
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"
13 changes: 13 additions & 0 deletions src/cli.rs
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),
}
7 changes: 7 additions & 0 deletions src/command.rs
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<()>;
}
39 changes: 39 additions & 0 deletions src/command/mount.rs
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(())
}
187 changes: 187 additions & 0 deletions src/file_manager.rs
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<_>>>()
}
}
57 changes: 57 additions & 0 deletions src/filesystem.rs
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();
}
}
21 changes: 21 additions & 0 deletions src/main.rs
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(),
}
}

0 comments on commit 3040397

Please sign in to comment.