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            // Atomically seek to end under the offset lock.
69            // Use cached size first; fall back to scheme.size() only if unknown.
70            if let Some(sz) = self.size {
71                *offset = sz;
72            } else if let Ok(sz) = self.scheme.size(self.file_id) {
73                *offset = sz;
74            }
75            // If size is unknown and scheme.size() fails, write at current offset
76            // (best-effort append for schemes that don't track size).
77        }
78
79        let bytes_written = self.scheme.write(self.file_id, *offset, buf)?;
80        *offset += bytes_written as u64;
81        Ok(bytes_written)
82    }
83
84    /// Read at a specific offset without changing current offset (pread).
85    pub fn pread(&self, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
86        if !self.open_flags.contains(OpenFlags::READ) {
87            return Err(SyscallError::PermissionDenied);
88        }
89        self.scheme.read(self.file_id, offset, buf)
90    }
91
92    /// Write at a specific offset without changing current offset (pwrite).
93    pub fn pwrite(&self, offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
94        if !self.open_flags.contains(OpenFlags::WRITE) {
95            return Err(SyscallError::PermissionDenied);
96        }
97        self.scheme.write(self.file_id, offset, buf)
98    }
99
100    /// Seek to a new offset (absolute).
101    pub fn seek(&self, new_offset: u64) -> Result<u64, SyscallError> {
102        let mut offset = self.offset.lock();
103        *offset = new_offset;
104        Ok(*offset)
105    }
106
107    /// POSIX lseek: whence=0 SET, 1 CUR, 2 END.
108    pub fn lseek(&self, off: i64, whence: u32) -> Result<u64, SyscallError> {
109        let mut cur = self.offset.lock();
110        let new_pos: i64 = match whence {
111            0 => off,
112            1 => (*cur as i64)
113                .checked_add(off)
114                .ok_or(SyscallError::InvalidArgument)?,
115            2 => {
116                let sz = self.size().unwrap_or(0) as i64;
117                sz.checked_add(off).ok_or(SyscallError::InvalidArgument)?
118            }
119            _ => return Err(SyscallError::InvalidArgument),
120        };
121        if new_pos < 0 {
122            return Err(SyscallError::InvalidArgument);
123        }
124        *cur = new_pos as u64;
125        Ok(*cur)
126    }
127
128    /// Get current offset.
129    pub fn tell(&self) -> u64 {
130        *self.offset.lock()
131    }
132
133    /// Get file size.
134    pub fn size(&self) -> Result<u64, SyscallError> {
135        if let Ok(sz) = self.scheme.size(self.file_id) {
136            Ok(sz)
137        } else if let Some(sz) = self.size {
138            Ok(sz)
139        } else {
140            Err(SyscallError::NotImplemented)
141        }
142    }
143
144    /// Sync file to storage.
145    pub fn sync(&self) -> Result<(), SyscallError> {
146        self.scheme.sync(self.file_id)
147    }
148
149    /// Signal that this file descriptor is being closed.
150    ///
151    /// Does NOT call scheme.close() directly; the actual scheme close is deferred
152    /// to the Drop impl so it fires only when the last Arc<OpenFile> is released
153    /// (POSIX: scheme is closed when all dup'd/fork'd copies are gone).
154    pub fn close(&self) -> Result<(), SyscallError> {
155        Ok(())
156    }
157
158    /// Get file flags.
159    pub fn flags(&self) -> FileFlags {
160        self.file_flags
161    }
162
163    /// Get open flags.
164    pub fn open_flags(&self) -> OpenFlags {
165        self.open_flags
166    }
167
168    /// Get path (for debugging).
169    pub fn path(&self) -> &str {
170        &self.path
171    }
172
173    /// Get file metadata.
174    pub fn stat(&self) -> Result<FileStat, SyscallError> {
175        self.scheme.stat(self.file_id)
176    }
177
178    /// List directory entries (only valid for directories).
179    pub fn readdir(&self) -> Result<Vec<DirEntry>, SyscallError> {
180        if !self.file_flags.contains(FileFlags::DIRECTORY) {
181            return Err(SyscallError::InvalidArgument);
182        }
183        self.scheme.readdir(self.file_id)
184    }
185
186    /// Get internal file_id (for scheme delegation).
187    pub fn file_id(&self) -> u64 {
188        self.file_id
189    }
190
191    /// Get the underlying scheme.
192    pub fn scheme(&self) -> &DynScheme {
193        &self.scheme
194    }
195}
196
197impl Drop for OpenFile {
198    /// Performs the drop operation.
199    fn drop(&mut self) {
200        // Best-effort close on drop
201        let _ = self.scheme.close(self.file_id);
202    }
203}