1use std::collections::BTreeMap;
2
3use crate::{
4 error::Error,
5 fs::Fd,
6 runtime::{dir::Dir, file::File},
7 storage::types::Node,
8};
9
10pub const STDERR_FD: Fd = 2;
14pub const ROOT_FD: Fd = 3;
16const FIRST_AVAILABLE_FD: Fd = 4;
18
19pub enum FdEntry {
20 File(File),
21 Dir(Dir),
22}
23
24pub struct FdTable {
26 table: BTreeMap<Fd, FdEntry>,
28 node_refcount: BTreeMap<Node, usize>,
30 next_fd: Fd,
32 free_fds: Vec<Fd>,
34}
35
36impl FdTable {
37 pub fn new() -> Self {
39 Self {
40 table: BTreeMap::default(),
41 node_refcount: BTreeMap::default(),
42 next_fd: FIRST_AVAILABLE_FD,
43 free_fds: vec![],
44 }
45 }
46
47 pub fn node_refcount(&self) -> &BTreeMap<Node, usize> {
49 &self.node_refcount
50 }
51
52 pub fn update(&mut self, fd: Fd, entry: FdEntry) {
54 self.insert(fd, entry);
55 }
56
57 pub fn insert(&mut self, fd: Fd, entry: FdEntry) -> Option<FdEntry> {
59 self.inc_node_refcount(&entry);
60
61 let prev_entry = self.table.insert(fd, entry);
62 if let Some(prev_entry) = prev_entry.as_ref() {
63 self.dec_node_refcount(prev_entry);
64 }
65
66 prev_entry
67 }
68
69 pub fn get(&self, fd: Fd) -> Option<&FdEntry> {
71 self.table.get(&fd)
72 }
73
74 pub fn open_root(&mut self, entry: FdEntry) -> Fd {
76 let fd = ROOT_FD;
77
78 let prev = self.insert(fd, entry);
79 assert!(prev.is_none());
80 fd
81 }
82
83 pub fn open(&mut self, entry: FdEntry) -> Fd {
85 let fd = match self.free_fds.pop() {
86 Some(fd) => fd,
87 None => {
88 let fd = self.next_fd;
89 self.next_fd += 1;
90 fd
91 }
92 };
93
94 let prev = self.insert(fd, entry);
95 assert!(prev.is_none());
96 fd
97 }
98
99 pub fn renumber(&mut self, src: Fd, dst: Fd) -> Result<(), Error> {
102 if src == dst {
103 return Ok(());
104 }
105
106 let src_entry: Option<&FdEntry> = self.table.get(&src);
108 let dst_entry: Option<&FdEntry> = self.table.get(&dst);
109
110 if let Some(FdEntry::Dir(_s)) = src_entry
111 && let Some(FdEntry::File(_d)) = dst_entry
112 {
113 return Err(Error::BadFileDescriptor);
114 }
115
116 if let Some(FdEntry::File(_s)) = src_entry
117 && let Some(FdEntry::Dir(_d)) = dst_entry
118 {
119 return Err(Error::BadFileDescriptor);
120 }
121
122 let old_entry = self.close(src).ok_or(Error::BadFileDescriptor)?;
124
125 if let Some(_old_dst_entry) = self.close(dst) {
127 self.free_fds.retain(|value| *value != dst);
129 }
130
131 self.insert(dst, old_entry);
132
133 Ok(())
134 }
135
136 pub fn close(&mut self, fd: Fd) -> Option<FdEntry> {
138 if fd == ROOT_FD {
139 return None;
140 }
141
142 let entry = self.table.remove(&fd);
143
144 if let Some(entry) = entry {
145 if fd >= FIRST_AVAILABLE_FD {
146 self.free_fds.push(fd);
147 }
148
149 self.dec_node_refcount(&entry);
150
151 Some(entry)
152 } else {
153 None
154 }
155 }
156
157 fn inc_node_refcount(&mut self, entry: &FdEntry) {
158 let node = match entry {
159 FdEntry::File(file) => file.node,
160 FdEntry::Dir(dir) => dir.node,
161 };
162 let refcount = self.node_refcount.entry(node).or_default();
163 *refcount += 1;
164 }
165
166 fn dec_node_refcount(&mut self, entry: &FdEntry) {
167 let node = match entry {
168 FdEntry::File(file) => file.node,
169 FdEntry::Dir(dir) => dir.node,
170 };
171
172 let refcount = self.node_refcount.remove(&node);
173 if let Some(mut refcount) = refcount {
174 refcount -= 1;
175 if refcount > 0 {
176 self.node_refcount.insert(node, refcount);
177 }
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use crate::fs::FdStat;
185
186 use super::*;
187
188 #[test]
189 fn test_fdtable_new() {
190 let fd_table = FdTable::new();
191 assert!(fd_table.table.is_empty());
192 assert!(fd_table.node_refcount.is_empty());
193
194 assert_eq!(fd_table.next_fd, FIRST_AVAILABLE_FD);
195 assert!(fd_table.free_fds.is_empty());
196 }
197
198 #[test]
199 fn test_fdtable_open_and_get() {
200 let mut fd_table = FdTable::new();
201 let file = File {
202 node: 1,
203 cursor: 0,
204 stat: FdStat::default(),
205 };
206 let fd = fd_table.open(FdEntry::File(file));
207
208 assert_eq!(fd, FIRST_AVAILABLE_FD);
210
211 let entry = fd_table.get(fd).unwrap();
213 match entry {
214 FdEntry::File(file) => assert_eq!(file.node, 1),
215 _ => panic!("Expected a file entry"),
216 }
217 }
218
219 #[test]
220 fn test_fdtable_close() {
221 let mut fd_table = FdTable::new();
222 let file = File {
223 node: 1,
224 cursor: 0,
225 stat: FdStat::default(),
226 };
227 let fd = fd_table.open(FdEntry::File(file));
228
229 let entry = fd_table.close(fd);
231 assert!(entry.is_some());
232
233 assert!(fd_table.get(fd).is_none());
235
236 assert!(fd_table.free_fds.contains(&fd));
238 }
239
240 #[test]
241 fn test_fdtable_renumber() {
242 let mut fd_table = FdTable::new();
243 let file = File {
244 node: 1,
245 cursor: 0,
246 stat: FdStat::default(),
247 };
248 let src_fd = fd_table.open(FdEntry::File(file));
249 let dst_fd = 10;
250
251 fd_table.renumber(src_fd, dst_fd).unwrap();
253
254 assert!(fd_table.get(src_fd).is_none());
256
257 let entry = fd_table.get(dst_fd).unwrap();
259 match entry {
260 FdEntry::File(file) => assert_eq!(file.node, 1),
261 _ => panic!("Expected a file entry"),
262 }
263 }
264
265 #[test]
266 fn test_renumber_different_types() {
267 let mut fd_table = FdTable::new();
268 let file = File {
269 node: 1,
270 cursor: 0,
271 stat: FdStat::default(),
272 };
273
274 let dir = Dir {
275 node: 2,
276 stat: FdStat::default(),
277 };
278
279 let fd1 = fd_table.open(FdEntry::File(file));
280 let fd2 = fd_table.open(FdEntry::Dir(dir));
281
282 let result = fd_table.renumber(fd1, fd2);
283 assert_eq!(result, Err(Error::BadFileDescriptor));
284
285 let result = fd_table.renumber(fd2, fd1);
286 assert_eq!(result, Err(Error::BadFileDescriptor));
287 }
288
289 #[test]
290 fn test_fdtable_node_refcount() {
291 let mut fd_table = FdTable::new();
292
293 let file1 = File {
295 node: 1,
296 cursor: 0,
297 stat: FdStat::default(),
298 };
299 let file2 = File {
300 node: 1,
301 cursor: 0,
302 stat: FdStat::default(),
303 };
304
305 let fd1 = fd_table.open(FdEntry::File(file1));
306 let fd2 = fd_table.open(FdEntry::File(file2));
307
308 assert_eq!(fd_table.node_refcount().get(&1), Some(&2));
310
311 fd_table.close(fd1);
313
314 assert_eq!(fd_table.node_refcount().get(&1), Some(&1));
316
317 fd_table.close(fd2);
319
320 assert!(fd_table.node_refcount().get(&1).is_none());
322 }
323
324 #[test]
325 fn test_fdtable_update() {
326 let mut fd_table = FdTable::new();
327
328 let file = File {
329 node: 1,
330 cursor: 0,
331 stat: FdStat::default(),
332 };
333 let fd = fd_table.open(FdEntry::File(file));
334
335 let updated_file = File {
336 node: 2,
337 cursor: 0,
338 stat: FdStat::default(),
339 };
340 fd_table.update(fd, FdEntry::File(updated_file));
341
342 let entry = fd_table.get(fd).unwrap();
344 match entry {
345 FdEntry::File(file) => assert_eq!(file.node, 2),
346 _ => panic!("Expected a file entry"),
347 }
348
349 assert!(fd_table.node_refcount().get(&1).is_none());
351 assert_eq!(fd_table.node_refcount().get(&2), Some(&1));
352 }
353}