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