jax_daemon/fuse/
inode_table.rs1use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9#[derive(Debug)]
11pub struct InodeTable {
12 path_to_inode: HashMap<String, u64>,
14 inode_to_path: HashMap<u64, String>,
16 next_inode: AtomicU64,
18}
19
20impl Default for InodeTable {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl InodeTable {
27 pub const ROOT_INODE: u64 = 1;
29
30 pub fn new() -> Self {
32 let mut table = Self {
33 path_to_inode: HashMap::new(),
34 inode_to_path: HashMap::new(),
35 next_inode: AtomicU64::new(2), };
37
38 table
40 .path_to_inode
41 .insert("/".to_string(), Self::ROOT_INODE);
42 table
43 .inode_to_path
44 .insert(Self::ROOT_INODE, "/".to_string());
45
46 table
47 }
48
49 pub fn get_or_create(&mut self, path: &str) -> u64 {
51 let normalized = Self::normalize_path(path);
52
53 if let Some(&inode) = self.path_to_inode.get(&normalized) {
54 return inode;
55 }
56
57 let inode = self.next_inode.fetch_add(1, Ordering::SeqCst);
58 self.path_to_inode.insert(normalized.clone(), inode);
59 self.inode_to_path.insert(inode, normalized);
60 inode
61 }
62
63 pub fn get_inode(&self, path: &str) -> Option<u64> {
65 let normalized = Self::normalize_path(path);
66 self.path_to_inode.get(&normalized).copied()
67 }
68
69 pub fn get_path(&self, inode: u64) -> Option<&str> {
71 self.inode_to_path.get(&inode).map(String::as_str)
72 }
73
74 pub fn remove(&mut self, inode: u64) -> Option<String> {
76 if let Some(path) = self.inode_to_path.remove(&inode) {
77 self.path_to_inode.remove(&path);
78 Some(path)
79 } else {
80 None
81 }
82 }
83
84 pub fn remove_by_path(&mut self, path: &str) -> Option<u64> {
86 let normalized = Self::normalize_path(path);
87 if let Some(inode) = self.path_to_inode.remove(&normalized) {
88 self.inode_to_path.remove(&inode);
89 Some(inode)
90 } else {
91 None
92 }
93 }
94
95 pub fn rename(&mut self, old_path: &str, new_path: &str) -> Option<u64> {
97 let old_normalized = Self::normalize_path(old_path);
98 let new_normalized = Self::normalize_path(new_path);
99
100 if let Some(inode) = self.path_to_inode.remove(&old_normalized) {
101 self.inode_to_path.insert(inode, new_normalized.clone());
102 self.path_to_inode.insert(new_normalized, inode);
103 Some(inode)
104 } else {
105 None
106 }
107 }
108
109 pub fn clear(&mut self) {
111 self.path_to_inode.clear();
112 self.inode_to_path.clear();
113 self.next_inode.store(2, Ordering::SeqCst);
114
115 self.path_to_inode.insert("/".to_string(), Self::ROOT_INODE);
117 self.inode_to_path.insert(Self::ROOT_INODE, "/".to_string());
118 }
119
120 fn normalize_path(path: &str) -> String {
122 let path = path.trim();
123
124 if path.is_empty() || path == "/" {
126 return "/".to_string();
127 }
128
129 let mut normalized = if path.starts_with('/') {
131 path.to_string()
132 } else {
133 format!("/{}", path)
134 };
135
136 if normalized.len() > 1 && normalized.ends_with('/') {
137 normalized.pop();
138 }
139
140 normalized
141 }
142
143 pub fn parent_path(path: &str) -> String {
145 let normalized = Self::normalize_path(path);
146 if normalized == "/" {
147 return "/".to_string();
148 }
149
150 match normalized.rfind('/') {
151 Some(0) => "/".to_string(),
152 Some(pos) => normalized[..pos].to_string(),
153 None => "/".to_string(),
154 }
155 }
156
157 pub fn filename(path: &str) -> &str {
159 let normalized = path.trim();
160 if normalized == "/" || normalized.is_empty() {
161 return "";
162 }
163
164 match normalized.rfind('/') {
165 Some(pos) => &normalized[pos + 1..],
166 None => normalized,
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_root_inode() {
177 let table = InodeTable::new();
178 assert_eq!(table.get_inode("/"), Some(InodeTable::ROOT_INODE));
179 assert_eq!(table.get_path(InodeTable::ROOT_INODE), Some("/"));
180 }
181
182 #[test]
183 fn test_get_or_create() {
184 let mut table = InodeTable::new();
185
186 let inode1 = table.get_or_create("/foo");
187 let inode2 = table.get_or_create("/foo");
188 let inode3 = table.get_or_create("/bar");
189
190 assert_eq!(inode1, inode2);
191 assert_ne!(inode1, inode3);
192 assert_ne!(inode1, InodeTable::ROOT_INODE);
193 }
194
195 #[test]
196 fn test_normalize_path() {
197 assert_eq!(InodeTable::normalize_path(""), "/");
198 assert_eq!(InodeTable::normalize_path("/"), "/");
199 assert_eq!(InodeTable::normalize_path("foo"), "/foo");
200 assert_eq!(InodeTable::normalize_path("/foo"), "/foo");
201 assert_eq!(InodeTable::normalize_path("/foo/"), "/foo");
202 assert_eq!(InodeTable::normalize_path("/foo/bar"), "/foo/bar");
203 }
204
205 #[test]
206 fn test_parent_path() {
207 assert_eq!(InodeTable::parent_path("/"), "/");
208 assert_eq!(InodeTable::parent_path("/foo"), "/");
209 assert_eq!(InodeTable::parent_path("/foo/bar"), "/foo");
210 assert_eq!(InodeTable::parent_path("/foo/bar/baz"), "/foo/bar");
211 }
212
213 #[test]
214 fn test_filename() {
215 assert_eq!(InodeTable::filename("/"), "");
216 assert_eq!(InodeTable::filename("/foo"), "foo");
217 assert_eq!(InodeTable::filename("/foo/bar"), "bar");
218 assert_eq!(InodeTable::filename("/foo/bar.txt"), "bar.txt");
219 }
220
221 #[test]
222 fn test_remove() {
223 let mut table = InodeTable::new();
224 let inode = table.get_or_create("/foo");
225
226 assert!(table.get_inode("/foo").is_some());
227 table.remove(inode);
228 assert!(table.get_inode("/foo").is_none());
229 }
230
231 #[test]
232 fn test_rename() {
233 let mut table = InodeTable::new();
234 let inode = table.get_or_create("/old");
235
236 table.rename("/old", "/new");
237
238 assert!(table.get_inode("/old").is_none());
239 assert_eq!(table.get_inode("/new"), Some(inode));
240 assert_eq!(table.get_path(inode), Some("/new"));
241 }
242
243 #[test]
244 fn test_clear() {
245 let mut table = InodeTable::new();
246 table.get_or_create("/foo");
247 table.get_or_create("/bar");
248
249 table.clear();
250
251 assert_eq!(table.get_inode("/"), Some(InodeTable::ROOT_INODE));
252 assert!(table.get_inode("/foo").is_none());
253 assert!(table.get_inode("/bar").is_none());
254 }
255}