Skip to main content

strat9_kernel/vfs/
scheme.rs

1//! Scheme abstraction : backends for VFS operations.
2//!
3//! Schemes provide the actual implementation for file operations.
4//! Examples: IPC-based schemes (ext4, network), kernel schemes (devfs, procfs).
5
6use crate::{
7    ipc::{message::IpcMessage, port::PortId},
8    memory::{UserSliceRead, UserSliceWrite},
9    sync::SpinLock,
10    syscall::error::SyscallError,
11};
12use alloc::{
13    collections::BTreeMap,
14    string::{String, ToString},
15    sync::Arc,
16    vec::Vec,
17};
18
19pub use strat9_abi::data::{
20    FileStat, DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK, DT_UNKNOWN,
21    IPC_FILE_FLAG_APPEND, IPC_FILE_FLAG_CHUNK_READ, IPC_FILE_FLAG_CHUNK_WRITE,
22    IPC_FILE_FLAG_DEVICE, IPC_FILE_FLAG_DIRECTORY, IPC_FILE_FLAG_PIPE,
23};
24use strat9_abi::{
25    ipc_codec::{get_u32, put_u16_len_prefixed},
26    ipc_payload::{
27        CloseRequest, CreateRequest, OpenReply, OpenRequest, ReadReply, ReadRequest, WriteRequest,
28        OPCODE_CLOSE, OPCODE_CREATE_DIR, OPCODE_CREATE_FILE, OPCODE_OPEN, OPCODE_READ,
29        OPCODE_READDIR, OPCODE_UNLINK, OPCODE_WRITE,
30    },
31};
32
33/// A single directory entry returned by readdir.
34#[derive(Debug, Clone)]
35pub struct DirEntry {
36    pub ino: u64,
37    pub file_type: u8,
38    pub name: String,
39}
40
41/// Result of an open operation.
42#[derive(Debug, Clone)]
43pub struct OpenResult {
44    /// Unique file handle (opaque to caller).
45    pub file_id: u64,
46    /// Size of the file (if known).
47    pub size: Option<u64>,
48    /// Flags describing the file (directory, device, etc.).
49    pub flags: FileFlags,
50}
51
52bitflags::bitflags! {
53    /// Flags describing a file's properties.
54    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
55    pub struct FileFlags: u32 {
56        const DIRECTORY   = IPC_FILE_FLAG_DIRECTORY;
57        const DEVICE      = IPC_FILE_FLAG_DEVICE;
58        const PIPE        = IPC_FILE_FLAG_PIPE;
59        const APPEND      = IPC_FILE_FLAG_APPEND;
60        const CHUNK_READ  = IPC_FILE_FLAG_CHUNK_READ;
61        const CHUNK_WRITE = IPC_FILE_FLAG_CHUNK_WRITE;
62    }
63}
64
65pub use strat9_abi::flag::OpenFlags;
66
67/// Abstraction for a filesystem/service backend.
68pub trait Scheme: Send + Sync {
69    /// Open a file/resource at the given path within this scheme.
70    ///
71    /// `path` is relative to the scheme's mount point.
72    /// Returns a unique file handle + metadata.
73    fn open(&self, path: &str, flags: OpenFlags) -> Result<OpenResult, SyscallError>;
74
75    /// Read bytes from an open file.
76    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError>;
77
78    /// Write bytes to an open file.
79    fn write(&self, file_id: u64, offset: u64, buf: &[u8]) -> Result<usize, SyscallError>;
80
81    /// Submit a read against a userspace buffer for async I/O.
82    ///
83    /// The default implementation performs the read synchronously and copies
84    /// the result back into the validated userspace slice before returning a
85    /// completed result.
86    fn async_read(
87        &self,
88        file_id: u64,
89        offset: u64,
90        user_buf_vaddr: u64,
91        len: usize,
92        _ring_id: u64,
93        _user_data: u64,
94    ) -> Result<AsyncSubmitResult, SyscallError> {
95        // Guard against userspace-controlled sizes causing kernel OOM.
96        // Schemes that need larger transfers must override async_read.
97        const MAX_SYNC_FALLBACK_LEN: usize = 4 * 1024 * 1024; // 4 MiB
98        if len > MAX_SYNC_FALLBACK_LEN {
99            return Err(SyscallError::InvalidArgument);
100        }
101        let user_buf = UserSliceWrite::new(user_buf_vaddr, len)?;
102        let mut kernel_buf = alloc::vec![0u8; len];
103        let n = self.read(file_id, offset, &mut kernel_buf)?;
104        user_buf.copy_from(&kernel_buf[..n]);
105        Ok(AsyncSubmitResult::Completed(n as i32))
106    }
107
108    /// Submit a write sourced from a userspace buffer for async I/O.
109    ///
110    /// The default implementation validates and copies the user buffer, then
111    /// performs the write synchronously before returning a completed result.
112    fn async_write(
113        &self,
114        file_id: u64,
115        offset: u64,
116        user_buf_vaddr: u64,
117        len: usize,
118        _ring_id: u64,
119        _user_data: u64,
120    ) -> Result<AsyncSubmitResult, SyscallError> {
121        // Guard against userspace-controlled sizes causing kernel OOM.
122        const MAX_SYNC_FALLBACK_LEN: usize = 4 * 1024 * 1024; // 4 MiB
123        if len > MAX_SYNC_FALLBACK_LEN {
124            return Err(SyscallError::InvalidArgument);
125        }
126        let user_buf = UserSliceRead::new(user_buf_vaddr, len)?;
127        let kernel_buf = user_buf.read_to_vec();
128        let n = self.write(file_id, offset, &kernel_buf)?;
129        Ok(AsyncSubmitResult::Completed(n as i32))
130    }
131
132    /// Close an open file.
133    fn close(&self, file_id: u64) -> Result<(), SyscallError>;
134
135    /// Get file size (if supported).
136    fn size(&self, file_id: u64) -> Result<u64, SyscallError> {
137        let _ = file_id;
138        Err(SyscallError::NotImplemented)
139    }
140
141    /// Truncate/resize a file (if supported).
142    fn truncate(&self, file_id: u64, new_size: u64) -> Result<(), SyscallError> {
143        let _ = (file_id, new_size);
144        Err(SyscallError::NotImplemented)
145    }
146
147    /// Truncate a file by path (avoids open/close round-trip).
148    ///
149    /// Default implementation returns NotImplemented, causing the caller
150    /// to fall back to open+truncate+close.
151    fn truncate_by_path(&self, _path: &str, _new_size: u64) -> Result<(), SyscallError> {
152        Err(SyscallError::NotImplemented)
153    }
154
155    /// Sync file to storage (if applicable).
156    fn sync(&self, file_id: u64) -> Result<(), SyscallError> {
157        let _ = file_id;
158        Ok(()) // No-op by default
159    }
160
161    /// Create a new regular file.
162    fn create_file(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
163        let _ = (path, mode);
164        Err(SyscallError::NotImplemented)
165    }
166
167    /// Create a new directory.
168    fn create_directory(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
169        let _ = (path, mode);
170        Err(SyscallError::NotImplemented)
171    }
172
173    /// Remove a file or directory.
174    fn unlink(&self, path: &str) -> Result<(), SyscallError> {
175        let _ = path;
176        Err(SyscallError::NotImplemented)
177    }
178
179    /// Get metadata for an open file.
180    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
181        let _ = file_id;
182        Err(SyscallError::NotImplemented)
183    }
184
185    /// Read directory entries from an open directory handle.
186    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
187        let _ = file_id;
188        Err(SyscallError::NotImplemented)
189    }
190
191    /// Rename/move an entry within this scheme.
192    fn rename(&self, old_path: &str, new_path: &str) -> Result<(), SyscallError> {
193        let _ = (old_path, new_path);
194        Err(SyscallError::NotImplemented)
195    }
196
197    /// Change permission bits on a path.
198    fn chmod(&self, path: &str, mode: u32) -> Result<(), SyscallError> {
199        let _ = (path, mode);
200        Err(SyscallError::NotImplemented)
201    }
202
203    /// Change permission bits on an open file handle.
204    fn fchmod(&self, file_id: u64, mode: u32) -> Result<(), SyscallError> {
205        let _ = (file_id, mode);
206        Err(SyscallError::NotImplemented)
207    }
208
209    /// Create a hard link.
210    fn link(&self, old_path: &str, new_path: &str) -> Result<(), SyscallError> {
211        let _ = (old_path, new_path);
212        Err(SyscallError::NotImplemented)
213    }
214
215    /// Create a symbolic link.
216    fn symlink(&self, target: &str, link_path: &str) -> Result<(), SyscallError> {
217        let _ = (target, link_path);
218        Err(SyscallError::NotImplemented)
219    }
220
221    /// Read the target of a symbolic link.
222    fn readlink(&self, path: &str) -> Result<String, SyscallError> {
223        let _ = path;
224        Err(SyscallError::NotImplemented)
225    }
226}
227
228/// Type-erased Scheme reference.
229pub type DynScheme = Arc<dyn Scheme>;
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232pub enum AsyncSubmitResult {
233    Completed(i32),
234    InFlight,
235}
236
237pub const DEV_RAMFS: u64 = 1;
238pub const DEV_SYSFS: u64 = 2;
239pub const DEV_PROCFS: u64 = 3;
240pub const DEV_DEVFS: u64 = 4;
241pub const DEV_CONSOLE: u64 = 5;
242pub const DEV_PIPEFS: u64 = 6;
243pub const DEV_IPCFS: u64 = 7;
244pub const DEV_NETFS: u64 = 8;
245pub const DEV_CHAR_FS: u64 = 9;
246pub const DEV_INPUT: u64 = 10;
247pub const DEV_THREADFS: u64 = 11;
248
249/// Finalize pseudo-filesystem stats with a stable device identity and
250/// synthetic timestamps.
251pub fn finalize_pseudo_stat(mut st: FileStat, st_dev: u64, st_rdev: u64) -> FileStat {
252    let now = strat9_abi::data::TimeSpec::from_nanos(crate::syscall::time::current_time_ns());
253    st.st_dev = st_dev;
254    st.st_rdev = st_rdev;
255    st.st_atime = now;
256    st.st_mtime = now;
257    st.st_ctime = now;
258    st
259}
260
261// ============================================================================
262// Built-in Schemes
263// ============================================================================
264
265/// IPC-based scheme: forwards operations to a userspace server via IPC.
266pub struct IpcScheme {
267    port_id: PortId,
268    open_file_flags: SpinLock<BTreeMap<u64, FileFlags>>,
269}
270
271impl IpcScheme {
272    /// Creates a new instance.
273    pub fn new(port_id: PortId) -> Self {
274        IpcScheme {
275            port_id,
276            open_file_flags: SpinLock::new(BTreeMap::new()),
277        }
278    }
279
280    fn remember_open_flags(&self, file_id: u64, flags: FileFlags) {
281        self.open_file_flags.lock().insert(file_id, flags);
282    }
283
284    fn take_open_flags(&self, file_id: u64) {
285        self.open_file_flags.lock().remove(&file_id);
286    }
287
288    fn open_flags_for(&self, file_id: u64) -> FileFlags {
289        self.open_file_flags
290            .lock()
291            .get(&file_id)
292            .copied()
293            .unwrap_or_else(FileFlags::empty)
294    }
295
296    /// Build an IPC message for open operation.
297    fn build_open_msg(path: &str, flags: OpenFlags) -> Result<IpcMessage, SyscallError> {
298        // Typed encode: bounds-checks the inline path; no hand-rolled offsets.
299        OpenRequest::encode(OPCODE_OPEN, flags.bits(), path).ok_or(SyscallError::InvalidArgument)
300        // path too long for inline
301    }
302
303    /// Build an IPC message for read operation.
304    fn build_read_msg(file_id: u64, offset: u64, count: u32) -> IpcMessage {
305        ReadRequest::encode(OPCODE_READ, file_id, offset, count)
306    }
307
308    /// Build an IPC message for write operation.
309    ///
310    /// Returns the message and the number of bytes actually packed, or
311    /// `MessageSize` if `data` exceeds the inline capacity (the caller's
312    /// chunking logic guarantees this never happens; we refuse rather
313    /// than truncate or emit an empty write).
314    fn build_write_msg(
315        file_id: u64,
316        offset: u64,
317        data: &[u8],
318    ) -> Result<(IpcMessage, usize), SyscallError> {
319        WriteRequest::encode(OPCODE_WRITE, file_id, offset, data).ok_or(SyscallError::MessageSize)
320    }
321
322    /// Build an IPC message for close operation.
323    fn build_close_msg(file_id: u64) -> IpcMessage {
324        CloseRequest::encode(OPCODE_CLOSE, file_id)
325    }
326
327    /// Performs the build readdir msg operation.
328    fn build_readdir_msg(file_id: u64, cursor: u16) -> IpcMessage {
329        // Wire layout: [ino: u64][cursor: u16 @ 8..10].
330        let mut msg = IpcMessage::new(OPCODE_READDIR);
331        msg.payload[0..8].copy_from_slice(&file_id.to_le_bytes());
332        msg.payload[8..10].copy_from_slice(&cursor.to_le_bytes());
333        msg
334    }
335
336    /// Parses status.
337    fn parse_status(reply: &IpcMessage) -> Result<(), SyscallError> {
338        if reply.msg_type != IpcMessage::REPLY_MSG_TYPE {
339            return Err(SyscallError::IoError);
340        }
341
342        let status = get_u32(&reply.payload, 0).ok_or(SyscallError::IoError)?;
343        if status == 0 {
344            return Ok(());
345        }
346
347        // Accept both forms:
348        // - positive errno (2 => ENOENT)
349        // - raw signed -errno encoded in u32
350        let signed = status as i32;
351        let code = if signed < 0 {
352            signed as i64
353        } else {
354            -(signed as i64)
355        };
356        Err(SyscallError::from_code(code))
357    }
358}
359
360impl IpcScheme {
361    /// Perform a synchronous IPC call: send `msg` to the server port and block
362    /// the current task until the server calls `ipc_reply`.  This mirrors
363    /// `sys_ipc_call` exactly so that `sys_ipc_reply` can correctly route the
364    /// reply back to us via `reply::deliver_reply`.
365    fn call(&self, mut msg: IpcMessage) -> Result<IpcMessage, SyscallError> {
366        let task_id = crate::process::current_task_id().ok_or(SyscallError::PermissionDenied)?;
367
368        // Stamp our task-id so the server knows where to deliver the reply.
369        msg.sender = task_id.as_u64();
370
371        let port = crate::ipc::port::get_port(self.port_id).ok_or(SyscallError::BadHandle)?;
372        let port_owner = port.owner;
373        port.send(msg).map_err(|_| SyscallError::BadHandle)?;
374        // Drop the Arc before blocking so we don't hold the port alive across
375        // a potentially long sleep.
376        drop(port);
377
378        Ok(crate::ipc::reply::wait_for_reply(task_id, port_owner))
379    }
380}
381
382impl Scheme for IpcScheme {
383    /// Performs the open operation.
384    fn open(&self, path: &str, flags: OpenFlags) -> Result<OpenResult, SyscallError> {
385        let msg = Self::build_open_msg(path, flags)?;
386        let reply = self.call(msg)?;
387
388        // Parse reply via the typed ABI struct:
389        // [status: u32][file_id: u64][size: u64][flags: u32]
390        Self::parse_status(&reply)?;
391
392        let reply = OpenReply::parse(&reply.payload).ok_or(SyscallError::IoError)?;
393        let file_id = reply.file_id;
394        let size = reply.size;
395        let file_flags = reply.file_flags;
396        let flags = FileFlags::from_bits_truncate(file_flags);
397        self.remember_open_flags(file_id, flags);
398
399        Ok(OpenResult {
400            file_id,
401            size: if size == u64::MAX { None } else { Some(size) },
402            flags,
403        })
404    }
405
406    /// Performs the read operation.
407    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
408        let flags = self.open_flags_for(file_id);
409        let chunked = flags.contains(FileFlags::CHUNK_READ);
410        let chunk_size = if chunked {
411            IpcMessage::READ_INLINE_CAPACITY
412        } else {
413            buf.len()
414        };
415
416        let mut total = 0usize;
417        let mut current_offset = offset;
418        while total < buf.len() {
419            let request_len = core::cmp::min(buf.len() - total, chunk_size);
420            let msg = Self::build_read_msg(file_id, current_offset, request_len as u32);
421            let reply = self.call(msg)?;
422
423            Self::parse_status(&reply)?;
424
425            // READ reply: [status][count u32 @ 4..8][data @ 8..] (ReadReply).
426            let count = get_u32(&reply.payload, 4).ok_or(SyscallError::IoError)? as usize;
427            let available = core::cmp::min(count, reply.payload.len() - ReadReply::DATA_OFFSET);
428            let to_copy = core::cmp::min(available, request_len);
429            buf[total..total + to_copy].copy_from_slice(
430                &reply.payload[ReadReply::DATA_OFFSET..ReadReply::DATA_OFFSET + to_copy],
431            );
432
433            total += to_copy;
434            current_offset += to_copy as u64;
435
436            if !chunked || to_copy < request_len {
437                break;
438            }
439        }
440
441        Ok(total)
442    }
443
444    /// Performs the write operation.
445    fn write(&self, file_id: u64, offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
446        let flags = self.open_flags_for(file_id);
447        let chunked = flags.contains(FileFlags::CHUNK_WRITE);
448        // Non-chunked handles (control endpoints, datagrams) must fit in one IPC
449        // message; chunked handles (streams, files) can split across multiple calls.
450        if !chunked && buf.len() > IpcMessage::WRITE_INLINE_CAPACITY {
451            return Err(SyscallError::MessageSize);
452        }
453        let chunk_size = if chunked {
454            IpcMessage::WRITE_INLINE_CAPACITY
455        } else {
456            buf.len()
457        };
458
459        let mut total = 0usize;
460        let mut current_offset = offset;
461        while total < buf.len() {
462            let request_len = core::cmp::min(buf.len() - total, chunk_size);
463            let (msg, packed) =
464                Self::build_write_msg(file_id, current_offset, &buf[total..total + request_len])?;
465            let reply = self.call(msg)?;
466
467            Self::parse_status(&reply)?;
468
469            // WRITE reply: [status][written u32 @ 4..8] (WriteReply).
470            let bytes_written = get_u32(&reply.payload, 4).ok_or(SyscallError::IoError)? as usize;
471
472            let chunk_written = bytes_written.min(packed);
473            total += chunk_written;
474            current_offset += chunk_written as u64;
475
476            if !chunked || chunk_written < packed {
477                break;
478            }
479        }
480
481        Ok(total)
482    }
483
484    /// Performs the close operation.
485    fn close(&self, file_id: u64) -> Result<(), SyscallError> {
486        let msg = Self::build_close_msg(file_id);
487        let reply = self.call(msg)?;
488        Self::parse_status(&reply)?;
489        self.take_open_flags(file_id);
490        Ok(())
491    }
492
493    /// Creates file.
494    fn create_file(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
495        self.handle_create_op(OPCODE_CREATE_FILE, path, mode)
496    }
497
498    /// Creates directory.
499    fn create_directory(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
500        self.handle_create_op(OPCODE_CREATE_DIR, path, mode)
501    }
502
503    /// Performs the unlink operation.
504    fn unlink(&self, path: &str) -> Result<(), SyscallError> {
505        let mut msg = IpcMessage::new(OPCODE_UNLINK);
506
507        if path.len() > IpcMessage::UNLINK_INLINE_CAPACITY {
508            return Err(SyscallError::InvalidArgument);
509        }
510
511        // Wire framing: [path_len: u16][path bytes...] (put_u16_len_prefixed).
512        put_u16_len_prefixed(&mut msg.payload, 0, path.as_bytes())
513            .ok_or(SyscallError::InvalidArgument)?;
514
515        let reply = self.call(msg)?;
516        Self::parse_status(&reply)?;
517
518        Ok(())
519    }
520
521    /// Performs the readdir operation.
522    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
523        const MAX_READDIR_ENTRIES: usize = 8192;
524        let mut cursor: u16 = 0;
525        let mut entries = Vec::new();
526
527        loop {
528            let msg = Self::build_readdir_msg(file_id, cursor);
529            let reply = self.call(msg)?;
530            Self::parse_status(&reply)?;
531
532            let next_cursor = u16::from_le_bytes([reply.payload[4], reply.payload[5]]);
533            let entry_count = reply.payload[6] as usize;
534            let used_bytes = reply.payload[7] as usize;
535            if used_bytes > reply.payload.len() - 8 {
536                return Err(SyscallError::IoError);
537            }
538
539            let mut offset = 8usize;
540            for _ in 0..entry_count {
541                if offset + 10 > 8 + used_bytes {
542                    return Err(SyscallError::IoError);
543                }
544
545                let ino = u64::from_le_bytes([
546                    reply.payload[offset],
547                    reply.payload[offset + 1],
548                    reply.payload[offset + 2],
549                    reply.payload[offset + 3],
550                    reply.payload[offset + 4],
551                    reply.payload[offset + 5],
552                    reply.payload[offset + 6],
553                    reply.payload[offset + 7],
554                ]);
555                let file_type = reply.payload[offset + 8];
556                let name_len = reply.payload[offset + 9] as usize;
557                if offset + 10 + name_len > 8 + used_bytes {
558                    return Err(SyscallError::IoError);
559                }
560                let name_bytes = &reply.payload[offset + 10..offset + 10 + name_len];
561                let name = core::str::from_utf8(name_bytes)
562                    .map_err(|_| SyscallError::IoError)?
563                    .to_string();
564
565                entries.push(DirEntry {
566                    ino,
567                    file_type,
568                    name,
569                });
570                offset += 10 + name_len;
571            }
572
573            // The cursor protocol bounds the number of round-trips, but a
574            // buggy or malicious server could keep us accumulating entries
575            // for up to 64K iterations. Cap the total to bound kernel
576            // memory under our trust boundary.
577            if entries.len() > MAX_READDIR_ENTRIES {
578                return Err(SyscallError::IoError);
579            }
580
581            if next_cursor == u16::MAX {
582                break;
583            }
584            if next_cursor <= cursor {
585                return Err(SyscallError::IoError);
586            }
587            cursor = next_cursor;
588        }
589
590        Ok(entries)
591    }
592}
593
594impl IpcScheme {
595    /// Handles create op.
596    fn handle_create_op(
597        &self,
598        opcode: u32,
599        path: &str,
600        mode: u32,
601    ) -> Result<OpenResult, SyscallError> {
602        // Typed encode: [mode: u32][path_len: u16][path bytes...]
603        let msg = CreateRequest::encode(opcode, mode, path).ok_or(SyscallError::InvalidArgument)?;
604
605        let reply = self.call(msg)?;
606
607        Self::parse_status(&reply)?;
608
609        // Reply layout: [status: u32][file_id: u64 @ 4..12] (CreateReply).
610        let file_id = {
611            let payload = &reply.payload;
612            if payload.len() < 12 {
613                return Err(SyscallError::IoError);
614            }
615            u64::from_le_bytes(
616                payload[4..12]
617                    .try_into()
618                    .map_err(|_| SyscallError::IoError)?,
619            )
620        };
621
622        Ok(OpenResult {
623            file_id,
624            size: Some(0),
625            flags: FileFlags::empty(),
626        })
627    }
628}
629
630/// Kernel-backed scheme: serves files from kernel memory (read-only).
631///
632/// SAFETY: All stored pointers are kernel-static (`'static`) and accessed
633/// only through the scheme trait methods which are `&self` (shared reference).
634pub struct KernelScheme {
635    /// Files indexed by path => (id, base, len).
636    files: SpinLock<BTreeMap<String, (u64, *const u8, usize)>>,
637    /// Reverse lookup: file_id => path name.
638    by_id: SpinLock<BTreeMap<u64, String>>,
639}
640
641// SAFETY: KernelScheme only stores kernel-static pointers that are valid
642// for the entire kernel lifetime. No mutable access through raw pointers.
643unsafe impl Send for KernelScheme {}
644unsafe impl Sync for KernelScheme {}
645
646impl KernelScheme {
647    /// Creates a new instance.
648    pub fn new() -> Self {
649        KernelScheme {
650            files: SpinLock::new(BTreeMap::new()),
651            by_id: SpinLock::new(BTreeMap::new()),
652        }
653    }
654
655    /// Register a static kernel file.
656    pub fn register(&self, path: &str, base: *const u8, len: usize) {
657        static NEXT_ID: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(1);
658        let id = NEXT_ID.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
659        self.files
660            .lock()
661            .insert(String::from(path), (id, base, len));
662        self.by_id.lock().insert(id, String::from(path));
663    }
664
665    /// Returns (id, base, len) for a given file_id.
666    fn get_by_id(&self, file_id: u64) -> Option<(u64, *const u8, usize)> {
667        let name = self.by_id.lock().get(&file_id)?.clone();
668        let entry = self.files.lock().get(&name).cloned()?;
669        Some(entry)
670    }
671
672    /// Returns the bytes of a registered static kernel file.
673    pub fn lookup_bytes(&self, path: &str) -> Option<&'static [u8]> {
674        let (_, base, len) = self.files.lock().get(path).cloned()?;
675        // SAFETY: initfs files are bootloader-provided mappings kept alive for
676        // the full kernel lifetime.
677        Some(unsafe { core::slice::from_raw_parts(base, len) })
678    }
679}
680
681impl Scheme for KernelScheme {
682    /// Performs the open operation.
683    fn open(&self, path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
684        if path.is_empty() || path == "/" {
685            return Ok(OpenResult {
686                file_id: 0, // Root directory ID
687                size: None,
688                flags: FileFlags::DIRECTORY,
689            });
690        }
691
692        let files = self.files.lock();
693        let (id, _, len) = files.get(path).ok_or(SyscallError::BadHandle)?;
694        Ok(OpenResult {
695            file_id: *id,
696            size: Some(*len as u64),
697            flags: FileFlags::empty(),
698        })
699    }
700
701    /// Performs the read operation.
702    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
703        if file_id == 0 {
704            // Handle directory listing for root
705            let mut list = String::new();
706            let files = self.files.lock();
707            for name in files.keys() {
708                list.push_str(name);
709                list.push('\n');
710            }
711
712            if offset >= list.len() as u64 {
713                return Ok(0);
714            }
715
716            let start = offset as usize;
717            let end = core::cmp::min(start + buf.len(), list.len());
718            let to_copy = end - start;
719            buf[..to_copy].copy_from_slice(&list.as_bytes()[start..end]);
720            return Ok(to_copy);
721        }
722
723        let (_, base, len) = self.get_by_id(file_id).ok_or(SyscallError::BadHandle)?;
724
725        if offset >= len as u64 {
726            return Ok(0);
727        }
728
729        let remaining = len - offset as usize;
730        let to_copy = core::cmp::min(remaining, buf.len());
731
732        // SAFETY: file.base is a kernel-static pointer, bounds checked above
733        unsafe {
734            let src = base.add(offset as usize);
735            core::ptr::copy_nonoverlapping(src, buf.as_mut_ptr(), to_copy);
736        }
737
738        Ok(to_copy)
739    }
740
741    /// Performs the write operation.
742    fn write(&self, _file_id: u64, _offset: u64, _buf: &[u8]) -> Result<usize, SyscallError> {
743        Err(SyscallError::PermissionDenied) // Read-only
744    }
745
746    /// Performs the close operation.
747    fn close(&self, _file_id: u64) -> Result<(), SyscallError> {
748        Ok(()) // No-op for kernel files
749    }
750
751    /// Performs the size operation.
752    fn size(&self, file_id: u64) -> Result<u64, SyscallError> {
753        let (_, _, len) = self.get_by_id(file_id).ok_or(SyscallError::BadHandle)?;
754        Ok(len as u64)
755    }
756
757    /// Performs the stat operation.
758    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
759        if file_id == 0 {
760            return Ok(finalize_pseudo_stat(
761                FileStat {
762                    st_ino: 0,
763                    st_mode: 0o040555,
764                    st_nlink: 2,
765                    st_size: 0,
766                    st_blksize: 512,
767                    st_blocks: 0,
768                    ..FileStat::zeroed()
769                },
770                DEV_SYSFS,
771                0,
772            ));
773        }
774        let (_, _, len) = self.get_by_id(file_id).ok_or(SyscallError::BadHandle)?;
775        Ok(finalize_pseudo_stat(
776            FileStat {
777                st_ino: file_id,
778                st_mode: 0o100444,
779                st_nlink: 1,
780                st_size: len as u64,
781                st_blksize: 512,
782                st_blocks: ((len as u64) + 511) / 512,
783                ..FileStat::zeroed()
784            },
785            DEV_SYSFS,
786            0,
787        ))
788    }
789
790    /// Performs the readdir operation.
791    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
792        if file_id != 0 {
793            return Err(SyscallError::InvalidArgument);
794        }
795        let files = self.files.lock();
796        let mut entries = Vec::new();
797        for (name, (id, _, _)) in files.iter() {
798            entries.push(DirEntry {
799                ino: *id,
800                file_type: DT_REG,
801                name: name.clone(),
802            });
803        }
804        Ok(entries)
805    }
806}