1use alloc::{
2 boxed::Box,
3 collections::BTreeMap,
4 format,
5 rc::Rc,
6 string::{String, ToString},
7 vec::Vec,
8};
9use core::cell::{Cell, RefCell};
10
11use crate::{
12 allocator::BlockAllocator,
13 block_device::BlockDevice,
14 cache::BlockCache,
15 commit::{CommitEntry, MetadataCommitWriter, checked_u10},
16 format::{
17 LFS_NULL, LFS_TYPE_CREATE, LFS_TYPE_CTZSTRUCT, LFS_TYPE_DELETE, LFS_TYPE_DIR,
18 LFS_TYPE_DIRSTRUCT, LFS_TYPE_HARDTAIL, LFS_TYPE_INLINESTRUCT, LFS_TYPE_MOVESTATE,
19 LFS_TYPE_REG, LFS_TYPE_SOFTTAIL, LFS_TYPE_SUPERBLOCK, LFS_TYPE_USERATTR, Tag, ctz, le32,
20 npw2, popc,
21 },
22 metadata::{FileData, FileRecord, GlobalState, MetadataPair, MetadataTail, StorageRef},
23 path::{components, join_path, normalize_dir_path},
24 types::{
25 Config, DirEntry, DirectoryUsage, Error, FileType, FilesystemLimits, FilesystemOptions,
26 FsInfo, Result, WalkEntry,
27 },
28 writer::ImageBuilder,
29};
30
31mod handles;
32mod mutable;
33mod read;
34
35const SUPPORTED_DISK_VERSION: u32 = 0x0002_0001;
36
37#[derive(Debug, Clone)]
45pub struct Filesystem<'a> {
46 image: ImageStorage<'a>,
47 cfg: Config,
48 root: MetadataPair,
49 info: FsInfo,
50 options: FilesystemOptions,
51 global_state: GlobalState,
52 allocation_seed: u32,
53}
54
55#[derive(Debug)]
63pub struct FilesystemMut<D: BlockDevice + 'static> {
64 fs: Filesystem<'static>,
65 device: Box<D>,
66 cache: BlockCache,
67 allocator: BlockAllocator,
68 block_cycles: Option<u32>,
69}
70
71pub struct FileWriter<'fs, D: BlockDevice + 'static> {
77 fs: &'fs mut FilesystemMut<D>,
78 path: String,
79 data: Vec<u8>,
80 pos: usize,
81 stream: Option<StreamingWrite>,
82}
83
84#[derive(Clone)]
88struct StreamingWrite {
89 allocator: BlockAllocator,
90 blocks: Vec<u32>,
91 current: Option<StreamingBlock>,
92 len: usize,
93 target: StreamingTarget,
94}
95
96#[derive(Clone)]
97struct StreamingBlock {
98 block: u32,
99 bytes: Vec<u8>,
100 off: usize,
101 mode: StreamingBlockMode,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105enum StreamingBlockMode {
106 New,
109 ExistingTail,
112}
113
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115enum StreamingTarget {
116 Create,
117 Replace,
118}
119
120#[derive(Clone)]
121struct MergeWrite {
122 original_len: usize,
123 patches: Vec<FilePatch>,
124}
125
126#[derive(Clone)]
127struct FilePatch {
128 off: usize,
129 data: Vec<u8>,
130}
131
132#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
133pub struct FileOptions {
134 read: bool,
135 write: bool,
136 create: bool,
137 create_new: bool,
138 truncate: bool,
139 append: bool,
140}
141
142pub struct FileHandle<'fs, D: BlockDevice + 'static> {
150 fs: &'fs mut FilesystemMut<D>,
151 path: String,
152 data: Vec<u8>,
155 pos: usize,
158 len: usize,
162 stream_read: bool,
165 stream_source: Option<FileData>,
169 stream_target: StreamingTarget,
173 stream: Option<StreamingWrite>,
176 merge: Option<MergeWrite>,
182 readable: bool,
183 writable: bool,
184 dirty: bool,
185}
186
187pub struct DirHandle<'fs, 'a> {
194 fs: &'fs Filesystem<'a>,
195 head: [u32; 2],
196 pos: usize,
197}
198
199enum ImageStorage<'a> {
200 Borrowed(&'a [u8]),
201 Owned(Vec<u8>),
202 Device {
203 device: &'a dyn BlockDevice,
204 cache: Rc<ReadBlockCache>,
205 },
206}
207
208impl core::fmt::Debug for ImageStorage<'_> {
209 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210 match self {
211 Self::Borrowed(image) => f
212 .debug_tuple("Borrowed")
213 .field(&format_args!("{} bytes", image.len()))
214 .finish(),
215 Self::Owned(image) => f
216 .debug_tuple("Owned")
217 .field(&format_args!("{} bytes", image.len()))
218 .finish(),
219 Self::Device { .. } => f.debug_tuple("Device").field(&"<block-device>").finish(),
220 }
221 }
222}
223
224impl<'a> Clone for ImageStorage<'a> {
225 fn clone(&self) -> Self {
226 match self {
227 Self::Borrowed(image) => Self::Borrowed(*image),
228 Self::Owned(image) => Self::Owned(image.clone()),
229 Self::Device { device, cache } => Self::Device {
230 device: *device,
231 cache: cache.clone(),
232 },
233 }
234 }
235}
236
237#[derive(Debug)]
238struct ReadBlockCache {
239 slots: RefCell<Vec<ReadBlockCacheSlot>>,
240 next: Cell<usize>,
241 chunk_size: usize,
242}
243
244#[derive(Debug)]
245struct ReadBlockCacheSlot {
246 block: Option<u32>,
247 off: usize,
248 len: usize,
249 data: Vec<u8>,
250}
251
252impl ReadBlockCache {
253 fn new(cache_size: usize, slots: usize) -> Self {
254 let slots = core::cmp::max(slots, 1);
255 let cache_size = core::cmp::max(cache_size, 1);
256 Self {
257 slots: RefCell::new(
258 (0..slots)
259 .map(|_| ReadBlockCacheSlot {
260 block: None,
261 off: 0,
262 len: 0,
263 data: alloc::vec![0xff; cache_size],
264 })
265 .collect(),
266 ),
267 next: Cell::new(0),
268 chunk_size: cache_size,
269 }
270 }
271
272 fn read(
273 &self,
274 device: &dyn BlockDevice,
275 cfg: Config,
276 block: u32,
277 off: usize,
278 out: &mut [u8],
279 ) -> Result<()> {
280 if off.checked_add(out.len()).ok_or(Error::OutOfBounds)? > cfg.block_size {
281 return Err(Error::OutOfBounds);
282 }
283
284 let mut copied = 0usize;
285 while copied < out.len() {
286 let absolute = off + copied;
287 let chunk_off = (absolute / self.chunk_size) * self.chunk_size;
288 let chunk_len = core::cmp::min(self.chunk_size, cfg.block_size - chunk_off);
289 let in_chunk = absolute - chunk_off;
290 let len = core::cmp::min(out.len() - copied, chunk_len - in_chunk);
291 self.read_chunk(
292 device,
293 block,
294 chunk_off,
295 chunk_len,
296 in_chunk,
297 &mut out[copied..copied + len],
298 )?;
299 copied += len;
300 }
301 Ok(())
302 }
303
304 fn read_chunk(
305 &self,
306 device: &dyn BlockDevice,
307 block: u32,
308 off: usize,
309 chunk_len: usize,
310 in_chunk: usize,
311 out: &mut [u8],
312 ) -> Result<()> {
313 let mut slots = self.slots.borrow_mut();
314 if let Some(slot) = slots
315 .iter()
316 .find(|slot| slot.block == Some(block) && slot.off == off && slot.len == chunk_len)
317 {
318 out.copy_from_slice(&slot.data[in_chunk..in_chunk + out.len()]);
319 return Ok(());
320 }
321
322 let slot_index = self.next.get() % slots.len();
323 self.next.set((slot_index + 1) % slots.len());
324 let slot = &mut slots[slot_index];
325 if slot.data.len() < chunk_len {
326 slot.data.resize(chunk_len, 0xff);
327 }
328 device.read(block, off, &mut slot.data[..chunk_len])?;
329 slot.block = Some(block);
330 slot.off = off;
331 slot.len = chunk_len;
332 out.copy_from_slice(&slot.data[in_chunk..in_chunk + out.len()]);
333 Ok(())
334 }
335}
336
337fn ctz_data_start(index: usize) -> Result<usize> {
338 if index == 0 {
339 Ok(0)
340 } else {
341 let skips = index.trailing_zeros() as usize + 1;
342 skips.checked_mul(4).ok_or(Error::NoSpace)
343 }
344}
345
346fn program_nor_bytes(block: &mut [u8], off: usize, data: &[u8]) -> Result<()> {
347 let end = off.checked_add(data.len()).ok_or(Error::NoSpace)?;
348 if end > block.len() {
349 return Err(Error::NoSpace);
350 }
351 for (dst, src) in block[off..end].iter_mut().zip(data) {
352 *dst &= *src;
353 }
354 Ok(())
355}
356
357fn root_create_id(files: &[FileRecord], name: &str) -> Result<u16> {
358 let id = files
359 .iter()
360 .filter(|file| file.name.as_str() < name)
361 .count()
362 .checked_add(1)
363 .ok_or(Error::Unsupported)?;
364 let id = u16::try_from(id).map_err(|_| Error::Unsupported)?;
365 if id < 0x3ff {
366 Ok(id)
367 } else {
368 Err(Error::Unsupported)
369 }
370}
371
372fn dir_create_id(files: &[FileRecord], name: &str) -> Result<u16> {
373 let id = files
374 .iter()
375 .filter(|file| file.name.as_str() < name)
376 .count();
377 let id = u16::try_from(id).map_err(|_| Error::Unsupported)?;
378 if id < 0x3ff {
379 Ok(id)
380 } else {
381 Err(Error::Unsupported)
382 }
383}