Skip to main content

strat9_kernel/vfs/
fd.rs

1//! File Descriptor Table (per-process).
2//!
3//! Each process has its own FD table mapping integers (0, 1, 2...) to open files.
4
5use super::file::OpenFile;
6use crate::syscall::error::SyscallError;
7use alloc::{sync::Arc, vec::Vec};
8
9/// Standard file descriptor numbers.
10pub const STDIN: u32 = 0;
11pub const STDOUT: u32 = 1;
12pub const STDERR: u32 = 2;
13
14/// Wrapper for a file descriptor with FD-level flags (CLOEXEC).
15#[derive(Clone)]
16pub struct FileDescriptor {
17    /// The open file handle.
18    pub file: Arc<OpenFile>,
19    /// Close-on-exec flag (FD_CLOEXEC).
20    pub cloexec: bool,
21}
22
23impl FileDescriptor {
24    /// Create a new file descriptor without CLOEXEC.
25    pub fn new(file: Arc<OpenFile>) -> Self {
26        FileDescriptor {
27            file,
28            cloexec: false,
29        }
30    }
31
32    /// Create a new file descriptor with CLOEXEC flag.
33    pub fn new_cloexec(file: Arc<OpenFile>, cloexec: bool) -> Self {
34        FileDescriptor { file, cloexec }
35    }
36}
37
38/// Per-process file descriptor table.
39pub struct FileDescriptorTable {
40    fds: Vec<Option<FileDescriptor>>,
41    next_fd_hint: usize,
42}
43
44impl FileDescriptorTable {
45    /// Performs the advance next fd hint operation.
46    fn advance_next_fd_hint(&mut self) {
47        while self.next_fd_hint < self.fds.len() && self.fds[self.next_fd_hint].is_some() {
48            self.next_fd_hint = self.next_fd_hint.saturating_add(1);
49        }
50    }
51
52    /// Create a new empty FD table.
53    pub fn new() -> Self {
54        let mut fds = Vec::with_capacity(64);
55        fds.resize(3, None); // Reserve 0, 1, 2
56        FileDescriptorTable {
57            fds,
58            next_fd_hint: 3,
59        }
60    }
61
62    /// Find the lowest available file descriptor number.
63    fn find_free_fd(&mut self) -> u32 {
64        for i in self.next_fd_hint..self.fds.len() {
65            if self.fds[i].is_none() {
66                self.next_fd_hint = i.saturating_add(1);
67                self.advance_next_fd_hint();
68                return i as u32;
69            }
70        }
71        for i in 0..self.next_fd_hint.min(self.fds.len()) {
72            if self.fds[i].is_none() {
73                self.next_fd_hint = i.saturating_add(1);
74                self.advance_next_fd_hint();
75                return i as u32;
76            }
77        }
78        self.next_fd_hint = self.fds.len().saturating_add(1);
79        self.fds.len() as u32
80    }
81
82    /// Insert an open file and return its FD number.
83    pub fn insert(&mut self, file: Arc<OpenFile>) -> u32 {
84        self.insert_with_flags(file, false)
85    }
86
87    /// Insert a file at a specific FD (for stdin/stdout/stderr).
88    pub fn insert_at(&mut self, fd: u32, file: Arc<OpenFile>) {
89        let fd_usize = fd as usize;
90        if fd_usize >= self.fds.len() {
91            self.fds.resize(fd_usize + 1, None);
92        }
93        self.fds[fd_usize] = Some(FileDescriptor::new(file));
94        if fd_usize == self.next_fd_hint {
95            self.next_fd_hint = self.next_fd_hint.saturating_add(1);
96            self.advance_next_fd_hint();
97        }
98    }
99
100    /// Insert a file with explicit CLOEXEC flag.
101    pub fn insert_with_flags(&mut self, file: Arc<OpenFile>, cloexec: bool) -> u32 {
102        let fd = self.find_free_fd();
103        let fd_usize = fd as usize;
104        if fd_usize >= self.fds.len() {
105            self.fds.resize(fd_usize + 1, None);
106        }
107        self.fds[fd_usize] = Some(FileDescriptor::new_cloexec(file, cloexec));
108        if fd_usize == self.next_fd_hint {
109            self.next_fd_hint = self.next_fd_hint.saturating_add(1);
110            self.advance_next_fd_hint();
111        }
112        fd
113    }
114
115    /// Get an open file by FD.
116    pub fn get(&self, fd: u32) -> Result<Arc<OpenFile>, SyscallError> {
117        let fd_usize = fd as usize;
118        if fd_usize < self.fds.len() {
119            if let Some(desc) = &self.fds[fd_usize] {
120                return Ok(desc.file.clone());
121            }
122        }
123        Err(SyscallError::BadHandle)
124    }
125
126    /// Get the CLOEXEC flag for a file descriptor.
127    pub fn get_cloexec(&self, fd: u32) -> Result<bool, SyscallError> {
128        let fd_usize = fd as usize;
129        if fd_usize < self.fds.len() {
130            if let Some(desc) = &self.fds[fd_usize] {
131                return Ok(desc.cloexec);
132            }
133        }
134        Err(SyscallError::BadHandle)
135    }
136
137    /// Set the CLOEXEC flag for a file descriptor.
138    pub fn set_cloexec(&mut self, fd: u32, cloexec: bool) -> Result<(), SyscallError> {
139        let fd_usize = fd as usize;
140        if fd_usize < self.fds.len() {
141            if let Some(desc) = &mut self.fds[fd_usize] {
142                desc.cloexec = cloexec;
143                return Ok(());
144            }
145        }
146        Err(SyscallError::BadHandle)
147    }
148
149    /// Remove an FD and return the file.
150    pub fn remove(&mut self, fd: u32) -> Result<Arc<OpenFile>, SyscallError> {
151        let fd_usize = fd as usize;
152        if fd_usize < self.fds.len() {
153            if let Some(desc) = self.fds[fd_usize].take() {
154                if fd_usize < self.next_fd_hint {
155                    self.next_fd_hint = fd_usize;
156                }
157                return Ok(desc.file);
158            }
159        }
160        Err(SyscallError::BadHandle)
161    }
162
163    /// Check if an FD exists.
164    pub fn contains(&self, fd: u32) -> bool {
165        let fd_usize = fd as usize;
166        fd_usize < self.fds.len() && self.fds[fd_usize].is_some()
167    }
168
169    /// Duplicate an FD (fork/dup semantics).
170    pub fn duplicate(&mut self, old_fd: u32) -> Result<u32, SyscallError> {
171        let file = self.get(old_fd)?;
172        Ok(self.insert(file))
173    }
174
175    /// Duplicate an FD with a minimum target FD number (F_DUPFD semantics).
176    pub fn duplicate_from(&mut self, old_fd: u32, min_fd: u32) -> Result<u32, SyscallError> {
177        let file = self.get(old_fd)?;
178        let min = min_fd as usize;
179        if min >= self.fds.len() {
180            self.fds.resize(min + 1, None);
181        }
182        let start = core::cmp::max(min, self.next_fd_hint);
183        for i in start..self.fds.len() {
184            if self.fds[i].is_none() {
185                self.fds[i] = Some(FileDescriptor::new(file));
186                if i == self.next_fd_hint {
187                    self.next_fd_hint = self.next_fd_hint.saturating_add(1);
188                    self.advance_next_fd_hint();
189                }
190                return Ok(i as u32);
191            }
192        }
193        for i in min..start.min(self.fds.len()) {
194            if self.fds[i].is_none() {
195                self.fds[i] = Some(FileDescriptor::new(file));
196                if i == self.next_fd_hint {
197                    self.next_fd_hint = self.next_fd_hint.saturating_add(1);
198                    self.advance_next_fd_hint();
199                }
200                return Ok(i as u32);
201            }
202        }
203        let fd = self.fds.len() as u32;
204        self.fds.push(Some(FileDescriptor::new(file)));
205        if (fd as usize) == self.next_fd_hint {
206            self.next_fd_hint = self.next_fd_hint.saturating_add(1);
207            self.advance_next_fd_hint();
208        }
209        Ok(fd)
210    }
211
212    /// Duplicate `old_fd` onto `new_fd` (dup2 semantics).
213    pub fn duplicate_to(&mut self, old_fd: u32, new_fd: u32) -> Result<u32, SyscallError> {
214        let file = self.get(old_fd)?;
215        if old_fd == new_fd {
216            return Ok(new_fd);
217        }
218        let new_idx = new_fd as usize;
219        if new_idx >= self.fds.len() {
220            self.fds.resize(new_idx + 1, None);
221        }
222        self.fds[new_idx] = Some(FileDescriptor::new(file));
223        if new_idx == self.next_fd_hint {
224            self.next_fd_hint = self.next_fd_hint.saturating_add(1);
225            self.advance_next_fd_hint();
226        }
227        Ok(new_fd)
228    }
229
230    /// Close all file descriptors (process exit).
231    pub fn close_all(&mut self) {
232        self.fds.clear();
233        self.next_fd_hint = 0;
234    }
235
236    /// Close all file descriptors with CLOEXEC flag (execve cleanup).
237    pub fn close_cloexec(&mut self) {
238        for (i, fd) in self.fds.iter_mut().enumerate() {
239            if let Some(desc) = fd {
240                if desc.cloexec {
241                    *fd = None;
242                    if i < self.next_fd_hint {
243                        self.next_fd_hint = i;
244                    }
245                }
246            }
247        }
248    }
249
250    /// Clone this FD table (fork semantics).
251    ///
252    /// All descriptors are copied, including those with CLOEXEC.
253    /// CLOEXEC only takes effect at exec-time via `close_cloexec()`.
254    pub fn clone_for_fork(&self) -> Self {
255        FileDescriptorTable {
256            fds: self.fds.clone(),
257            next_fd_hint: self.next_fd_hint,
258        }
259    }
260}
261
262impl Default for FileDescriptorTable {
263    /// Builds a default instance.
264    fn default() -> Self {
265        Self::new()
266    }
267}