Skip to main content

littlefs_rust/
types.rs

1use alloc::string::String;
2
3/// Errors returned by the parser and mounted write API.
4///
5/// The variants intentionally mirror the littlefs errno classes once those
6/// differences become visible through the public filesystem API. Internal
7/// implementation limits still use `Unsupported` until a C-observable behavior
8/// demands a narrower error.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum Error {
11    InvalidConfig,
12    OutOfBounds,
13    Corrupt,
14    NotFound,
15    AlreadyExists,
16    NotDir,
17    IsDir,
18    NotEmpty,
19    BadFileDescriptor,
20    InvalidPath,
21    NameTooLong,
22    FileTooLarge,
23    Unsupported,
24    NoSpace,
25    Utf8,
26    Io,
27}
28
29pub type Result<T> = core::result::Result<T, Error>;
30
31/// Minimal mount-time geometry required to read an existing image.
32///
33/// The superblock is still checked after parsing; this config tells the parser
34/// how to slice the raw byte image into logical blocks.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct Config {
37    pub block_size: usize,
38    pub block_count: usize,
39}
40
41impl Config {
42    pub const DEFAULT_CACHE_SIZE: usize = 256;
43
44    #[cfg(any(feature = "std", test))]
45    pub(crate) fn cache_size(self) -> usize {
46        core::cmp::min(self.block_size, Self::DEFAULT_CACHE_SIZE)
47    }
48}
49
50/// Policy knobs that mirror the non-callback fields of C littlefs'
51/// `struct lfs_config`.
52///
53/// `Config` deliberately remains only the physical geometry. That keeps the
54/// old API and all block devices simple, while this type carries the settings
55/// that affect formatting, mounting caches, metadata commit padding, write
56/// thresholds, and the limits recorded in the superblock. The default values
57/// match the C fixture geometry used by the test oracle, with cache/prog sizes
58/// kept at the current low-memory Rust defaults.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct FilesystemOptions {
61    pub read_size: usize,
62    pub prog_size: usize,
63    pub cache_size: usize,
64    pub lookahead_size: usize,
65    pub block_cycles: Option<u32>,
66    pub name_max: u32,
67    pub file_max: u32,
68    pub attr_max: u32,
69    pub metadata_max: Option<usize>,
70    pub inline_max: InlineMax,
71}
72
73/// Inline-file limit policy.
74///
75/// C littlefs gives `inline_max = 0` the special meaning "derive the largest
76/// safe inline threshold", while `(lfs_size_t)-1` disables inline storage. A
77/// Rust `usize` alone cannot represent both sentinels cleanly, so the public
78/// API uses an explicit enum.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum InlineMax {
81    /// Derive C's default `min(cache_size, attr_max, metadata_limit / 8)`.
82    Default,
83    /// Disable non-empty inline file data, matching C's `(lfs_size_t)-1`.
84    Disabled,
85    /// Use an explicit upper bound after validating it against C's constraints.
86    Limit(usize),
87}
88
89impl Default for FilesystemOptions {
90    fn default() -> Self {
91        Self {
92            read_size: 16,
93            prog_size: 16,
94            cache_size: Config::DEFAULT_CACHE_SIZE,
95            // The C fixture currently uses a 64-byte lookahead. Our allocator
96            // still keeps an in-memory bitmap, but accepting the field now
97            // makes the public API shape match littlefs and gives the future
98            // checkpoint allocator a stable home.
99            lookahead_size: 64,
100            block_cycles: Some(128),
101            name_max: 255,
102            file_max: 2_147_483_647,
103            attr_max: 1_022,
104            metadata_max: None,
105            inline_max: InlineMax::Default,
106        }
107    }
108}
109
110impl FilesystemOptions {
111    pub(crate) fn validate(self, cfg: Config) -> Result<Self> {
112        if cfg.block_size == 0
113            || cfg.block_count < 2
114            || self.read_size == 0
115            || self.prog_size == 0
116            || self.cache_size == 0
117            || self.lookahead_size == 0
118        {
119            return Err(Error::InvalidConfig);
120        }
121
122        // These are the same divisibility relationships C checks before it
123        // allocates caches or performs alignment arithmetic.
124        let cache_size = self.cache_size_for(cfg);
125        if cache_size % self.read_size != 0
126            || cache_size % self.prog_size != 0
127            || cfg.block_size % cache_size != 0
128        {
129            return Err(Error::InvalidConfig);
130        }
131        if !self.prog_size.is_power_of_two() {
132            return Err(Error::InvalidConfig);
133        }
134        if self.lookahead_size % 8 != 0 {
135            return Err(Error::InvalidConfig);
136        }
137        if matches!(self.block_cycles, Some(0)) {
138            return Err(Error::InvalidConfig);
139        }
140        if self.name_max == 0
141            || self.name_max > 1_022
142            || self.file_max == 0
143            || self.file_max > 2_147_483_647
144            || self.attr_max == 0
145            || self.attr_max > 1_022
146        {
147            return Err(Error::InvalidConfig);
148        }
149
150        if let Some(metadata_max) = self.metadata_max {
151            if metadata_max == 0
152                || metadata_max > cfg.block_size
153                || metadata_max % self.read_size != 0
154                || metadata_max % self.prog_size != 0
155                || cfg.block_size % metadata_max != 0
156            {
157                return Err(Error::InvalidConfig);
158            }
159        }
160
161        if let InlineMax::Limit(limit) = self.inline_max {
162            let metadata_limit = self.metadata_max.unwrap_or(cfg.block_size);
163            if limit > cache_size || limit > self.attr_max as usize || limit > metadata_limit / 8 {
164                return Err(Error::InvalidConfig);
165            }
166        }
167
168        Ok(self)
169    }
170
171    pub(crate) fn cache_size_for(self, cfg: Config) -> usize {
172        core::cmp::min(self.cache_size, cfg.block_size)
173    }
174
175    pub(crate) fn inline_threshold(self, cfg: Config, attr_max: u32) -> usize {
176        match self.inline_max {
177            InlineMax::Default => {
178                let metadata_limit = self.metadata_max.unwrap_or(cfg.block_size);
179                core::cmp::min(
180                    self.cache_size_for(cfg),
181                    core::cmp::min(attr_max as usize, metadata_limit / 8),
182                )
183            }
184            InlineMax::Disabled => 0,
185            InlineMax::Limit(limit) => core::cmp::min(limit, attr_max as usize),
186        }
187    }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct FsInfo {
192    pub disk_version: u32,
193    pub block_size: u32,
194    pub block_count: u32,
195    pub name_max: u32,
196    pub file_max: u32,
197    pub attr_max: u32,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct FilesystemLimits {
202    pub block_size: u32,
203    pub block_count: u32,
204    pub name_max: u32,
205    pub file_max: u32,
206    pub attr_max: u32,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct DirectoryUsage {
211    pub entry_count: usize,
212    pub metadata_pair_count: usize,
213    pub is_split: bool,
214    pub append_bytes_used: usize,
215    pub append_bytes_remaining: usize,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum FileType {
220    File,
221    Dir,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct DirEntry {
226    pub name: String,
227    pub ty: FileType,
228    pub size: u32,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct WalkEntry {
233    pub path: String,
234    pub entry: DirEntry,
235}