Skip to main content

strat9_kernel/vfs/
file.rs

1//! Open file state.
2//!
3//! Represents an open file with its associated scheme and current offset.
4
5use super::scheme::{DirEntry, DynScheme, FileFlags, FileStat, OpenFlags};
6use crate::{sync::SpinLock, syscall::error::SyscallError};
7use alloc::{string::String, vec::Vec};
8
9/// An open file handle.
10pub struct OpenFile {
11    /// Scheme handling this file.
12    scheme: DynScheme,
13    /// File ID within the scheme.
14    file_id: u64,
15    /// Original path (for debugging).
16    path: String,
17    /// Current read/write offset.
18    offset: SpinLock<u64>,
19    /// Flags from open().
20    open_flags: OpenFlags,
21    /// File properties.
22    file_flags: FileFlags,
23    /// Cached file size (if known).
24    size: Option<u64>,
25}
26
27impl OpenFile {
28    /// Create a new open file.
29    pub fn new(
30        scheme: DynScheme,
31        file_id: u64,
32        path: String,
33        open_flags: OpenFlags,
34        file_flags: FileFlags,
35        size: Option<u64>,
36    ) -> Self {
37        OpenFile {
38            scheme,
39            file_id,
40            path,
41            offset: SpinLock::new(0),
42            open_flags,
43            file_flags,
44            size,
45        }
46    }
47
48    /// Read from the file at current offset, advancing the offset.
49    pub fn read(&self, buf: &mut [u8]) -> Result<usize, SyscallError> {
50        if !self.open_flags.contains(OpenFlags::READ) {
51            return Err(SyscallError::PermissionDenied);
52        }
53
54        let mut offset = self.offset.lock();
55        let bytes_read = self.scheme.read(self.file_id, *offset, buf)?;
56        *offset += bytes_read as u64;
57        Ok(bytes_read)
58    }
59
60    /// Write to the file at current offset, advancing the offset.
61    pub fn write(&self, buf: &[u8]) -> Result<usize, SyscallError> {
62        if !self.open_flags.contains(OpenFlags::WRITE) {
63            return Err(SyscallError::PermissionDenied);
64        }
65
66        let mut offset = self.offset.lock();
67        if self.open_flags.contains(OpenFlags::APPEND) {
68            if let Ok(sz) = self.scheme.size(self.file_id) {
69                *offset = sz;
70            } else if let Some(sz) = self.size {
71                *offset = sz;
72            }
73        }
74
75        let bytes_written = self.scheme.write(self.file_id, *offset, buf)?;
76        *offset += bytes_written as u64;
77        Ok(bytes_written)
78    }
79
80    /// Read at a specific offset without changing current offset (pread).
81    pub fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
82        if !self.open_flags.contains(OpenFlags::READ) {
83            return Err(SyscallError::PermissionDenied);
84        }
85        self.scheme.read(self.file_id, offset, buf)
86    }
87
88    /// Write at a specific offset without changing current offset (pwrite).
89    pub fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
90        if !self.open_flags.contains(OpenFlags::WRITE) {
91            return Err(SyscallError::PermissionDenied);
92        }
93        self.scheme.write(self.file_id, offset, buf)
94    }
95
96    /// Seek to a new offset (absolute).
97    pub fn seek(&self, new_offset: u64) -> Result<u64, SyscallError> {
98        let mut offset = self.offset.lock();
99        *offset = new_offset;
100        Ok(*offset)
101    }
102
103    /// POSIX lseek: whence=0 SET, 1 CUR, 2 END.
104    pub fn lseek(&self, off: i64, whence: u32) -> Result<u64, SyscallError> {
105        let mut cur = self.offset.lock();
106        let new_pos: i64 = match whence {
107            0 => off,
108            1 => (*cur as i64)
109                .checked_add(off)
110                .ok_or(SyscallError::InvalidArgument)?,
111            2 => {
112                let sz = self.size().unwrap_or(0) as i64;
113                sz.checked_add(off).ok_or(SyscallError::InvalidArgument)?
114            }
115            _ => return Err(SyscallError::InvalidArgument),
116        };
117        if new_pos < 0 {
118            return Err(SyscallError::InvalidArgument);
119        }
120        *cur = new_pos as u64;
121        Ok(*cur)
122    }
123
124    /// Get current offset.
125    pub fn tell(&self) -> u64 {
126        *self.offset.lock()
127    }
128
129    /// Get file size.
130    pub fn size(&self) -> Result<u64, SyscallError> {
131        if let Ok(sz) = self.scheme.size(self.file_id) {
132            Ok(sz)
133        } else if let Some(sz) = self.size {
134            Ok(sz)
135        } else {
136            Err(SyscallError::NotImplemented)
137        }
138    }
139
140    /// Sync file to storage.
141    pub fn sync(&self) -> Result<(), SyscallError> {
142        self.scheme.sync(self.file_id)
143    }
144
145    /// Signal that this file descriptor is being closed.
146    ///
147    /// Does NOT call scheme.close() directly; the actual scheme close is deferred
148    /// to the Drop impl so it fires only when the last Arc<OpenFile> is released
149    /// (POSIX: scheme is closed when all dup'd/fork'd copies are gone).
150    pub fn close(&self) -> Result<(), SyscallError> {
151        Ok(())
152    }
153
154    /// Get file flags.
155    pub fn flags(&self) -> FileFlags {
156        self.file_flags
157    }
158
159    /// Get open flags.
160    pub fn open_flags(&self) -> OpenFlags {
161        self.open_flags
162    }
163
164    /// Get path (for debugging).
165    pub fn path(&self) -> &str {
166        &self.path
167    }
168
169    /// Get file metadata.
170    pub fn stat(&self) -> Result<FileStat, SyscallError> {
171        self.scheme.stat(self.file_id)
172    }
173
174    /// List directory entries (only valid for directories).
175    pub fn readdir(&self) -> Result<Vec<DirEntry>, SyscallError> {
176        if !self.file_flags.contains(FileFlags::DIRECTORY) {
177            return Err(SyscallError::InvalidArgument);
178        }
179        self.scheme.readdir(self.file_id)
180    }
181
182    /// Get internal file_id (for scheme delegation).
183    pub fn file_id(&self) -> u64 {
184        self.file_id
185    }
186
187    /// Get the underlying scheme.
188    pub fn scheme(&self) -> &DynScheme {
189        &self.scheme
190    }
191}
192
193impl Drop for OpenFile {
194    /// Performs the drop operation.
195    fn drop(&mut self) {
196        // Best-effort close on drop
197        let _ = self.scheme.close(self.file_id);
198    }
199}