stable_fs/runtime/
fd.rs

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
10// stdio descriptors
11//pub const STDIN_FD: Fd = 0;
12//pub const STDOUT_FD: Fd = 1;
13pub const STDERR_FD: Fd = 2;
14// file descriptor used for the main root entry
15pub const ROOT_FD: Fd = 3;
16// number of file descriptors reserved for standard streams
17const FIRST_AVAILABLE_FD: Fd = 4;
18
19pub enum FdEntry {
20    File(File),
21    Dir(Dir),
22}
23
24//
25pub struct FdTable {
26    // currently open file descriptors.
27    table: BTreeMap<Fd, FdEntry>,
28    // backward links to see how many file descriptors are currently pointing to any particular node.
29    node_refcount: BTreeMap<Node, usize>,
30    // the next generated descriptor's ID (if there is nothing to reuse).
31    next_fd: Fd,
32    // freed file descriptors ready to reuse.
33    free_fds: Vec<Fd>,
34}
35
36impl FdTable {
37    // create a new file descriptor table.
38    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    // Get the map of node references.
48    pub fn node_refcount(&self) -> &BTreeMap<Node, usize> {
49        &self.node_refcount
50    }
51
52    // Update a file descriptor entry.
53    pub fn update(&mut self, fd: Fd, entry: FdEntry) {
54        self.insert(fd, entry);
55    }
56
57    // Update a file descriptor entry, it returns the old entry if existed.
58    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    // Get an FdEntry for a given file descriptor.
70    pub fn get(&self, fd: Fd) -> Option<&FdEntry> {
71        self.table.get(&fd)
72    }
73
74    // open root file descriptor
75    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    // Open a new file descriptor.
84    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    // Copy a file descriptor to a new number, the source descriptor is closed in the process.
100    // If the destination descriptor is busy, it is closed in the process.
101    pub fn renumber(&mut self, src: Fd, dst: Fd) -> Result<(), Error> {
102        if src == dst {
103            return Ok(());
104        }
105
106        // cannot do renumbering between a file and a folder
107        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        // now assign the source file descriptor to the destination
123        let old_entry = self.close(src).ok_or(Error::BadFileDescriptor)?;
124
125        // quietly close the destination file descriptor
126        if let Some(_old_dst_entry) = self.close(dst) {
127            // dst should not be reused by anyone else, so we must undo the fd marked for reusal
128            self.free_fds.retain(|value| *value != dst);
129        }
130
131        self.insert(dst, old_entry);
132
133        Ok(())
134    }
135
136    // Close file descriptor.
137    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        // Check that the file descriptor was assigned to the first possible value
209        assert_eq!(fd, FIRST_AVAILABLE_FD);
210
211        // Check that the entry exists in the table
212        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        // Close the file descriptor
230        let entry = fd_table.close(fd);
231        assert!(entry.is_some());
232
233        // Ensure the table no longer has the file descriptor
234        assert!(fd_table.get(fd).is_none());
235
236        // Ensure the freed FD is available for reuse
237        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        // Renumber the file descriptor
252        fd_table.renumber(src_fd, dst_fd).unwrap();
253
254        // Ensure the old FD no longer exists
255        assert!(fd_table.get(src_fd).is_none());
256
257        // Ensure the new FD points to the correct entry
258        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        // Open two file descriptors pointing to the same node
294        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        // Check node reference count
309        assert_eq!(fd_table.node_refcount().get(&1), Some(&2));
310
311        // Close one file descriptor
312        fd_table.close(fd1);
313
314        // Check node reference count again
315        assert_eq!(fd_table.node_refcount().get(&1), Some(&1));
316
317        // Close the second file descriptor
318        fd_table.close(fd2);
319
320        // Ensure the node reference count is now removed
321        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        // Ensure the updated entry is now in the table
343        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        // Ensure the node reference count was updated
350        assert!(fd_table.node_refcount().get(&1).is_none());
351        assert_eq!(fd_table.node_refcount().get(&2), Some(&1));
352    }
353}