implemented FUSE lookup function
This commit is contained in:
parent
372aa34022
commit
833fb71108
7 changed files with 369 additions and 66 deletions
40
Cargo.lock
generated
40
Cargo.lock
generated
|
|
@ -41,6 +41,15 @@ version = "3.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "castaway"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.30"
|
version = "1.2.30"
|
||||||
|
|
@ -74,6 +83,20 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compact_str"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
|
||||||
|
dependencies = [
|
||||||
|
"castaway",
|
||||||
|
"cfg-if",
|
||||||
|
"itoa",
|
||||||
|
"rustversion",
|
||||||
|
"ryu",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
|
|
@ -99,6 +122,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"compact_str",
|
||||||
"enum_dispatch",
|
"enum_dispatch",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
@ -126,6 +150,10 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fat-mount"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuser"
|
name = "fuser"
|
||||||
version = "0.15.1"
|
version = "0.15.1"
|
||||||
|
|
@ -178,6 +206,12 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.77"
|
version = "0.3.77"
|
||||||
|
|
@ -297,6 +331,12 @@ version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ chrono = { version = "0.4.41", default-features = false, features = [
|
||||||
"alloc",
|
"alloc",
|
||||||
"std",
|
"std",
|
||||||
] }
|
] }
|
||||||
|
compact_str = "0.9.0"
|
||||||
enum_dispatch = "0.3.13"
|
enum_dispatch = "0.3.13"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::io::Read;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
|
use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
|
||||||
|
use compact_str::CompactString;
|
||||||
|
|
||||||
use crate::datetime::{Date, Time};
|
use crate::datetime::{Date, Time};
|
||||||
use crate::utils::{load_u16_le, load_u32_le};
|
use crate::utils::{load_u16_le, load_u32_le};
|
||||||
|
|
@ -48,7 +49,7 @@ impl Display for Attr {
|
||||||
/// represents an entry in a diectory
|
/// represents an entry in a diectory
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DirEntry {
|
pub struct DirEntry {
|
||||||
name: [u8; 11],
|
name: [u8; 13],
|
||||||
attr: Attr,
|
attr: Attr,
|
||||||
|
|
||||||
create_time_tenths: u8,
|
create_time_tenths: u8,
|
||||||
|
|
@ -64,12 +65,13 @@ pub struct DirEntry {
|
||||||
|
|
||||||
file_size: u32,
|
file_size: u32,
|
||||||
|
|
||||||
long_name: Option<String>,
|
checksum: u8,
|
||||||
|
long_name: Option<CompactString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for DirEntry {
|
impl Display for DirEntry {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut name = self.name_string().unwrap_or_else(|| "<unknown>".to_owned());
|
let mut name = self.name_string().unwrap_or_else(|| "<unknown>".into());
|
||||||
|
|
||||||
if self.attr.contains(Attr::Directory) {
|
if self.attr.contains(Attr::Directory) {
|
||||||
name.push('/');
|
name.push('/');
|
||||||
|
|
@ -89,12 +91,63 @@ impl Display for DirEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirEntry {
|
impl DirEntry {
|
||||||
|
fn load_name(bytes: [u8; 11], attr: &Attr) -> [u8; 13] {
|
||||||
|
let mut name = [0; 13];
|
||||||
|
|
||||||
|
let mut iter = name.iter_mut();
|
||||||
|
|
||||||
|
if attr.contains(Attr::Hidden) && !attr.contains(Attr::VolumeId) {
|
||||||
|
// hidden file or folder
|
||||||
|
*iter.next().unwrap() = b'.';
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate_trailing_spaces(mut bytes: &[u8]) -> &[u8] {
|
||||||
|
while bytes.last() == Some(&0x20) {
|
||||||
|
bytes = &bytes[..bytes.len() - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
let stem_bytes = truncate_trailing_spaces(&bytes[..8]);
|
||||||
|
let ext_bytes = truncate_trailing_spaces(&bytes[8..]);
|
||||||
|
|
||||||
|
for &c in stem_bytes {
|
||||||
|
let c = match c {
|
||||||
|
// reject non-ascii
|
||||||
|
// TODO: implement code pages? probably not...
|
||||||
|
c if c >= 128 => b'?',
|
||||||
|
c => c,
|
||||||
|
};
|
||||||
|
|
||||||
|
*iter.next().unwrap() = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ext_bytes.is_empty() {
|
||||||
|
*iter.next().unwrap() = b'.';
|
||||||
|
|
||||||
|
for &c in ext_bytes {
|
||||||
|
let c = match c {
|
||||||
|
// reject non-ascii
|
||||||
|
// TODO: implement code pages? probably not...
|
||||||
|
c if c >= 128 => b'?',
|
||||||
|
c => c,
|
||||||
|
};
|
||||||
|
|
||||||
|
*iter.next().unwrap() = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntry> {
|
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntry> {
|
||||||
assert_eq!(bytes.len(), 32);
|
assert_eq!(bytes.len(), 32);
|
||||||
|
|
||||||
let name = bytes[..11].try_into().unwrap();
|
|
||||||
let attr = Attr::from_bits_truncate(bytes[11]);
|
let attr = Attr::from_bits_truncate(bytes[11]);
|
||||||
|
|
||||||
|
let name = Self::load_name(bytes[..11].try_into().unwrap(), &attr);
|
||||||
|
|
||||||
let create_time_tenths = bytes[13];
|
let create_time_tenths = bytes[13];
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
create_time_tenths <= 199,
|
create_time_tenths <= 199,
|
||||||
|
|
@ -142,6 +195,7 @@ impl DirEntry {
|
||||||
write_date,
|
write_date,
|
||||||
file_size,
|
file_size,
|
||||||
long_name: None,
|
long_name: None,
|
||||||
|
checksum: Self::checksum(&bytes[..11]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,8 +227,6 @@ impl DirEntry {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// &self.name[..2] == &[b'.', b' ']
|
|
||||||
|
|
||||||
self.name[0] == b'.' && &self.name[1..] == &[b' '; 10]
|
self.name[0] == b'.' && &self.name[1..] == &[b' '; 10]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,8 +235,6 @@ impl DirEntry {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// &self.name[..3] == &[b'.', b'.', b' ']
|
|
||||||
|
|
||||||
&self.name[..2] == &[b'.', b'.'] && &self.name[2..] == &[b' '; 9]
|
&self.name[..2] == &[b'.', b'.'] && &self.name[2..] == &[b' '; 9]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,15 +266,18 @@ impl DirEntry {
|
||||||
std::str::from_utf8(self.extension()).ok()
|
std::str::from_utf8(self.extension()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name_string(&self) -> Option<String> {
|
pub fn name_string(&self) -> Option<CompactString> {
|
||||||
|
// use a CompactString here to allow inlining of short names
|
||||||
|
// maybe switch to a Cow instead? has disadvantage that we need to alloc for short names
|
||||||
|
|
||||||
if let Some(long_filename) = self.long_name() {
|
if let Some(long_filename) = self.long_name() {
|
||||||
return Some(long_filename.to_owned());
|
return Some(long_filename.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = std::str::from_utf8(&self.name[..8]).ok()?.trim_ascii_end();
|
let name = std::str::from_utf8(&self.name[..8]).ok()?.trim_ascii_end();
|
||||||
let ext = std::str::from_utf8(&self.name[8..]).ok()?.trim_ascii_end();
|
let ext = std::str::from_utf8(&self.name[8..]).ok()?.trim_ascii_end();
|
||||||
|
|
||||||
let mut s = String::new();
|
let mut s = CompactString::const_new("");
|
||||||
|
|
||||||
if self.attr.contains(Attr::Hidden) {
|
if self.attr.contains(Attr::Hidden) {
|
||||||
s.push('.');
|
s.push('.');
|
||||||
|
|
@ -245,7 +298,7 @@ impl DirEntry {
|
||||||
self.long_name.as_deref()
|
self.long_name.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_long_name(&mut self, long_name: String) {
|
pub fn set_long_name(&mut self, long_name: CompactString) {
|
||||||
self.long_name = Some(long_name);
|
self.long_name = Some(long_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,10 +336,10 @@ impl DirEntry {
|
||||||
self.file_size
|
self.file_size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checksum(&self) -> u8 {
|
pub fn checksum(name: &[u8]) -> u8 {
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
|
|
||||||
for &x in self.name() {
|
for &x in name {
|
||||||
checksum = checksum.rotate_right(1).wrapping_add(x);
|
checksum = checksum.rotate_right(1).wrapping_add(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -482,9 +535,6 @@ impl LongFilenameBuf {
|
||||||
pub struct DirIter<R: Read> {
|
pub struct DirIter<R: Read> {
|
||||||
reader: R,
|
reader: R,
|
||||||
|
|
||||||
// long_filename_rev_buf: Vec<u16>,
|
|
||||||
// long_filename_checksum: Option<u8>,
|
|
||||||
// long_filename_last_ordinal: Option<u8>,
|
|
||||||
long_filename_buf: LongFilenameBuf,
|
long_filename_buf: LongFilenameBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -492,9 +542,6 @@ impl<R: Read> DirIter<R> {
|
||||||
pub fn new(reader: R) -> DirIter<R> {
|
pub fn new(reader: R) -> DirIter<R> {
|
||||||
DirIter {
|
DirIter {
|
||||||
reader,
|
reader,
|
||||||
// long_filename_rev_buf: Vec::new(),
|
|
||||||
// long_filename_checksum: None,
|
|
||||||
// long_filename_last_ordinal: None,
|
|
||||||
long_filename_buf: Default::default(),
|
long_filename_buf: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -503,14 +550,10 @@ impl<R: Read> DirIter<R> {
|
||||||
fn next_impl(&mut self) -> anyhow::Result<Option<DirEntry>> {
|
fn next_impl(&mut self) -> anyhow::Result<Option<DirEntry>> {
|
||||||
let mut chunk = [0; 32];
|
let mut chunk = [0; 32];
|
||||||
if self.reader.read_exact(&mut chunk).is_err() {
|
if self.reader.read_exact(&mut chunk).is_err() {
|
||||||
// reading failed; nothing we can do here
|
// nothing we can do here since we might be in an invalid state after a partial read
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// let Ok(dir_entry) = DirEntry::load(&chunk) else {
|
|
||||||
// return self.next();
|
|
||||||
// };
|
|
||||||
|
|
||||||
let dir_entry = DirEntryWrapper::load(&chunk)
|
let dir_entry = DirEntryWrapper::load(&chunk)
|
||||||
.map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
|
||||||
|
|
||||||
|
|
@ -534,25 +577,23 @@ impl<R: Read> DirIter<R> {
|
||||||
return self.next_impl();
|
return self.next_impl();
|
||||||
}
|
}
|
||||||
|
|
||||||
match self
|
if let Some(iter) = self
|
||||||
.long_filename_buf
|
.long_filename_buf
|
||||||
.get_buf(dir_entry.checksum())
|
.get_buf(dir_entry.checksum)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"failed to get long filename for {}: {}",
|
"failed to get long filename for {}: {}",
|
||||||
dir_entry.name_string().as_deref().unwrap_or("<invalid>"),
|
dir_entry.name_string().as_deref().unwrap_or("<invalid>"),
|
||||||
e
|
e
|
||||||
)
|
)
|
||||||
})? {
|
})?
|
||||||
Some(iter) => {
|
{
|
||||||
// attach long filename to dir_entry
|
// attach long filename to dir_entry
|
||||||
|
|
||||||
let long_filename: String =
|
let long_filename: CompactString =
|
||||||
char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
|
char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
|
||||||
|
|
||||||
dir_entry.set_long_name(long_filename);
|
dir_entry.set_long_name(long_filename);
|
||||||
}
|
|
||||||
None => {} // no long filename -> do nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.long_filename_buf.reset();
|
self.long_filename_buf.reset();
|
||||||
|
|
@ -568,6 +609,7 @@ impl<R: Read> Iterator for DirIter<R> {
|
||||||
match self.next_impl() {
|
match self.next_impl() {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// print error message, try next
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
|
|
||||||
self.next()
|
self.next()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fat_bits::FatFs;
|
use fat_bits::FatFs;
|
||||||
use fat_bits::dir::{DirEntry, DirIter};
|
use fat_bits::dir::DirEntry;
|
||||||
use fat_bits::fat::Fatty as _;
|
use fat_bits::fat::Fatty as _;
|
||||||
|
|
||||||
pub fn main() -> anyhow::Result<()> {
|
pub fn main() -> anyhow::Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use std::ffi::c_int;
|
use std::ffi::c_int;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use fat_bits::dir::DirEntry;
|
||||||
use fuser::Filesystem;
|
use fuser::Filesystem;
|
||||||
use libc::{EIO, ENOENT, ENOSYS, ENOTDIR, EPERM};
|
use libc::{EIO, ENOENT, ENOSYS, EPERM};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
|
|
||||||
use crate::FatFuse;
|
use crate::FatFuse;
|
||||||
|
use crate::inode::Inode;
|
||||||
|
|
||||||
impl Filesystem for FatFuse {
|
impl Filesystem for FatFuse {
|
||||||
fn init(
|
fn init(
|
||||||
|
|
@ -28,35 +30,91 @@ impl Filesystem for FatFuse {
|
||||||
// warn!("[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name);
|
// warn!("[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name);
|
||||||
// reply.error(ENOSYS);
|
// reply.error(ENOSYS);
|
||||||
|
|
||||||
let parent_inode = if let Some(inode) = self.get_inode(parent) {
|
let Some(name) = name.to_str() else {
|
||||||
inode
|
// TODO: add proper handling of non-utf8 strings
|
||||||
} else {
|
reply.error(ENOSYS);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(parent_inode) = self.get_inode(parent) else {
|
||||||
// parent inode does not exist
|
// parent inode does not exist
|
||||||
// TODO: how can we make sure this does not exist?
|
// TODO: how can we make sure this does not happed?
|
||||||
// panic?
|
// TODO: panic?
|
||||||
reply.error(EIO);
|
reply.error(EIO);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(dir_iter) = parent_inode.dir_iter(&self.fat_fs) else {
|
// let Ok(mut dir_iter) = parent_inode.dir_iter(&self.fat_fs) else {
|
||||||
reply.error(ENOTDIR);
|
// reply.error(ENOTDIR);
|
||||||
return;
|
// return;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let Some(dir_entry) =
|
||||||
|
// dir_iter.find(|dir_entry| dir_entry.name_string().as_deref() == Some(name))
|
||||||
|
// else {
|
||||||
|
// reply.error(ENOENT);
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
|
||||||
|
let dir_entry: DirEntry = match parent_inode
|
||||||
|
.dir_iter(&self.fat_fs)
|
||||||
|
// .map_err(|_| ENOTDIR)
|
||||||
|
.and_then(|mut dir_iter| {
|
||||||
|
dir_iter
|
||||||
|
.find(|dir_entry| dir_entry.name_string().as_deref() == Some(name))
|
||||||
|
.ok_or(ENOENT)
|
||||||
|
}) {
|
||||||
|
Ok(dir_entry) => dir_entry,
|
||||||
|
Err(err) => {
|
||||||
|
reply.error(err);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(dir_entry) =
|
let inode = match self.get_inode_by_first_cluster(dir_entry.first_cluster()) {
|
||||||
dir_iter.find(|dir_entry| dir_entry.name_str().as_deref() == Some(""))
|
Some(inode) => inode,
|
||||||
else {
|
None => {
|
||||||
reply.error(ENOENT);
|
// no inode found, make a new one
|
||||||
return;
|
|
||||||
|
let ino = self.next_ino();
|
||||||
|
|
||||||
|
let inode = Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid);
|
||||||
|
|
||||||
|
self.insert_inode(inode)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
reply.entry(&Duration::from_secs(1), attr, generation);
|
let attr = inode.file_attr();
|
||||||
|
let generation = inode.generation();
|
||||||
|
|
||||||
todo!();
|
reply.entry(&Duration::from_secs(1), &attr, generation as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forget(&mut self, _req: &fuser::Request<'_>, _ino: u64, _nlookup: u64) {}
|
fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) {
|
||||||
|
let Some(inode) = self.get_inode_mut(ino) else {
|
||||||
|
debug!("tried to forget {} refs of inode {}, but was not found", ino, nlookup);
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ref_count = inode.ref_count_mut();
|
||||||
|
|
||||||
|
if *ref_count < nlookup {
|
||||||
|
debug!(
|
||||||
|
"tried to forget {} refs of inode {}, but ref_count is only {}",
|
||||||
|
nlookup, ino, *ref_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
*ref_count = ref_count.saturating_sub(nlookup);
|
||||||
|
|
||||||
|
if *ref_count == 0 {
|
||||||
|
// no more references, drop inode
|
||||||
|
self.drop_inode(ino);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn getattr(
|
fn getattr(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ use std::time::SystemTime;
|
||||||
|
|
||||||
use chrono::{NaiveDateTime, NaiveTime};
|
use chrono::{NaiveDateTime, NaiveTime};
|
||||||
use fat_bits::FatFs;
|
use fat_bits::FatFs;
|
||||||
use fat_bits::dir::{DirEntry, DirIter};
|
use fat_bits::dir::DirEntry;
|
||||||
use fuser::FileAttr;
|
use fuser::FileAttr;
|
||||||
|
use libc::ENOTDIR;
|
||||||
use rand::{Rng, SeedableRng as _};
|
use rand::{Rng, SeedableRng as _};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
|
|
@ -19,7 +20,10 @@ thread_local! {
|
||||||
static RNG: LazyCell<RefCell<rand::rngs::SmallRng>> = LazyCell::new(|| RefCell::new(rand::rngs::SmallRng::from_os_rng()));
|
static RNG: LazyCell<RefCell<rand::rngs::SmallRng>> = LazyCell::new(|| RefCell::new(rand::rngs::SmallRng::from_os_rng()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_random_u32() -> u32 {
|
fn get_random<T>() -> T
|
||||||
|
where
|
||||||
|
rand::distr::StandardUniform: rand::distr::Distribution<T>,
|
||||||
|
{
|
||||||
// RNG.with(|x| unsafe {
|
// RNG.with(|x| unsafe {
|
||||||
// let rng = &mut (*x.get());
|
// let rng = &mut (*x.get());
|
||||||
|
|
||||||
|
|
@ -50,8 +54,12 @@ const ROOT_INO: u64 = 1;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Inode {
|
pub struct Inode {
|
||||||
ino: u64,
|
ino: u64,
|
||||||
|
// FUSE uses a u64 for generation, but the Linux kernel only handles u32s anyway, truncating
|
||||||
|
// the high bits, so using more is pretty pointless and possibly even detrimental
|
||||||
generation: u32,
|
generation: u32,
|
||||||
|
|
||||||
|
ref_count: u64,
|
||||||
|
|
||||||
size: u64,
|
size: u64,
|
||||||
block_size: u32,
|
block_size: u32,
|
||||||
|
|
||||||
|
|
@ -72,10 +80,21 @@ pub struct Inode {
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl Inode {
|
impl Inode {
|
||||||
pub fn new(fat_fs: &FatFs, dir_entry: DirEntry, uid: u32, gid: u32) -> Inode {
|
fn new_generation() -> u32 {
|
||||||
|
let rand: u16 = get_random();
|
||||||
|
|
||||||
|
let secs = SystemTime::UNIX_EPOCH
|
||||||
|
.elapsed()
|
||||||
|
.map(|dur| dur.as_secs() as u16)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
((secs as u32) << 16) | rand as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(fat_fs: &FatFs, dir_entry: DirEntry, ino: u64, uid: u32, gid: u32) -> Inode {
|
||||||
assert!(dir_entry.is_file() || dir_entry.is_dir());
|
assert!(dir_entry.is_file() || dir_entry.is_dir());
|
||||||
|
|
||||||
let generation = get_random_u32();
|
let generation = Self::new_generation();
|
||||||
|
|
||||||
let kind = if dir_entry.is_dir() {
|
let kind = if dir_entry.is_dir() {
|
||||||
Kind::Dir
|
Kind::Dir
|
||||||
|
|
@ -96,8 +115,9 @@ impl Inode {
|
||||||
let crtime = datetime_to_system(dir_entry.create_time());
|
let crtime = datetime_to_system(dir_entry.create_time());
|
||||||
|
|
||||||
Inode {
|
Inode {
|
||||||
ino: dir_entry.first_cluster() as u64,
|
ino,
|
||||||
generation,
|
generation,
|
||||||
|
ref_count: 0,
|
||||||
size: dir_entry.file_size() as u64,
|
size: dir_entry.file_size() as u64,
|
||||||
block_size: fat_fs.bpb().bytes_per_sector() as u32,
|
block_size: fat_fs.bpb().bytes_per_sector() as u32,
|
||||||
kind,
|
kind,
|
||||||
|
|
@ -111,6 +131,26 @@ impl Inode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ino(&self) -> u64 {
|
||||||
|
self.ino
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generation(&self) -> u32 {
|
||||||
|
self.generation
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ref_count(&self) -> u64 {
|
||||||
|
self.ref_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ref_count_mut(&mut self) -> &mut u64 {
|
||||||
|
&mut self.ref_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn first_cluster(&self) -> u32 {
|
||||||
|
self.first_cluster
|
||||||
|
}
|
||||||
|
|
||||||
pub fn file_attr(&self) -> FileAttr {
|
pub fn file_attr(&self) -> FileAttr {
|
||||||
let perm = if self.read_only { 0o555 } else { 0o777 };
|
let perm = if self.read_only { 0o555 } else { 0o777 };
|
||||||
|
|
||||||
|
|
@ -133,11 +173,12 @@ impl Inode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dir_iter(&self, fat_fs: &FatFs) -> anyhow::Result<impl Iterator<Item = DirEntry>> {
|
pub fn dir_iter(&self, fat_fs: &FatFs) -> Result<impl Iterator<Item = DirEntry>, i32> {
|
||||||
anyhow::ensure!(self.kind == Kind::Dir, "cannot dir_iter on a file");
|
// anyhow::ensure!(self.kind == Kind::Dir, "cannot dir_iter on a file");
|
||||||
|
|
||||||
// TODO: the boxing here is not particularly pretty, but neccessary, since the DirIter for
|
if self.kind != Kind::Dir {
|
||||||
// the root holds a
|
return Err(ENOTDIR);
|
||||||
|
}
|
||||||
|
|
||||||
if self.ino == ROOT_INO {
|
if self.ino == ROOT_INO {
|
||||||
// root dir
|
// root dir
|
||||||
|
|
@ -145,9 +186,6 @@ impl Inode {
|
||||||
return Ok(fat_fs.root_dir_iter());
|
return Ok(fat_fs.root_dir_iter());
|
||||||
}
|
}
|
||||||
|
|
||||||
let chain_reader = fat_fs.chain_reader(self.first_cluster);
|
Ok(fat_fs.dir_iter(self.first_cluster))
|
||||||
|
|
||||||
// TODO: get rid of this Box if the boxing is removed from root_dir_iter
|
|
||||||
Ok(DirIter::new(Box::new(chain_reader)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use std::collections::BTreeMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fat_bits::{FatFs, SliceLike};
|
use fat_bits::{FatFs, SliceLike};
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
use crate::inode::Inode;
|
use crate::inode::Inode;
|
||||||
|
|
||||||
|
|
@ -16,9 +17,12 @@ pub struct FatFuse {
|
||||||
uid: u32,
|
uid: u32,
|
||||||
gid: u32,
|
gid: u32,
|
||||||
|
|
||||||
|
next_ino: u64,
|
||||||
next_fd: u32,
|
next_fd: u32,
|
||||||
|
|
||||||
inode_table: BTreeMap<u64, Inode>,
|
inode_table: BTreeMap<u64, Inode>,
|
||||||
|
|
||||||
|
ino_by_first_cluster: BTreeMap<u32, u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FatFuse {
|
impl FatFuse {
|
||||||
|
|
@ -28,15 +32,105 @@ impl FatFuse {
|
||||||
|
|
||||||
let fat_fs = FatFs::load(data)?;
|
let fat_fs = FatFs::load(data)?;
|
||||||
|
|
||||||
|
// TODO: build and insert root dir inode
|
||||||
|
|
||||||
Ok(FatFuse {
|
Ok(FatFuse {
|
||||||
fat_fs,
|
fat_fs,
|
||||||
uid,
|
uid,
|
||||||
gid,
|
gid,
|
||||||
|
next_ino: 2, // 0 is reserved and 1 is root
|
||||||
next_fd: 0,
|
next_fd: 0,
|
||||||
inode_table: BTreeMap::new(),
|
inode_table: BTreeMap::new(),
|
||||||
|
ino_by_first_cluster: BTreeMap::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_ino(&mut self) -> u64 {
|
||||||
|
let ino = self.next_ino;
|
||||||
|
|
||||||
|
assert!(!self.inode_table.contains_key(&ino));
|
||||||
|
|
||||||
|
self.next_ino += 1;
|
||||||
|
|
||||||
|
ino
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_inode(&mut self, inode: Inode) -> &mut Inode {
|
||||||
|
let ino = inode.ino();
|
||||||
|
let generation = inode.generation();
|
||||||
|
let first_cluster = inode.first_cluster();
|
||||||
|
|
||||||
|
// let old_inode = self.inode_table.insert(ino, inode);
|
||||||
|
|
||||||
|
let entry = self.inode_table.entry(ino);
|
||||||
|
|
||||||
|
let (new_inode, old_inode): (&mut Inode, Option<Inode>) = match entry {
|
||||||
|
std::collections::btree_map::Entry::Vacant(vacant_entry) => {
|
||||||
|
let new_inode = vacant_entry.insert(inode);
|
||||||
|
(new_inode, None)
|
||||||
|
}
|
||||||
|
std::collections::btree_map::Entry::Occupied(occupied_entry) => {
|
||||||
|
let entry_ref = occupied_entry.into_mut();
|
||||||
|
|
||||||
|
let old_inode = std::mem::replace(entry_ref, inode);
|
||||||
|
|
||||||
|
(entry_ref, Some(old_inode))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let old_ino = self.ino_by_first_cluster.insert(first_cluster, ino);
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"inserted new inode with ino {} and generation {} (first cluster: {})",
|
||||||
|
ino, generation, first_cluster
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(old_inode) = old_inode {
|
||||||
|
debug!("ejected inode {} {}", old_inode.ino(), old_inode.generation());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(old_ino) = old_ino {
|
||||||
|
debug!("ejected old {} -> {} cluster to ino mapping", first_cluster, old_ino);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_inode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop_inode(&mut self, ino: u64) {
|
||||||
|
let Some(inode) = self.inode_table.remove(&ino) else {
|
||||||
|
debug!("tried to drop inode with ino {}, but was not in table", ino);
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let first_cluster = inode.first_cluster();
|
||||||
|
|
||||||
|
let entry = self.ino_by_first_cluster.entry(first_cluster);
|
||||||
|
|
||||||
|
match entry {
|
||||||
|
std::collections::btree_map::Entry::Vacant(_) => debug!(
|
||||||
|
"removed inode with ino {} from table, but it's first cluster did not point to any ino",
|
||||||
|
ino
|
||||||
|
),
|
||||||
|
std::collections::btree_map::Entry::Occupied(occupied_entry) => {
|
||||||
|
let found_ino = *occupied_entry.get();
|
||||||
|
|
||||||
|
if found_ino == ino {
|
||||||
|
// matches our inode, remove it
|
||||||
|
occupied_entry.remove();
|
||||||
|
} else {
|
||||||
|
// does not match removed inode, leave it as is
|
||||||
|
debug!(
|
||||||
|
"removed inode with ino {} from table, but it's first cluster pointer to ino {} instead",
|
||||||
|
ino, found_ino
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_inode(&self, ino: u64) -> Option<&Inode> {
|
fn get_inode(&self, ino: u64) -> Option<&Inode> {
|
||||||
self.inode_table.get(&ino)
|
self.inode_table.get(&ino)
|
||||||
}
|
}
|
||||||
|
|
@ -44,4 +138,34 @@ impl FatFuse {
|
||||||
fn get_inode_mut(&mut self, ino: u64) -> Option<&mut Inode> {
|
fn get_inode_mut(&mut self, ino: u64) -> Option<&mut Inode> {
|
||||||
self.inode_table.get_mut(&ino)
|
self.inode_table.get_mut(&ino)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_inode_by_first_cluster(&self, first_cluster: u32) -> Option<&Inode> {
|
||||||
|
let ino = self.ino_by_first_cluster.get(&first_cluster)?;
|
||||||
|
|
||||||
|
if let Some(inode) = self.inode_table.get(ino) {
|
||||||
|
Some(inode)
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"first cluster {} is mapped to ino {}, but inode is not in table",
|
||||||
|
first_cluster, ino
|
||||||
|
);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_inode_by_first_cluster_mut(&mut self, first_cluster: u32) -> Option<&mut Inode> {
|
||||||
|
let ino = self.ino_by_first_cluster.get(&first_cluster)?;
|
||||||
|
|
||||||
|
if let Some(inode) = self.inode_table.get_mut(ino) {
|
||||||
|
Some(inode)
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"first cluster {} is mapped to ino {}, but inode is not in table",
|
||||||
|
first_cluster, ino
|
||||||
|
);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue