use age::{armor::ArmoredReader, stream::StreamReader};
use fuse_mt::*;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Mutex};
use std::time::{Duration, SystemTime};
use zip::{read::ZipFile, ZipArchive};
fn zip_path(path: &Path) -> &Path {
path.strip_prefix("/").unwrap()
}
fn zipfile_to_filetype(zf: &ZipFile) -> FileType {
if zf.is_dir() {
FileType::Directory
} else {
FileType::RegularFile
}
}
fn zipfile_to_fuse(zf: &ZipFile) -> FileAttr {
let kind = zipfile_to_filetype(zf);
let perm = (zf.unix_mode().unwrap_or(0) & 0o7777) as u16;
let mtime: SystemTime = zf.last_modified().to_time().unwrap().into();
FileAttr {
size: zf.size() as u64,
blocks: 1,
atime: mtime,
mtime,
ctime: mtime,
crtime: SystemTime::UNIX_EPOCH,
kind,
perm,
nlink: 1,
uid: 1000,
gid: 1000,
rdev: 0,
flags: 0,
}
}
const DIR_ATTR: FileAttr = FileAttr {
size: 0,
blocks: 0,
atime: SystemTime::UNIX_EPOCH,
mtime: SystemTime::UNIX_EPOCH,
ctime: SystemTime::UNIX_EPOCH,
crtime: SystemTime::UNIX_EPOCH,
kind: FileType::Directory,
perm: 0o0755,
nlink: 1,
uid: 1000,
gid: 1000,
rdev: 0,
flags: 0,
};
fn add_dir_to_map(
dir_map: &mut HashMap<PathBuf, Vec<DirectoryEntry>>,
path: &Path,
kind: FileType,
) {
let name = path
.file_name()
.expect("All ZIP files have filenames")
.to_owned();
let parent = path
.parent()
.expect("paths should have parents")
.to_path_buf();
if !dir_map.contains_key(&parent) {
add_dir_to_map(dir_map, &parent, FileType::Directory);
}
dir_map
.entry(parent)
.or_default()
.push(DirectoryEntry { name, kind });
}
pub struct AgeZipFs {
inner: Mutex<ZipArchive<StreamReader<ArmoredReader<BufReader<File>>>>>,
destroy_tx: mpsc::SyncSender<()>,
dir_map: HashMap<PathBuf, Vec<DirectoryEntry>>,
open_dirs: Mutex<(HashMap<u64, PathBuf>, u64)>,
open_files: Mutex<(HashMap<u64, usize>, u64)>,
}
impl AgeZipFs {
pub fn open(
stream: StreamReader<ArmoredReader<BufReader<File>>>,
destroy_tx: mpsc::SyncSender<()>,
) -> io::Result<Self> {
let mut archive =
ZipArchive::new(stream).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let mut dir_map: HashMap<PathBuf, Vec<DirectoryEntry>> = HashMap::new();
dir_map.insert(PathBuf::new(), vec![]); for i in 0..archive.len() {
let zf = archive
.by_index(i)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
if let Some(path) = zf.enclosed_name() {
add_dir_to_map(&mut dir_map, path, zipfile_to_filetype(&zf));
}
}
Ok(AgeZipFs {
inner: Mutex::new(archive),
destroy_tx,
dir_map,
open_dirs: Mutex::new((HashMap::new(), 0)),
open_files: Mutex::new((HashMap::new(), 0)),
})
}
}
const TTL: Duration = Duration::from_secs(1);
impl FilesystemMT for AgeZipFs {
fn destroy(&self) {
self.destroy_tx
.send(())
.expect("Could not send signal on channel.");
}
fn getattr(&self, _req: RequestInfo, path: &Path, fh: Option<u64>) -> ResultEntry {
let mut inner = self.inner.lock().unwrap();
let open_dirs = self.open_dirs.lock().unwrap();
let open_files = self.open_files.lock().unwrap();
if let Some(fh) = fh {
if open_dirs.0.contains_key(&fh) {
Ok((TTL, DIR_ATTR))
} else if let Some(index) = open_files.0.get(&fh) {
let zf = inner.by_index(*index).expect("open_files is correct");
Ok((TTL, zipfile_to_fuse(&zf)))
} else {
Err(libc::EBADF)
}
} else if self.dir_map.contains_key(zip_path(path)) {
Ok((TTL, DIR_ATTR))
} else {
match inner.by_name(zip_path(path).to_str().unwrap()) {
Ok(zf) => Ok((TTL, zipfile_to_fuse(&zf))),
Err(_) => Err(libc::ENOENT),
}
}
}
fn opendir(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen {
let mut open_dirs = self.open_dirs.lock().unwrap();
let fh = open_dirs.1;
let path = zip_path(path);
open_dirs.0.insert(fh, path.to_path_buf());
open_dirs.1 = open_dirs.1.wrapping_add(1);
Ok((fh, 0))
}
fn readdir(&self, _req: RequestInfo, _path: &Path, fh: u64) -> ResultReaddir {
let open_dirs = self.open_dirs.lock().unwrap();
if let Some(path) = open_dirs.0.get(&fh) {
Ok(self.dir_map.get(path).cloned().unwrap_or_default())
} else {
Err(libc::EBADF)
}
}
fn releasedir(&self, _req: RequestInfo, _path: &Path, fh: u64, _flags: u32) -> ResultEmpty {
let mut open_dirs = self.open_dirs.lock().unwrap();
open_dirs.0.remove(&fh).map(|_| ()).ok_or(libc::EBADF)
}
fn statfs(&self, _req: RequestInfo, _path: &Path) -> ResultStatfs {
let inner = self.inner.lock().unwrap();
Ok(Statfs {
blocks: inner.len() as u64,
bfree: 0,
bavail: 0,
files: inner.len() as u64,
ffree: 0,
bsize: 64 * 1024,
namelen: u32::max_value(),
frsize: 64 * 1024,
})
}
fn open(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen {
let mut inner = self.inner.lock().unwrap();
let mut open_files = self.open_files.lock().unwrap();
for i in 0..inner.len() {
if inner.by_index(i).unwrap().enclosed_name() == Some(zip_path(path)) {
let fh = open_files.1;
open_files.0.insert(fh, i);
open_files.1 = open_files.1.wrapping_add(1);
return Ok((fh, 0));
}
}
Err(libc::ENOENT)
}
fn read(
&self,
_req: RequestInfo,
_path: &Path,
fh: u64,
offset: u64,
size: u32,
callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult,
) -> CallbackResult {
let mut inner = self.inner.lock().unwrap();
let open_files = self.open_files.lock().unwrap();
match open_files.0.get(&fh) {
Some(index) => {
let mut zf = inner.by_index(*index).expect("open_files is correct");
if offset > zf.size() {
return callback(Err(libc::EINVAL));
}
let mut buf = vec![];
buf.resize(offset as usize, 0);
if zf.read_exact(&mut buf).is_err() {
return callback(Err(libc::EIO));
}
let to_read = usize::min(size as usize, (zf.size() - offset) as usize);
buf.resize(to_read, 0);
match zf.read_exact(&mut buf) {
Ok(_) => callback(Ok(&buf)),
Err(_) => callback(Err(libc::EIO)),
}
}
None => callback(Err(libc::EBADF)),
}
}
fn release(
&self,
_req: RequestInfo,
_path: &Path,
fh: u64,
_flags: u32,
_lock_owner: u64,
_flush: bool,
) -> ResultEmpty {
let mut open_files = self.open_files.lock().unwrap();
open_files.0.remove(&fh).map(|_| ()).ok_or(libc::EBADF)
}
}