ove/
fs.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Filesystem abstraction for oveRTOS.
8//!
9//! Provides RAII [`File`] and [`Dir`] handles for reading, writing, and
10//! directory traversal. All paths must be null-terminated byte slices
11//! (e.g. `b"/data/config.bin\0"`).
12
13use crate::bindings;
14use crate::error::{Error, Result};
15
16/// Open flag: open file for reading.
17pub const O_READ: i32 = bindings::OVE_FS_O_READ as i32;
18/// Open flag: open file for writing.
19pub const O_WRITE: i32 = bindings::OVE_FS_O_WRITE as i32;
20/// Open flag: create the file if it does not exist.
21pub const O_CREATE: i32 = bindings::OVE_FS_O_CREATE as i32;
22/// Open flag: all writes append to the end of the file.
23pub const O_APPEND: i32 = bindings::OVE_FS_O_APPEND as i32;
24
25/// Mount a filesystem.
26///
27/// Pass `None` for `dev` or `mnt` to use platform defaults.
28pub fn mount(dev: Option<&[u8]>, mnt: Option<&[u8]>) -> Result<()> {
29    let dev_ptr = dev.map_or(core::ptr::null(), |d| d.as_ptr() as *const _);
30    let mnt_ptr = mnt.map_or(core::ptr::null(), |m| m.as_ptr() as *const _);
31    let rc = unsafe { bindings::ove_fs_mount(dev_ptr, mnt_ptr) };
32    Error::from_code(rc)
33}
34
35/// RAII file handle.
36pub struct File {
37    handle: bindings::ove_file_t,
38}
39
40impl File {
41    /// Open a file. `path` must be `\0`-terminated.
42    pub fn open(path: &[u8], flags: i32) -> Result<Self> {
43        let mut handle: bindings::ove_file_t = core::ptr::null_mut();
44        let rc =
45            unsafe { bindings::ove_fs_open(&mut handle, path.as_ptr() as *const _, flags) };
46        Error::from_code(rc)?;
47        Ok(Self { handle })
48    }
49
50    /// Read from the file into `buf`. Returns the number of bytes read.
51    pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
52        let mut bytes_read: usize = 0;
53        let rc = unsafe {
54            bindings::ove_fs_read(
55                self.handle,
56                buf.as_mut_ptr() as *mut _,
57                buf.len(),
58                &mut bytes_read,
59            )
60        };
61        Error::from_code(rc)?;
62        Ok(bytes_read)
63    }
64
65    /// Write `buf` to the file. Returns the number of bytes written.
66    pub fn write(&self, buf: &[u8]) -> Result<usize> {
67        let mut bytes_written: usize = 0;
68        let rc = unsafe {
69            bindings::ove_fs_write(
70                self.handle,
71                buf.as_ptr() as *const _,
72                buf.len(),
73                &mut bytes_written,
74            )
75        };
76        Error::from_code(rc)?;
77        Ok(bytes_written)
78    }
79}
80
81impl Drop for File {
82    fn drop(&mut self) {
83        unsafe { bindings::ove_fs_close(self.handle) };
84    }
85}
86
87// SAFETY: File wraps an opaque RTOS handle. Access is single-threaded by
88// application design (only one thread owns the File at a time).
89unsafe impl Send for File {}
90
91/// A directory entry returned by `Dir::read_entry`.
92pub struct DirEntry {
93    inner: bindings::ove_dirent,
94}
95
96impl DirEntry {
97    /// The entry name as a byte slice (without trailing `\0`).
98    pub fn name(&self) -> &[u8] {
99        let name_bytes = unsafe {
100            core::slice::from_raw_parts(self.inner.name.as_ptr() as *const u8, self.inner.name.len())
101        };
102        cstr_to_slice(name_bytes)
103    }
104
105    /// The file size in bytes.
106    pub fn size(&self) -> u32 {
107        self.inner.size
108    }
109
110    /// Whether this entry is a directory.
111    pub fn is_dir(&self) -> bool {
112        self.inner.is_dir != 0
113    }
114}
115
116/// RAII directory handle.
117pub struct Dir {
118    handle: bindings::ove_dir_t,
119}
120
121impl Dir {
122    /// Open a directory. `path` must be `\0`-terminated.
123    pub fn open(path: &[u8]) -> Result<Self> {
124        let mut handle: bindings::ove_dir_t = core::ptr::null_mut();
125        let rc =
126            unsafe { bindings::ove_fs_opendir(&mut handle, path.as_ptr() as *const _) };
127        Error::from_code(rc)?;
128        Ok(Self { handle })
129    }
130
131    /// Read the next directory entry.
132    ///
133    /// Returns `Ok(None)` at end-of-directory. Different backends signal
134    /// end-of-dir differently (error code vs. empty name), so both are
135    /// treated as `Ok(None)`.
136    pub fn read_entry(&mut self) -> Result<Option<DirEntry>> {
137        let mut entry: bindings::ove_dirent = unsafe { core::mem::zeroed() };
138        let rc = unsafe { bindings::ove_fs_readdir(self.handle, &mut entry) };
139        if rc != 0 || entry.name[0] == 0 {
140            return Ok(None);
141        }
142        Ok(Some(DirEntry { inner: entry }))
143    }
144}
145
146impl Drop for Dir {
147    fn drop(&mut self) {
148        unsafe { bindings::ove_fs_closedir(self.handle) };
149    }
150}
151
152// SAFETY: Dir wraps an opaque RTOS handle. Access is single-threaded by
153// application design.
154unsafe impl Send for Dir {}
155
156fn cstr_to_slice(buf: &[u8]) -> &[u8] {
157    let mut len = 0;
158    while len < buf.len() && buf[len] != 0 {
159        len += 1;
160    }
161    &buf[..len]
162}