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, scheme::OpenFlags};
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 a mutable reference to an open file by fd.
127    pub fn get_mut(&self, fd: u32) -> Result<&OpenFile, 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.file);
132            }
133        }
134        Err(SyscallError::BadHandle)
135    }
136
137    /// Replace the open flags on an fd (for fcntl F_SETFL).
138    /// Creates a new OpenFile with updated flags, preserving scheme/file_id/etc.
139    pub fn replace_open_flags(
140        &mut self,
141        fd: u32,
142        new_flags: OpenFlags,
143    ) -> Result<(), SyscallError> {
144        let fd_usize = fd as usize;
145        if fd_usize >= self.fds.len() {
146            return Err(SyscallError::BadHandle);
147        }
148        let desc = self.fds[fd_usize].as_mut().ok_or(SyscallError::BadHandle)?;
149        let old = desc.file.clone();
150        let new_file = Arc::new(OpenFile::new(
151            old.scheme().clone(),
152            old.file_id(),
153            alloc::string::String::from(old.path()),
154            new_flags,
155            old.flags(),
156            None,
157        ));
158        desc.file = new_file;
159        Ok(())
160    }
161
162    /// Get the CLOEXEC flag for a file descriptor.
163    pub fn get_cloexec(&self, fd: u32) -> Result<bool, SyscallError> {
164        let fd_usize = fd as usize;
165        if fd_usize < self.fds.len() {
166            if let Some(desc) = &self.fds[fd_usize] {
167                return Ok(desc.cloexec);
168            }
169        }
170        Err(SyscallError::BadHandle)
171    }
172
173    /// Set the CLOEXEC flag for a file descriptor.
174    pub fn set_cloexec(&mut self, fd: u32, cloexec: bool) -> Result<(), SyscallError> {
175        let fd_usize = fd as usize;
176        if fd_usize < self.fds.len() {
177            if let Some(desc) = &mut self.fds[fd_usize] {
178                desc.cloexec = cloexec;
179                return Ok(());
180            }
181        }
182        Err(SyscallError::BadHandle)
183    }
184
185    /// Remove an FD and return the file.
186    pub fn remove(&mut self, fd: u32) -> Result<Arc<OpenFile>, SyscallError> {
187        let fd_usize = fd as usize;
188        if fd_usize < self.fds.len() {
189            if let Some(desc) = self.fds[fd_usize].take() {
190                if fd_usize < self.next_fd_hint {
191                    self.next_fd_hint = fd_usize;
192                }
193                return Ok(desc.file);
194            }
195        }
196        Err(SyscallError::BadHandle)
197    }
198
199    /// Check if an FD exists.
200    pub fn contains(&self, fd: u32) -> bool {
201        let fd_usize = fd as usize;
202        fd_usize < self.fds.len() && self.fds[fd_usize].is_some()
203    }
204
205    /// Duplicate an FD (fork/dup semantics).
206    pub fn duplicate(&mut self, old_fd: u32) -> Result<u32, SyscallError> {
207        let file = self.get(old_fd)?;
208        Ok(self.insert(file))
209    }
210
211    /// Duplicate an FD with a minimum target FD number (F_DUPFD semantics).
212    pub fn duplicate_from(&mut self, old_fd: u32, min_fd: u32) -> Result<u32, SyscallError> {
213        let file = self.get(old_fd)?;
214        let min = min_fd as usize;
215        if min >= self.fds.len() {
216            self.fds.resize(min + 1, None);
217        }
218        let start = core::cmp::max(min, self.next_fd_hint);
219        for i in start..self.fds.len() {
220            if self.fds[i].is_none() {
221                self.fds[i] = Some(FileDescriptor::new(file));
222                if i == self.next_fd_hint {
223                    self.next_fd_hint = self.next_fd_hint.saturating_add(1);
224                    self.advance_next_fd_hint();
225                }
226                return Ok(i as u32);
227            }
228        }
229        for i in min..start.min(self.fds.len()) {
230            if self.fds[i].is_none() {
231                self.fds[i] = Some(FileDescriptor::new(file));
232                if i == self.next_fd_hint {
233                    self.next_fd_hint = self.next_fd_hint.saturating_add(1);
234                    self.advance_next_fd_hint();
235                }
236                return Ok(i as u32);
237            }
238        }
239        let fd = self.fds.len() as u32;
240        self.fds.push(Some(FileDescriptor::new(file)));
241        if (fd as usize) == self.next_fd_hint {
242            self.next_fd_hint = self.next_fd_hint.saturating_add(1);
243            self.advance_next_fd_hint();
244        }
245        Ok(fd)
246    }
247
248    /// Duplicate `old_fd` onto `new_fd` (dup2 semantics).
249    pub fn duplicate_to(&mut self, old_fd: u32, new_fd: u32) -> Result<u32, SyscallError> {
250        let file = self.get(old_fd)?;
251        if old_fd == new_fd {
252            return Ok(new_fd);
253        }
254        let new_idx = new_fd as usize;
255        if new_idx >= self.fds.len() {
256            self.fds.resize(new_idx + 1, None);
257        }
258        self.fds[new_idx] = Some(FileDescriptor::new(file));
259        if new_idx == self.next_fd_hint {
260            self.next_fd_hint = self.next_fd_hint.saturating_add(1);
261            self.advance_next_fd_hint();
262        }
263        Ok(new_fd)
264    }
265
266    /// Close all file descriptors (process exit).
267    pub fn close_all(&mut self) {
268        self.fds.clear();
269        self.next_fd_hint = 0;
270    }
271
272    /// Close all file descriptors with CLOEXEC flag (execve cleanup).
273    pub fn close_cloexec(&mut self) {
274        for (i, fd) in self.fds.iter_mut().enumerate() {
275            if let Some(desc) = fd {
276                if desc.cloexec {
277                    *fd = None;
278                    if i < self.next_fd_hint {
279                        self.next_fd_hint = i;
280                    }
281                }
282            }
283        }
284    }
285
286    /// Clone this FD table (fork semantics).
287    ///
288    /// All descriptors are copied, including those with CLOEXEC.
289    /// CLOEXEC only takes effect at exec-time via `close_cloexec()`.
290    pub fn clone_for_fork(&self) -> Self {
291        FileDescriptorTable {
292            fds: self.fds.clone(),
293            next_fd_hint: self.next_fd_hint,
294        }
295    }
296}
297
298impl Default for FileDescriptorTable {
299    /// Builds a default instance.
300    fn default() -> Self {
301        Self::new()
302    }
303}