1use std::ffi::OsStr;
8use std::time::{Duration, UNIX_EPOCH};
9
10use fuser::{
11 FileAttr, FileType, Filesystem, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry,
12 Request,
13};
14
15use crate::projected::ProjectedFs;
16
17const TTL: Duration = Duration::from_secs(1);
18
19pub struct FuseFs {
21 fs: ProjectedFs,
22 inodes: Vec<String>, }
25
26impl FuseFs {
27 pub fn new(fs: ProjectedFs) -> Self {
28 let mut inodes = vec![String::new()]; inodes.push(String::new()); if let Ok(root_entries) = fs.list_dir("") {
34 for entry in &root_entries {
35 inodes.push(entry.clone());
36 if let Ok(children) = fs.list_dir(entry) {
38 for child in &children {
39 inodes.push(format!("{}/{}", entry, child));
40 }
41 }
42 }
43 }
44
45 FuseFs { fs, inodes }
46 }
47
48 fn path_to_inode(&self, path: &str) -> Option<u64> {
49 self.inodes.iter().position(|p| p == path).map(|i| i as u64)
50 }
51
52 fn inode_to_path(&self, inode: u64) -> Option<&str> {
53 self.inodes.get(inode as usize).map(|s| s.as_str())
54 }
55
56 fn make_attr(&self, inode: u64, path: &str) -> FileAttr {
57 let is_dir = self.fs.list_dir(path).is_ok();
58 let size = if is_dir {
59 0
60 } else {
61 self.fs.read_file(path).map(|d| d.len() as u64).unwrap_or(0)
62 };
63
64 FileAttr {
65 ino: inode,
66 size,
67 blocks: size.div_ceil(512),
68 atime: UNIX_EPOCH,
69 mtime: UNIX_EPOCH,
70 ctime: UNIX_EPOCH,
71 crtime: UNIX_EPOCH,
72 kind: if is_dir { FileType::Directory } else { FileType::RegularFile },
73 perm: if is_dir { 0o755 } else { 0o644 },
74 nlink: 1,
75 uid: 0,
76 gid: 0,
77 rdev: 0,
78 blksize: 512,
79 flags: 0,
80 }
81 }
82}
83
84impl Filesystem for FuseFs {
85 fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option<u64>, reply: ReplyAttr) {
86 if let Some(path) = self.inode_to_path(ino) {
87 let path = path.to_string();
88 if ino == 1 || self.fs.exists(&path) {
89 reply.attr(&TTL, &self.make_attr(ino, &path));
90 } else {
91 reply.error(libc::ENOENT);
92 }
93 } else {
94 reply.error(libc::ENOENT);
95 }
96 }
97
98 fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
99 let parent_path = match self.inode_to_path(parent) {
100 Some(p) => p.to_string(),
101 None => { reply.error(libc::ENOENT); return; }
102 };
103
104 let child_path = if parent_path.is_empty() {
105 name.to_string_lossy().to_string()
106 } else {
107 format!("{}/{}", parent_path, name.to_string_lossy())
108 };
109
110 if self.fs.exists(&child_path) {
111 let inode = if let Some(ino) = self.path_to_inode(&child_path) {
113 ino
114 } else {
115 self.inodes.push(child_path.clone());
116 (self.inodes.len() - 1) as u64
117 };
118 reply.entry(&TTL, &self.make_attr(inode, &child_path), 0);
119 } else {
120 reply.error(libc::ENOENT);
121 }
122 }
123
124 fn readdir(
125 &mut self,
126 _req: &Request,
127 ino: u64,
128 _fh: u64,
129 offset: i64,
130 mut reply: ReplyDirectory,
131 ) {
132 let path = match self.inode_to_path(ino) {
133 Some(p) => p.to_string(),
134 None => { reply.error(libc::ENOENT); return; }
135 };
136
137 let entries = match self.fs.list_dir(&path) {
138 Ok(e) => e,
139 Err(_) => { reply.error(libc::ENOENT); return; }
140 };
141
142 let mut full_entries = vec![
143 (ino, FileType::Directory, ".".to_string()),
144 (ino, FileType::Directory, "..".to_string()),
145 ];
146
147 for entry_name in entries {
148 let child_path = if path.is_empty() {
149 entry_name.clone()
150 } else {
151 format!("{}/{}", path, entry_name)
152 };
153
154 let inode = if let Some(ino) = self.path_to_inode(&child_path) {
155 ino
156 } else {
157 self.inodes.push(child_path.clone());
158 (self.inodes.len() - 1) as u64
159 };
160
161 let kind = if self.fs.list_dir(&child_path).is_ok() {
162 FileType::Directory
163 } else {
164 FileType::RegularFile
165 };
166
167 full_entries.push((inode, kind, entry_name));
168 }
169
170 for (i, (inode, kind, name)) in full_entries.iter().enumerate().skip(offset as usize) {
171 if reply.add(*inode, (i + 1) as i64, *kind, name) {
172 break;
173 }
174 }
175 reply.ok();
176 }
177
178 fn read(
179 &mut self,
180 _req: &Request,
181 ino: u64,
182 _fh: u64,
183 offset: i64,
184 size: u32,
185 _flags: i32,
186 _lock_owner: Option<u64>,
187 reply: ReplyData,
188 ) {
189 let path = match self.inode_to_path(ino) {
190 Some(p) => p.to_string(),
191 None => { reply.error(libc::ENOENT); return; }
192 };
193
194 match self.fs.read_file(&path) {
195 Ok(data) => {
196 let start = offset as usize;
197 if start >= data.len() {
198 reply.data(&[]);
199 } else {
200 let end = (start + size as usize).min(data.len());
201 reply.data(&data[start..end]);
202 }
203 }
204 Err(_) => reply.error(libc::ENOENT),
205 }
206 }
207}