look up inodes by path
also DirEntry::name now no longer returns option invalid chars get replaced by ?
This commit is contained in:
parent
7f6c304709
commit
19340bd4ee
6 changed files with 291 additions and 44 deletions
53
Cargo.lock
generated
53
Cargo.lock
generated
|
|
@ -100,6 +100,12 @@ 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 = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "castaway"
|
name = "castaway"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
@ -162,6 +168,16 @@ dependencies = [
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compact_string"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5255b88d8ea09573f588088dea17fbea682b4442abea6761a15d1da2c3a76c5c"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
|
|
@ -223,7 +239,7 @@ dependencies = [
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"enum_dispatch",
|
"enum_dispatch",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -240,12 +256,14 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"compact_string",
|
||||||
"fat-bits",
|
"fat-bits",
|
||||||
"fuser",
|
"fuser",
|
||||||
|
"fxhash",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -275,6 +293,15 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fxhash"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
@ -583,13 +610,33 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 2.0.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ pub struct DirEntry {
|
||||||
|
|
||||||
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>".into());
|
let mut name = self.name_string();
|
||||||
|
|
||||||
if self.attr.contains(Attr::Directory) {
|
if self.attr.contains(Attr::Directory) {
|
||||||
name.push('/');
|
name.push('/');
|
||||||
|
|
@ -266,16 +266,19 @@ impl DirEntry {
|
||||||
std::str::from_utf8(self.extension()).ok()
|
std::str::from_utf8(self.extension()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name_string(&self) -> Option<CompactString> {
|
pub fn name_string(&self) -> CompactString {
|
||||||
// use a CompactString here to allow inlining of short names
|
// 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
|
// maybe switch to a Cow instead? has disadvantage that we need to alloc for short names
|
||||||
|
|
||||||
|
// can't be empty
|
||||||
|
assert!(!self.is_empty());
|
||||||
|
|
||||||
if let Some(long_filename) = self.long_name() {
|
if let Some(long_filename) = self.long_name() {
|
||||||
return Some(long_filename.into());
|
return long_filename.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = std::str::from_utf8(&self.name[..8]).ok()?.trim_ascii_end();
|
let name = &self.name[..8];
|
||||||
let ext = std::str::from_utf8(&self.name[8..]).ok()?.trim_ascii_end();
|
let ext = &self.name[8..];
|
||||||
|
|
||||||
let mut s = CompactString::const_new("");
|
let mut s = CompactString::const_new("");
|
||||||
|
|
||||||
|
|
@ -283,15 +286,50 @@ impl DirEntry {
|
||||||
s.push('.');
|
s.push('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
s += name;
|
// s += name;
|
||||||
|
|
||||||
|
// for &c in self.name[..8].trim_ascii_end() {
|
||||||
|
// // stem
|
||||||
|
|
||||||
|
// if !c.is_ascii()
|
||||||
|
// || c < 0x20
|
||||||
|
// || !(c.is_ascii_alphanumeric() || VALID_SYMBOLS.contains(&c))
|
||||||
|
// {
|
||||||
|
// // replace invalid character
|
||||||
|
// // characters above 127 are also ignored, even tho allowed
|
||||||
|
// s.push('?');
|
||||||
|
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// s.push(c as char);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const VALID_SYMBOLS: &[u8] = &[
|
||||||
|
b'$', b'%', b'\'', b'-', b'_', b'@', b'~', b'`', b'!', b'(', b')', b'{', b'}', b'^',
|
||||||
|
b'#', b'&',
|
||||||
|
];
|
||||||
|
|
||||||
|
fn map_chars(c: u8) -> char {
|
||||||
|
if !c.is_ascii()
|
||||||
|
|| c < 0x20
|
||||||
|
|| !(c.is_ascii_alphanumeric() || VALID_SYMBOLS.contains(&c))
|
||||||
|
{
|
||||||
|
'?'
|
||||||
|
} else {
|
||||||
|
c as char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.extend(name.trim_ascii_end().iter().copied().map(map_chars));
|
||||||
|
|
||||||
if !ext.is_empty() {
|
if !ext.is_empty() {
|
||||||
s.push('.');
|
s.push('.');
|
||||||
|
|
||||||
s += ext;
|
s.extend(ext.trim_ascii_end().iter().copied().map(map_chars));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(s)
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn long_name(&self) -> Option<&str> {
|
pub fn long_name(&self) -> Option<&str> {
|
||||||
|
|
@ -463,6 +501,11 @@ impl LongFilenameBuf {
|
||||||
if dir_entry.is_last() {
|
if dir_entry.is_last() {
|
||||||
// first/lasts entry
|
// first/lasts entry
|
||||||
|
|
||||||
|
anyhow::ensure!(
|
||||||
|
dir_entry.ordinal() <= 20,
|
||||||
|
"can't have more than 20 long filename dir entries"
|
||||||
|
);
|
||||||
|
|
||||||
let mut name = dir_entry.name();
|
let mut name = dir_entry.name();
|
||||||
|
|
||||||
while name.last() == Some(&0xFFFF) {
|
while name.last() == Some(&0xFFFF) {
|
||||||
|
|
@ -485,7 +528,7 @@ impl LongFilenameBuf {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(self.checksum.is_some());
|
assert!(self.checksum.is_some() && self.last_ordinal.is_some());
|
||||||
|
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
self.checksum == Some(dir_entry.checksum()),
|
self.checksum == Some(dir_entry.checksum()),
|
||||||
|
|
@ -535,6 +578,8 @@ impl LongFilenameBuf {
|
||||||
self.checksum.unwrap()
|
self.checksum.unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
anyhow::ensure!(self.rev_buf.len() <= 255, "long filename too long");
|
||||||
|
|
||||||
Ok(Some(self.rev_buf.iter().copied().rev()))
|
Ok(Some(self.rev_buf.iter().copied().rev()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -590,7 +635,7 @@ impl<R: Read> DirIter<R> {
|
||||||
.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(),
|
||||||
e
|
e
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@ edition = "2024"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] }
|
chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] }
|
||||||
|
compact_string = "0.1.0"
|
||||||
fat-bits = { version = "0.1.0", path = "../fat-bits" }
|
fat-bits = { version = "0.1.0", path = "../fat-bits" }
|
||||||
fuser = "0.15.1"
|
fuser = "0.15.1"
|
||||||
|
fxhash = "0.2.1"
|
||||||
libc = "0.2.174"
|
libc = "0.2.174"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
rand = { version = "0.9.2", default-features = false, features = ["os_rng", "small_rng"] }
|
rand = { version = "0.9.2", default-features = false, features = ["os_rng", "small_rng"] }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::ffi::c_int;
|
use std::ffi::c_int;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use fat_bits::dir::DirEntry;
|
use fat_bits::dir::DirEntry;
|
||||||
|
|
@ -28,9 +29,6 @@ impl Filesystem for FatFuse {
|
||||||
name: &std::ffi::OsStr,
|
name: &std::ffi::OsStr,
|
||||||
reply: fuser::ReplyEntry,
|
reply: fuser::ReplyEntry,
|
||||||
) {
|
) {
|
||||||
// warn!("[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name);
|
|
||||||
// reply.error(ENOSYS);
|
|
||||||
|
|
||||||
let Some(name) = name.to_str() else {
|
let Some(name) = name.to_str() else {
|
||||||
// TODO: add proper handling of non-utf8 strings
|
// TODO: add proper handling of non-utf8 strings
|
||||||
debug!("cannot convert OsStr {:?} to str", name);
|
debug!("cannot convert OsStr {:?} to str", name);
|
||||||
|
|
@ -67,7 +65,7 @@ impl Filesystem for FatFuse {
|
||||||
// .map_err(|_| ENOTDIR)
|
// .map_err(|_| ENOTDIR)
|
||||||
.and_then(|mut dir_iter| {
|
.and_then(|mut dir_iter| {
|
||||||
dir_iter
|
dir_iter
|
||||||
.find(|dir_entry| dir_entry.name_string().as_deref() == Some(name))
|
.find(|dir_entry| &dir_entry.name_string() == name)
|
||||||
.ok_or(ENOENT)
|
.ok_or(ENOENT)
|
||||||
}) {
|
}) {
|
||||||
Ok(dir_entry) => dir_entry,
|
Ok(dir_entry) => dir_entry,
|
||||||
|
|
@ -91,17 +89,23 @@ impl Filesystem for FatFuse {
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
let inode = self.get_or_make_inode_by_dir_entry(&dir_entry);
|
let inode = self.get_or_make_inode_by_dir_entry(
|
||||||
|
&dir_entry,
|
||||||
|
parent_inode.ino(),
|
||||||
|
parent_inode.path(),
|
||||||
|
);
|
||||||
|
|
||||||
let attr = inode.file_attr();
|
let attr = inode.file_attr();
|
||||||
let generation = inode.generation();
|
let generation = inode.generation();
|
||||||
|
|
||||||
reply.entry(&TTL, &attr, generation as u64);
|
reply.entry(&TTL, &attr, generation as u64);
|
||||||
|
|
||||||
inode.refcount_inc();
|
inode.inc_ref_count();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) {
|
fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) {
|
||||||
|
debug!("forgetting ino {} ({} times)", ino, nlookup);
|
||||||
|
|
||||||
let Some(inode) = self.get_inode_mut(ino) else {
|
let Some(inode) = self.get_inode_mut(ino) else {
|
||||||
debug!("tried to forget {} refs of inode {}, but was not found", ino, nlookup);
|
debug!("tried to forget {} refs of inode {}, but was not found", ino, nlookup);
|
||||||
|
|
||||||
|
|
@ -110,7 +114,9 @@ impl Filesystem for FatFuse {
|
||||||
|
|
||||||
// *ref_count = ref_count.saturating_sub(nlookup);
|
// *ref_count = ref_count.saturating_sub(nlookup);
|
||||||
|
|
||||||
if inode.refcount_dec(nlookup) == 0 {
|
if inode.dec_ref_count(nlookup) == 0 {
|
||||||
|
debug!("dropping inode with ino {}", inode.ino());
|
||||||
|
|
||||||
// no more references, drop inode
|
// no more references, drop inode
|
||||||
self.drop_inode(ino);
|
self.drop_inode(ino);
|
||||||
}
|
}
|
||||||
|
|
@ -380,17 +386,17 @@ impl Filesystem for FatFuse {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(inode) = self.get_inode_by_fh(fh) else {
|
let Some(dir_inode) = self.get_inode_by_fh(fh) else {
|
||||||
debug!("could not find inode accociated with fh {} (ino: {})", fh, ino);
|
debug!("could not find inode accociated with fh {} (ino: {})", fh, ino);
|
||||||
|
|
||||||
reply.error(EINVAL);
|
reply.error(EINVAL);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if inode.ino() != ino {
|
if dir_inode.ino() != ino {
|
||||||
debug!(
|
debug!(
|
||||||
"ino {} of inode associated with fh {} does not match given ino {}",
|
"ino {} of inode associated with fh {} does not match given ino {}",
|
||||||
inode.ino(),
|
dir_inode.ino(),
|
||||||
fh,
|
fh,
|
||||||
ino
|
ino
|
||||||
);
|
);
|
||||||
|
|
@ -406,7 +412,7 @@ impl Filesystem for FatFuse {
|
||||||
next_idx
|
next_idx
|
||||||
};
|
};
|
||||||
|
|
||||||
if inode.is_root() {
|
if dir_inode.is_root() {
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
debug!("adding . to root dir");
|
debug!("adding . to root dir");
|
||||||
if reply.add(1, next_offset(), FileType::Directory, ".") {
|
if reply.add(1, next_offset(), FileType::Directory, ".") {
|
||||||
|
|
@ -426,7 +432,7 @@ impl Filesystem for FatFuse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(dir_iter) = inode.dir_iter(&self.fat_fs) else {
|
let Ok(dir_iter) = dir_inode.dir_iter(&self.fat_fs) else {
|
||||||
reply.error(ENOTDIR);
|
reply.error(ENOTDIR);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -435,10 +441,14 @@ impl Filesystem for FatFuse {
|
||||||
// also skip over `offset` entries
|
// also skip over `offset` entries
|
||||||
let dirs: Vec<DirEntry> = dir_iter.skip(offset).collect();
|
let dirs: Vec<DirEntry> = dir_iter.skip(offset).collect();
|
||||||
|
|
||||||
for dir_entry in dirs {
|
let dir_ino = dir_inode.ino();
|
||||||
let name = dir_entry.name_string().unwrap_or("<invalid>".into());
|
let dir_path = dir_inode.path();
|
||||||
|
|
||||||
let inode: &Inode = self.get_or_make_inode_by_dir_entry(&dir_entry);
|
for dir_entry in dirs {
|
||||||
|
let name = dir_entry.name_string();
|
||||||
|
|
||||||
|
let inode: &Inode =
|
||||||
|
self.get_or_make_inode_by_dir_entry(&dir_entry, dir_ino, Rc::clone(&dir_path));
|
||||||
|
|
||||||
debug!("adding entry {} (ino: {})", name, inode.ino());
|
debug!("adding entry {} (ino: {})", name, inode.ino());
|
||||||
if reply.add(ino, next_offset(), inode.kind().into(), name) {
|
if reply.add(ino, next_offset(), inode.kind().into(), name) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::cell::{LazyCell, RefCell};
|
use std::cell::{LazyCell, RefCell};
|
||||||
|
use std::rc::Rc;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use chrono::{NaiveDateTime, NaiveTime};
|
use chrono::{NaiveDateTime, NaiveTime};
|
||||||
|
|
@ -49,7 +50,7 @@ impl From<Kind> for fuser::FileType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROOT_INO: u64 = 1;
|
pub const ROOT_INO: u64 = 1;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
@ -61,6 +62,8 @@ pub struct Inode {
|
||||||
|
|
||||||
ref_count: u64,
|
ref_count: u64,
|
||||||
|
|
||||||
|
parent_ino: u64,
|
||||||
|
|
||||||
size: u64,
|
size: u64,
|
||||||
block_size: u32,
|
block_size: u32,
|
||||||
|
|
||||||
|
|
@ -77,6 +80,8 @@ pub struct Inode {
|
||||||
gid: u32,
|
gid: u32,
|
||||||
|
|
||||||
first_cluster: u32,
|
first_cluster: u32,
|
||||||
|
|
||||||
|
path: Rc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
@ -92,7 +97,15 @@ impl Inode {
|
||||||
((secs as u32) << 16) | rand as u32
|
((secs as u32) << 16) | rand as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(fat_fs: &FatFs, dir_entry: &DirEntry, ino: u64, uid: u32, gid: u32) -> Inode {
|
pub fn new(
|
||||||
|
fat_fs: &FatFs,
|
||||||
|
dir_entry: &DirEntry,
|
||||||
|
ino: u64,
|
||||||
|
uid: u32,
|
||||||
|
gid: u32,
|
||||||
|
path: impl Into<Rc<str>>,
|
||||||
|
parent_ino: u64,
|
||||||
|
) -> Inode {
|
||||||
assert!(dir_entry.is_file() || dir_entry.is_dir());
|
assert!(dir_entry.is_file() || dir_entry.is_dir());
|
||||||
|
|
||||||
let generation = Self::new_generation();
|
let generation = Self::new_generation();
|
||||||
|
|
@ -115,16 +128,20 @@ impl Inode {
|
||||||
let mtime = datetime_to_system(dir_entry.write_time());
|
let mtime = datetime_to_system(dir_entry.write_time());
|
||||||
let crtime = datetime_to_system(dir_entry.create_time());
|
let crtime = datetime_to_system(dir_entry.create_time());
|
||||||
|
|
||||||
|
let path = path.into();
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"creating new inode: ino: {} name: {}",
|
"creating new inode: ino: {} name: {} path: {}",
|
||||||
ino,
|
ino,
|
||||||
dir_entry.name_string().unwrap_or("<invalid>".into())
|
dir_entry.name_string(),
|
||||||
|
path
|
||||||
);
|
);
|
||||||
|
|
||||||
Inode {
|
Inode {
|
||||||
ino,
|
ino,
|
||||||
generation,
|
generation,
|
||||||
ref_count: 0,
|
ref_count: 0,
|
||||||
|
parent_ino,
|
||||||
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,
|
||||||
|
|
@ -135,6 +152,7 @@ impl Inode {
|
||||||
uid,
|
uid,
|
||||||
gid,
|
gid,
|
||||||
first_cluster: dir_entry.first_cluster(),
|
first_cluster: dir_entry.first_cluster(),
|
||||||
|
path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,6 +165,7 @@ impl Inode {
|
||||||
ino: 1,
|
ino: 1,
|
||||||
generation: 0,
|
generation: 0,
|
||||||
ref_count: 0,
|
ref_count: 0,
|
||||||
|
parent_ino: ROOT_INO, // parent is self
|
||||||
size: 0,
|
size: 0,
|
||||||
block_size: fat_fs.bpb().bytes_per_sector() as u32,
|
block_size: fat_fs.bpb().bytes_per_sector() as u32,
|
||||||
kind: Kind::Dir,
|
kind: Kind::Dir,
|
||||||
|
|
@ -157,6 +176,7 @@ impl Inode {
|
||||||
uid,
|
uid,
|
||||||
gid,
|
gid,
|
||||||
first_cluster: root_cluster,
|
first_cluster: root_cluster,
|
||||||
|
path: "/".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,11 +192,24 @@ impl Inode {
|
||||||
self.ref_count
|
self.ref_count
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refcount_inc(&mut self) {
|
pub fn inc_ref_count(&mut self) {
|
||||||
|
debug!(
|
||||||
|
"increasing ref_count of ino {} by 1 (new ref_count: {})",
|
||||||
|
self.ino(),
|
||||||
|
self.ref_count() + 1
|
||||||
|
);
|
||||||
|
|
||||||
self.ref_count += 1;
|
self.ref_count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refcount_dec(&mut self, n: u64) -> u64 {
|
pub fn dec_ref_count(&mut self, n: u64) -> u64 {
|
||||||
|
debug!(
|
||||||
|
"decreasing ref_count of ino {} by {} (new ref_count: {})",
|
||||||
|
self.ino(),
|
||||||
|
n,
|
||||||
|
self.ref_count().saturating_sub(n),
|
||||||
|
);
|
||||||
|
|
||||||
if self.ref_count < n {
|
if self.ref_count < n {
|
||||||
debug!(
|
debug!(
|
||||||
"inode {}: tried to decrement refcount by {}, but is only {}",
|
"inode {}: tried to decrement refcount by {}, but is only {}",
|
||||||
|
|
@ -191,6 +224,10 @@ impl Inode {
|
||||||
self.ref_count
|
self.ref_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parent_ino(&self) -> u64 {
|
||||||
|
self.parent_ino
|
||||||
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> Kind {
|
pub fn kind(&self) -> Kind {
|
||||||
self.kind
|
self.kind
|
||||||
}
|
}
|
||||||
|
|
@ -199,6 +236,10 @@ impl Inode {
|
||||||
self.first_cluster
|
self.first_cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> Rc<str> {
|
||||||
|
Rc::clone(&self.path)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_root(&self) -> bool {
|
pub fn is_root(&self) -> bool {
|
||||||
self.ino == ROOT_INO
|
self.ino == ROOT_INO
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ mod fuse;
|
||||||
mod inode;
|
mod inode;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fat_bits::dir::DirEntry;
|
use fat_bits::dir::DirEntry;
|
||||||
use fat_bits::{FatFs, SliceLike};
|
use fat_bits::{FatFs, SliceLike};
|
||||||
|
use fxhash::FxHashMap;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
use crate::inode::Inode;
|
use crate::inode::Inode;
|
||||||
|
|
@ -23,6 +25,7 @@ pub struct FatFuse {
|
||||||
|
|
||||||
ino_by_first_cluster: BTreeMap<u32, u64>,
|
ino_by_first_cluster: BTreeMap<u32, u64>,
|
||||||
ino_by_fh: BTreeMap<u64, u64>,
|
ino_by_fh: BTreeMap<u64, u64>,
|
||||||
|
ino_by_path: FxHashMap<Rc<str>, u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for FatFuse {
|
impl Drop for FatFuse {
|
||||||
|
|
@ -35,9 +38,16 @@ impl Drop for FatFuse {
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("ino_by_fh: {}", self.ino_by_fh.len());
|
println!("ino_by_fh: {}", self.ino_by_fh.len());
|
||||||
|
|
||||||
|
println!("ino_by_path: {}", self.ino_by_path.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SAFETY
|
||||||
|
///
|
||||||
|
/// do NOT leak Rc<str> from this type
|
||||||
|
unsafe impl Send for FatFuse {}
|
||||||
|
|
||||||
impl FatFuse {
|
impl FatFuse {
|
||||||
pub fn new<S>(data: S) -> anyhow::Result<FatFuse>
|
pub fn new<S>(data: S) -> anyhow::Result<FatFuse>
|
||||||
where
|
where
|
||||||
|
|
@ -57,6 +67,7 @@ impl FatFuse {
|
||||||
inode_table: BTreeMap::new(),
|
inode_table: BTreeMap::new(),
|
||||||
ino_by_first_cluster: BTreeMap::new(),
|
ino_by_first_cluster: BTreeMap::new(),
|
||||||
ino_by_fh: BTreeMap::new(),
|
ino_by_fh: BTreeMap::new(),
|
||||||
|
ino_by_path: FxHashMap::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: build and insert root dir inode
|
// TODO: build and insert root dir inode
|
||||||
|
|
@ -126,6 +137,10 @@ impl FatFuse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(old_ino) = self.ino_by_path.insert(new_inode.path(), ino) {
|
||||||
|
debug!("ejected old {} -> {} path to ino mapping", new_inode.path(), old_ino);
|
||||||
|
}
|
||||||
|
|
||||||
new_inode
|
new_inode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,13 +172,45 @@ impl FatFuse {
|
||||||
} else {
|
} else {
|
||||||
// does not match removed inode, leave it as is
|
// does not match removed inode, leave it as is
|
||||||
debug!(
|
debug!(
|
||||||
"removed inode with ino {} from table, but it's first cluster pointer to ino {} instead",
|
"removed inode with ino {} from table, but its first cluster pointed to ino {} instead",
|
||||||
ino, found_ino
|
ino, found_ino
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let entry = self.ino_by_path.entry(inode.path());
|
||||||
|
|
||||||
|
match entry {
|
||||||
|
std::collections::hash_map::Entry::Vacant(_) => debug!(
|
||||||
|
"removed inode with ino {} from table, but it's path did not point to any ino",
|
||||||
|
ino
|
||||||
|
),
|
||||||
|
std::collections::hash_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 its path pointed to ino {} instead",
|
||||||
|
ino, found_ino
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(parent_inode) = self.get_inode_mut(inode.parent_ino()) else {
|
||||||
|
panic!("parent inode {} does not exists anymore", inode.parent_ino());
|
||||||
|
};
|
||||||
|
|
||||||
|
// dec refcount
|
||||||
|
parent_inode.dec_ref_count(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_inode(&self, ino: u64) -> Option<&Inode> {
|
fn get_inode(&self, ino: u64) -> Option<&Inode> {
|
||||||
|
|
@ -174,20 +221,51 @@ impl FatFuse {
|
||||||
self.inode_table.get_mut(&ino)
|
self.inode_table.get_mut(&ino)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_or_make_inode_by_dir_entry(&mut self, dir_entry: &DirEntry) -> &mut Inode {
|
fn get_or_make_inode_by_dir_entry(
|
||||||
if self
|
&mut self,
|
||||||
.get_inode_by_first_cluster_mut(dir_entry.first_cluster())
|
dir_entry: &DirEntry,
|
||||||
.is_some()
|
parent_ino: u64,
|
||||||
|
parent_path: Rc<str>,
|
||||||
|
) -> &mut Inode {
|
||||||
|
if dir_entry.first_cluster() != 0
|
||||||
|
&& self
|
||||||
|
.get_inode_by_first_cluster_mut(dir_entry.first_cluster())
|
||||||
|
.is_some()
|
||||||
{
|
{
|
||||||
return self
|
return self
|
||||||
.get_inode_by_first_cluster_mut(dir_entry.first_cluster())
|
.get_inode_by_first_cluster_mut(dir_entry.first_cluster())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let path = {
|
||||||
|
let mut path = parent_path.as_ref().to_owned();
|
||||||
|
|
||||||
|
if parent_ino != inode::ROOT_INO {
|
||||||
|
// root inode already has trailing slash
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
path += &dir_entry.name_string();
|
||||||
|
|
||||||
|
path
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.get_inode_by_path_mut(&path).is_some() {
|
||||||
|
return self.get_inode_by_path_mut(&path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// no inode found, make a new one
|
// no inode found, make a new one
|
||||||
let ino = self.next_ino();
|
let ino = self.next_ino();
|
||||||
|
|
||||||
let inode = Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid);
|
let Some(parent_inode) = self.get_inode_mut(parent_ino) else {
|
||||||
|
// TODO: what do we do here? should not happen
|
||||||
|
panic!("parent_ino {} does not lead to inode", parent_ino);
|
||||||
|
};
|
||||||
|
|
||||||
|
// inc ref of parent
|
||||||
|
parent_inode.inc_ref_count();
|
||||||
|
|
||||||
|
let inode = Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid, path, parent_ino);
|
||||||
|
|
||||||
self.insert_inode(inode)
|
self.insert_inode(inode)
|
||||||
}
|
}
|
||||||
|
|
@ -235,9 +313,9 @@ impl FatFuse {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_inode_by_fh(&self, fh: u64) -> Option<&Inode> {
|
pub fn get_inode_by_fh(&self, fh: u64) -> Option<&Inode> {
|
||||||
let ino = self.ino_by_fh.get(&fh)?;
|
let ino = *self.ino_by_fh.get(&fh)?;
|
||||||
|
|
||||||
if let Some(inode) = self.inode_table.get(ino) {
|
if let Some(inode) = self.get_inode(ino) {
|
||||||
Some(inode)
|
Some(inode)
|
||||||
} else {
|
} else {
|
||||||
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
|
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
|
||||||
|
|
@ -247,9 +325,9 @@ impl FatFuse {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_inode_by_fh_mut(&mut self, fh: u64) -> Option<&mut Inode> {
|
pub fn get_inode_by_fh_mut(&mut self, fh: u64) -> Option<&mut Inode> {
|
||||||
let ino = self.ino_by_fh.get(&fh)?;
|
let ino = *self.ino_by_fh.get(&fh)?;
|
||||||
|
|
||||||
if let Some(inode) = self.inode_table.get_mut(ino) {
|
if let Some(inode) = self.get_inode_mut(ino) {
|
||||||
Some(inode)
|
Some(inode)
|
||||||
} else {
|
} else {
|
||||||
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
|
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
|
||||||
|
|
@ -257,4 +335,28 @@ impl FatFuse {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_inode_by_path(&self, path: &str) -> Option<&Inode> {
|
||||||
|
let ino = *self.ino_by_path.get(path)?;
|
||||||
|
|
||||||
|
if let Some(inode) = self.get_inode(ino) {
|
||||||
|
Some(inode)
|
||||||
|
} else {
|
||||||
|
debug!("path {} is mapped to ino {}, but inode is not in table", path, ino);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_inode_by_path_mut(&mut self, path: &str) -> Option<&mut Inode> {
|
||||||
|
let ino = *self.ino_by_path.get(path)?;
|
||||||
|
|
||||||
|
if let Some(inode) = self.get_inode_mut(ino) {
|
||||||
|
Some(inode)
|
||||||
|
} else {
|
||||||
|
debug!("path {} is mapped to ino {}, but inode is not in table", path, ino);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue