diff --git a/Cargo.lock b/Cargo.lock index ea4134f..2d3355e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "libc" version = "0.2.186" @@ -149,6 +155,7 @@ dependencies = [ "ctrlc", "fuser", "libc", + "serde_json", "time", ] @@ -333,6 +340,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -353,6 +369,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "smallvec" version = "1.15.2" @@ -490,3 +519,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 1b8b6b7..85b30fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ edition = "2024" ctrlc = "3.5.2" fuser = "0.17.0" libc = "0.2.186" +serde_json = "1.0.150" time = "0.3.49" diff --git a/src/main.rs b/src/main.rs index a6f0b71..aebeb7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,24 @@ use fuser::{ - Errno, FileAttr, FileType, Filesystem, INodeNo, ReplyAttr, ReplyData, ReplyDirectory, - ReplyEntry, Request, + Errno, FileAttr, FileType, Filesystem, Generation, INodeNo, ReplyAttr, ReplyDirectory, Request, }; +use serde_json::{Map, Value, json}; +use std::collections::BTreeMap; use std::env; use std::path::Path; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, SystemTime}; -struct JsonFilesystem; - -impl Filesystem for JsonFilesystem { - // For stat function like `stat /path/to/fuse` - fn getattr( - &self, - _req: &Request, - ino: fuser::INodeNo, - fh: Option, - reply: ReplyAttr, - ) { - println!("getattr(ino={})", ino); +struct JsonFilesystem { + tree: Map, + attrs: BTreeMap, + inodes: BTreeMap, +} +impl JsonFilesystem { + fn new(tree: &Map) -> JsonFilesystem { + let mut attrs = BTreeMap::new(); + let mut inodes = BTreeMap::new(); let ts = SystemTime::now(); let attr = FileAttr { ino: INodeNo::ROOT, @@ -40,13 +38,58 @@ impl Filesystem for JsonFilesystem { flags: 0, }; - let ttl = Duration::new(1, 0); + attrs.insert(INodeNo(1), attr); + inodes.insert("/".to_string(), INodeNo(1)); - if ino == INodeNo::ROOT { - reply.attr(&ttl, &attr); - } else { - reply.error(Errno::ENOSYS); + for (i, (key, value)) in tree.iter().enumerate() { + let attr = FileAttr { + ino: INodeNo(i as u64 + 2), + size: value.to_string().len() as u64, + blocks: 0, + atime: ts, + mtime: ts, + ctime: ts, + crtime: ts, + kind: FileType::RegularFile, + perm: 0o644, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + flags: 0, + }; + + attrs.insert(attr.ino, attr); + inodes.insert(key.clone(), attr.ino); } + + return JsonFilesystem { + tree: tree.clone(), + attrs: attrs, + inodes: inodes, + }; + } +} + +impl Filesystem for JsonFilesystem { + // For stat function like `stat /path/to/fuse` + fn getattr( + &self, + _req: &Request, + ino: fuser::INodeNo, + fh: Option, + reply: ReplyAttr, + ) { + println!("getattr(ino={})", ino); + + match self.attrs.get(&ino) { + Some(attr) => { + let ttl = Duration::new(1, 0); + reply.attr(&ttl, attr); + } + None => reply.error(Errno::ENOENT), + }; } fn readdir( @@ -61,17 +104,86 @@ impl Filesystem for JsonFilesystem { if ino == INodeNo::ROOT { if offset == 0 { - reply.add(INodeNo::ROOT, 0, FileType::Directory, &Path::new(".")); - reply.add(INodeNo::ROOT, 1, FileType::Directory, &Path::new("..")); + let _ = reply.add(INodeNo::ROOT, 0, FileType::Directory, &Path::new(".")); + let _ = reply.add(INodeNo::ROOT, 1, FileType::Directory, &Path::new("..")); + for (i, key) in self.tree.keys().enumerate() { + let inode: u64 = 2 + i as u64; + let offset: u64 = 2 + i as u64; + let _ = reply.add( + INodeNo(inode), + offset, + FileType::RegularFile, + &Path::new(key), + ); + } } reply.ok(); } else { reply.error(Errno::ENOSYS); } } + + fn lookup( + &self, + _req: &Request, + parent: INodeNo, + name: &std::ffi::OsStr, + reply: fuser::ReplyEntry, + ) { + println!("lookup(parent={}, name={})", parent, name.display()); + + let inode = match self.inodes.get(name.to_str().unwrap()) { + Some(inode) => inode, + None => { + reply.error(Errno::ENOENT); + return; + } + }; + + match self.attrs.get(inode) { + Some(attr) => { + let ttl = Duration::new(1, 0); + reply.entry(&ttl, attr, Generation(0)); + } + None => reply.error(Errno::ENOENT), + }; + } + + fn read( + &self, + _req: &Request, + ino: INodeNo, + fh: fuser::FileHandle, + offset: u64, + size: u32, + flags: fuser::OpenFlags, + lock_owner: Option, + reply: fuser::ReplyData, + ) { + println!( + "read(ino={}, fh={}, offset={}, size={})", + ino, fh, offset, size + ); + + for (key, &inode) in self.inodes.iter() { + if inode == ino { + let value = self.tree.get(key).unwrap(); + reply.data(value.to_string().as_bytes()); + return; + } + } + + reply.error(Errno::ENOENT); + } } fn main() { + let data = json!({ + "foo": "bar", + "answer": 42, + }); + let tree = data.as_object().unwrap(); + let fs = JsonFilesystem::new(tree); let mountpoint = match env::args().nth(1) { Some(path) => path, None => { @@ -90,7 +202,7 @@ fn main() { .expect("Error setting Ctrl+C handler"); let cfg = fuser::Config::default(); - let session = fuser::spawn_mount2(JsonFilesystem, &mountpoint, &cfg); + let session = fuser::spawn_mount2(fs, &mountpoint, &cfg); while running.load(Ordering::SeqCst) { std::thread::sleep(Duration::from_millis(100));