srfs_core/
layout.rs

1/// Various data structures as they are on the permanent storage.
2///
3/// EntryMetadata is the same for files and directories.
4///
5/// Directories:
6/// - if the number of entries is at most MAX_ENTRIES_IN_META_BLOCK (14), then only the
7///   metadata block contains entries;
8/// - if the number of entries is at most MAX_ENTRIES_ONLY_DATA_BLOCKS (7440), then the
9///   metadata block lists data blocks;
10/// - if the number of entries is more than MAX_ENTRIES_ONLY_DATA_BLOCKS, but less than
11///   MAX_DIR_ENTRIES (65536), the metadata block lists block lists, which in turn list
12///   data blocks with entries.
13/// - TODO: the number of directory entries is limited to 64k because to find an entry
14///   by its name currently requires a linear search; we should add a hashmap to speed
15///   things up.
16///
17/// Files:
18/// - if file size is at most MAX_BYTES_IN_META_BLOCK, then the metadata block has the bytes
19///   immediately after EntryMetadata;
20/// - otherwise all file data are in data blocks;
21///   - if the file size is no more than MAX_BYTES_ONLY_DATA_BLOCKS (~2M), the metadata block
22///     lists data blocks;
23///   - if the file size is no more than MAX_BYTES_SINGLE_LEVEL_LIST_BLOCKS (~1G), the metadata
24///     block lists the first level block list blocks;
25///   - if the file size is no more than MAX_BYTES_LIST_OF_LISTS_BLOCKS (~500G), the metadata
26///     block lists second-level list-of-list blocks, which list the first level block list
27///     blocks which list data blocks;
28///   - file sizes above that are currently not supported, but it will be easy to continue with
29///     the same approach.
30use core::{mem::MaybeUninit, ptr::copy_nonoverlapping};
31
32#[cfg(feature = "std")]
33extern crate std;
34
35use super::*;
36
37use alloc::boxed::Box;
38use alloc::string::String;
39use alloc::borrow::ToOwned;
40
41use crc::CRC_32_ISO_HDLC;
42
43const CRC32: crc::Crc<u32> = crc::Crc::<u32>::new(&CRC_32_ISO_HDLC);
44const CRC32_VERIFY: u32 = 0x2144DF1C;
45
46pub(crate) fn crc32_hash(bytes: &[u8]) -> u32 {
47    let mut digest = CRC32.digest();
48    digest.update(bytes);
49    digest.finalize()
50}
51
52pub(crate) fn crc32_verify(bytes: &[u8]) -> Result<(), FsError> {
53    if CRC32_VERIFY == crc32_hash(bytes) {
54        Ok(())
55    } else {
56        Err(FsError::ValidationFailed)
57    }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum EntryKind {
62    Directory,
63    File,
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67#[repr(C)]
68pub struct EntryId {
69    /// The number of the block the Entry physically resides on.
70    /// Never changes, but can be re-used.
71    pub(crate) block_no: u64,
72    /// A unique number, to prevent ABA issues. Odd => dir, even => file.
73    /// Never changes and is never re-used.
74    pub(crate) generation: u64,
75}
76
77pub const ROOT_DIR_ID: EntryId = EntryId {
78    block_no: 1,
79    generation: 1,
80};
81
82impl EntryId {
83    pub(crate) fn new(block_no: u64, generation: u64) -> Self {
84        Self {
85            block_no,
86            generation,
87        }
88    }
89
90    pub fn kind(&self) -> EntryKind {
91        if (self.generation & 1) == 1 {
92            EntryKind::Directory
93        } else {
94            EntryKind::File
95        }
96    }
97}
98
99#[derive(Clone, Copy, Debug)]
100#[repr(C)]
101pub struct Timestamp {
102    pub secs: u64,
103    pub ns: u32,
104    _pad: u32,
105}
106
107impl Timestamp {
108    pub fn now() -> Self {
109        {
110            #![cfg(feature = "std")]
111            let ts = std::time::UNIX_EPOCH.elapsed().unwrap();
112
113            Self {
114                secs: ts.as_secs(),
115                ns: ts.subsec_nanos(),
116                _pad: 0,
117            }
118        }
119        {
120            #![cfg(not(feature = "std"))]
121            Self {
122                secs: 0,
123                ns: 0,
124                _pad: 0,
125            }
126        }
127    }
128
129    pub const fn zero() -> Self {
130        Self {
131            secs: 0,
132            ns: 0,
133            _pad: 0,
134        }
135    }
136}
137
138#[cfg(feature = "std")]
139impl From<Timestamp> for std::time::SystemTime {
140    fn from(ts: Timestamp) -> Self {
141        let dur = std::time::Duration::new(ts.secs, ts.ns);
142        std::time::SystemTime::checked_add(&std::time::UNIX_EPOCH, dur).unwrap()
143    }
144}
145
146// An entry in a directory.
147#[derive(Clone, Copy)]
148#[repr(C)]
149pub(crate) struct DirEntryInternal {
150    pub id: EntryId,
151    pub name: [u8; 255], // UTF-8.
152    pub name_len: u8,
153}
154
155const _: () = assert!(core::mem::size_of::<DirEntryInternal>() == 272);
156
157impl DirEntryInternal {
158    pub fn set_name(&mut self, name: &str) {
159        let bytes = name.as_bytes();
160        assert!(bytes.len() <= 255);
161
162        unsafe { copy_nonoverlapping(bytes.as_ptr(), self.name.as_mut_ptr(), bytes.len()) };
163        self.name_len = bytes.len() as u8;
164    }
165
166    pub fn to_owned(&self) -> Result<DirEntry, FsError> {
167        Ok(DirEntry {
168            id: self.id,
169            name: core::str::from_utf8(&self.name[0..(self.name_len as usize)])
170                .map_err(|_| FsError::Utf8Error)?
171                .to_owned(),
172        })
173    }
174}
175
176#[derive(Debug)]
177pub struct DirEntry {
178    pub id: EntryId,
179    pub name: String,
180}
181
182#[derive(Clone, Copy, Debug)]
183#[repr(C)]
184pub(crate) struct EntryMetadata {
185    pub id: EntryId,
186    pub parent_id: EntryId,
187    pub size: u64, // File size in bytes, or the number of directory entries.
188    pub created: Timestamp,
189    pub modified: Timestamp,
190    pub accessed: Timestamp, // Usually not tracked.
191    pub user_data: [u64; 4], // Whatever, e.g. permissions, uid/gid, etc.
192    pub _reserved_2: u32,
193    pub crc32: u32, // CRC32 of this data structure.
194}
195
196const _: () = assert!(core::mem::size_of::<EntryMetadata>() == 128);
197const _: () = assert!(core::mem::size_of::<EntryMetadata>() < (BLOCK_SIZE as usize));
198
199// In the metadata block, we put dir entries at the same place as in data blocks, leaving
200// the space at the beginning of the block to be used by EntryMetadata.
201const _: () =
202    assert!(core::mem::size_of::<EntryMetadata>() <= core::mem::size_of::<DirEntryInternal>());
203
204// Max entries in data block (leaf directory lists).
205pub(crate) const MAX_ENTRIES_IN_DATA_BLOCK: u64 =
206    BLOCK_SIZE / (core::mem::size_of::<DirEntryInternal>() as u64);
207const _: () = assert!(MAX_ENTRIES_IN_DATA_BLOCK == 15);
208
209// Directories with entries <= MAX_ENTRIES_IN_SELF_BLOCK occupy a single block.
210pub(crate) const MAX_ENTRIES_IN_META_BLOCK: u64 = MAX_ENTRIES_IN_DATA_BLOCK - 1;
211const _: () = assert!(MAX_ENTRIES_IN_META_BLOCK == 14);
212
213// The max number of directory entries without separate block lists.
214pub(crate) const MAX_ENTRIES_ONLY_DATA_BLOCKS: u64 =
215    MAX_LINKS_IN_META_BLOCK * MAX_ENTRIES_IN_DATA_BLOCK;
216const _: () = assert!(MAX_ENTRIES_ONLY_DATA_BLOCKS == 7440);
217
218pub(crate) const MAX_ENTRIES_COVERED_BY_FIRST_LEVEL_BLOCKLIST: u64 =
219    MAX_ENTRIES_IN_DATA_BLOCK * 512;
220const _: () = assert!(MAX_ENTRIES_COVERED_BY_FIRST_LEVEL_BLOCKLIST == 7680);
221
222// Files with size <= MAX_BYTES_IN_SELF_BLOCK occupy a single block.
223pub(crate) const MAX_BYTES_IN_META_BLOCK: u64 =
224    BLOCK_SIZE - (core::mem::size_of::<EntryMetadata>() as u64);
225const _: () = assert!(MAX_BYTES_IN_META_BLOCK == 3968);
226
227// The max number of blocks referenced in the meta block of a file.
228pub(crate) const MAX_LINKS_IN_META_BLOCK: u64 = MAX_BYTES_IN_META_BLOCK / 8;
229const _: () = assert!(MAX_LINKS_IN_META_BLOCK == 496);
230
231// The max size of a file without separate block list blocks.
232pub(crate) const MAX_BYTES_ONLY_DATA_BLOCKS: u64 = MAX_LINKS_IN_META_BLOCK * BLOCK_SIZE;
233const _: () = assert!(MAX_BYTES_ONLY_DATA_BLOCKS == 2_031_616);
234
235pub(crate) const BYTES_COVERED_BY_FIRST_LEVEL_BLOCKLIST: u64 = BLOCK_SIZE * BLOCK_SIZE / 8;
236const _: () = assert!(BYTES_COVERED_BY_FIRST_LEVEL_BLOCKLIST == 4096 * 512); // 2M.
237
238// The max size of a file without separate list-of-list blocks.
239pub(crate) const MAX_BYTES_SINGLE_LEVEL_LIST_BLOCKS: u64 =
240    MAX_LINKS_IN_META_BLOCK * BYTES_COVERED_BY_FIRST_LEVEL_BLOCKLIST;
241const _: () = assert!(MAX_BYTES_SINGLE_LEVEL_LIST_BLOCKS == 1_040_187_392); // ~1G.
242
243pub(crate) const BYTES_COVERED_BY_SECOND_LEVEL_BLOCKLIST: u64 =
244    BYTES_COVERED_BY_FIRST_LEVEL_BLOCKLIST * 512;
245const _: () = assert!(BYTES_COVERED_BY_SECOND_LEVEL_BLOCKLIST == 1024 * 1024 * 1024); // 1G.
246
247// The max size of a file with list-of-list blocks.
248pub(crate) const MAX_BYTES_LIST_OF_LISTS_BLOCKS: u64 =
249    MAX_LINKS_IN_META_BLOCK * BYTES_COVERED_BY_SECOND_LEVEL_BLOCKLIST;
250const _: () = assert!(MAX_BYTES_LIST_OF_LISTS_BLOCKS == 532_575_944_704); // ~500G.
251
252impl EntryMetadata {
253    pub fn new(id: EntryId, parent_id: EntryId) -> Self {
254        let now = Timestamp::now();
255        Self {
256            id,
257            parent_id,
258            size: 0,
259            created: now,
260            modified: now,
261            accessed: Timestamp::zero(),
262            user_data: [0; 4],
263            _reserved_2: 0,
264            crc32: 0,
265        }
266    }
267
268    pub fn as_bytes(&self) -> &[u8] {
269        unsafe {
270            core::slice::from_raw_parts(
271                self as *const _ as usize as *const u8,
272                core::mem::size_of::<Self>(),
273            )
274        }
275    }
276
277    pub fn set_crc32(&mut self) {
278        let bytes = unsafe {
279            core::slice::from_raw_parts(
280                self as *const _ as usize as *const u8,
281                core::mem::size_of::<Self>() - 4,
282            )
283        };
284        let crc32 = crc32_hash(bytes);
285        self.crc32 = crc32;
286    }
287
288    fn validate_basic(&self, id: EntryId) -> Result<(), FsError> {
289        crc32_verify(self.as_bytes())?;
290        if self.id != id {
291            return Err(FsError::ValidationFailed);
292        }
293        Ok(())
294    }
295
296    pub fn validate(&self, id: EntryId) -> Result<(), FsError> {
297        match id.kind() {
298            EntryKind::Directory => self.validate_dir(id),
299            EntryKind::File => self.validate_file(id),
300        }
301    }
302
303    pub fn validate_dir(&self, id: EntryId) -> Result<(), FsError> {
304        self.validate_basic(id)?;
305        if self.id.kind() != EntryKind::Directory {
306            return Err(FsError::ValidationFailed);
307        }
308        if self.size > MAX_DIR_ENTRIES {
309            return Err(FsError::ValidationFailed);
310        }
311
312        Ok(())
313    }
314
315    pub fn validate_file(&self, id: EntryId) -> Result<(), FsError> {
316        self.validate_basic(id)?;
317        if self.id.kind() != EntryKind::File {
318            return Err(FsError::ValidationFailed);
319        }
320
321        Ok(())
322    }
323}
324
325#[derive(Clone, Copy, Debug)]
326#[repr(C)]
327pub struct Attr {
328    pub id: EntryId,
329    pub size: u64, // File size in bytes, or the number of directory entries.
330    pub created: Timestamp,
331    pub modified: Timestamp,
332    pub accessed: Timestamp, // Usually not tracked.
333    pub user_data: [u64; 4], // Whatever, e.g. permissions, uid/gid, etc.
334}
335
336impl From<&EntryMetadata> for Attr {
337    fn from(meta: &EntryMetadata) -> Self {
338        Self {
339            id: meta.id,
340            size: meta.size,
341            created: meta.created,
342            modified: meta.modified,
343            accessed: meta.accessed,
344            user_data: meta.user_data,
345        }
346    }
347}
348
349pub(crate) const MAGIC: u64 = 0x0c51_a0bb_b108_3d14; // Just a random number.
350
351// The type of transaction type in process.
352pub(crate) const TXN_TYPE_NONE: u32 = 0;
353pub(crate) const TXN_TYPE_ADD_NODE: u32 = 1;
354pub(crate) const TXN_TYPE_ADD_BYTES: u32 = 2;
355pub(crate) const TXN_TYPE_REMOVE_NODE: u32 = 3;
356pub(crate) const TXN_TYPE_REMOVE_BYTES: u32 = 4;
357pub(crate) const TXN_TYPE_MOVE: u32 = 5;
358
359// The partition:
360// - the first block
361// - root_dir block
362// - either Entry blocks, data blocks, or empty blocks
363
364// The first block header. Duplicated at [0..) and [2048..) of the first block
365// of the partition.
366#[derive(Clone, Copy, PartialEq, Eq)]
367#[repr(C)]
368pub(crate) struct SuperblockHeader {
369    pub magic: u64,                   // MAGIC.
370    pub version: u64,                 // 1 at the moment.
371    pub num_blocks: u64,              // Num blocks (may be less than what the device has).
372    pub free_blocks: u64,             // The number of unused blocks.
373    pub generation: u64,              // Auto-incrementing. Used in EntityId.
374    pub freelist_head: u64,           // The head of the block freelist.
375    pub empty_area_start: u64,        // All blocks after this one are unused.
376    pub txn_meta_block: u64,          // A meta block currently being worked on.
377    pub txn_data_block: u64,          // A file data block currently being worked on.
378    pub txn_link_block: u64,          // A directory list block or file data block list.
379    pub txn_list_of_links_block: u64, // A file list-of-lists block currently being worked on.
380    pub txn_blocks_owner: u64,        // The "owner" of the txn blocks to check upon bootup.
381    pub txn_type: u32,                // TXN_TYPE_***.
382    pub crc32: u32,                   // CRC32 of this data structure.
383}
384
385impl SuperblockHeader {
386    pub fn as_bytes(&self) -> &[u8] {
387        unsafe {
388            core::slice::from_raw_parts(
389                self as *const _ as usize as *const u8,
390                core::mem::size_of::<Self>(),
391            )
392        }
393    }
394
395    pub fn set_crc32(&mut self) {
396        let bytes = unsafe {
397            core::slice::from_raw_parts(
398                self as *const _ as usize as *const u8,
399                core::mem::size_of::<Self>() - 4,
400            )
401        };
402        let crc32 = crc32_hash(bytes);
403        self.crc32 = crc32;
404    }
405
406    pub fn validate(&self) -> Result<(), FsError> {
407        crc32_verify(self.as_bytes())?;
408        if self.version != 1 {
409            return Err(FsError::UnsupportedVersion);
410        }
411        if self.magic == MAGIC {
412            Ok(())
413        } else {
414            return Err(FsError::ValidationFailed);
415        }
416    }
417}
418
419#[derive(Clone, Copy)]
420#[repr(C, align(4096))]
421pub(crate) struct Block {
422    bytes: [u8; 4096],
423}
424
425const _: () = assert!(core::mem::size_of::<Block>() as u64 == BLOCK_SIZE);
426
427impl Block {
428    pub const fn new_uninit() -> Self {
429        #[allow(invalid_value)]
430        unsafe {
431            MaybeUninit::<Self>::uninit().assume_init()
432        }
433    }
434
435    pub const fn new_zeroed() -> Self {
436        Self { bytes: [0; 4096] }
437    }
438
439    pub unsafe fn get<T>(&self) -> &T {
440        debug_assert!(core::mem::size_of::<T>() as u64 <= BLOCK_SIZE);
441
442        (self.bytes.as_ptr() as usize as *const T)
443            .as_ref()
444            .unwrap_unchecked()
445    }
446
447    pub unsafe fn get_mut<T>(&mut self) -> &mut T {
448        debug_assert!(core::mem::size_of::<T>() as u64 <= BLOCK_SIZE);
449
450        (self.bytes.as_ptr() as usize as *mut T)
451            .as_mut()
452            .unwrap_unchecked()
453    }
454
455    pub fn as_bytes(&self) -> &[u8] {
456        unsafe {
457            core::slice::from_raw_parts(
458                (self as *const _ as usize as *const u8)
459                    .as_ref()
460                    .unwrap_unchecked(),
461                BLOCK_SIZE as usize,
462            )
463        }
464    }
465
466    pub fn as_data_bytes_in_meta(&self) -> &[u8] {
467        unsafe {
468            core::slice::from_raw_parts(
469                (((self as *const _ as usize) + core::mem::size_of::<EntryMetadata>())
470                    as *const u8)
471                    .as_ref()
472                    .unwrap_unchecked(),
473                BLOCK_SIZE as usize - core::mem::size_of::<EntryMetadata>(),
474            )
475        }
476    }
477
478    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
479        unsafe {
480            core::slice::from_raw_parts_mut(
481                (self as *mut _ as usize as *mut u8)
482                    .as_mut()
483                    .unwrap_unchecked(),
484                BLOCK_SIZE as usize,
485            )
486        }
487    }
488
489    pub fn as_data_bytes_in_meta_mut(&mut self) -> &mut [u8] {
490        unsafe {
491            core::slice::from_raw_parts_mut(
492                (((self as *mut _ as usize) + core::mem::size_of::<EntryMetadata>()) as *mut u8)
493                    .as_mut()
494                    .unwrap_unchecked(),
495                BLOCK_SIZE as usize - core::mem::size_of::<EntryMetadata>(),
496            )
497        }
498    }
499
500    pub fn set_dir_entry(&mut self, pos: usize, id: EntryId, name: &str) {
501        let entry = self.get_dir_entry_mut(pos);
502        entry.id = id;
503        entry.set_name(name);
504    }
505
506    pub fn get_dir_entry(&self, pos: usize) -> &DirEntryInternal {
507        assert!(pos as u64 <= MAX_ENTRIES_IN_DATA_BLOCK);
508        let offset = pos * core::mem::size_of::<DirEntryInternal>();
509        unsafe {
510            ((self.bytes.as_ptr() as usize + offset) as *const DirEntryInternal)
511                .as_ref()
512                .unwrap_unchecked()
513        }
514    }
515
516    pub fn get_dir_entry_mut(&mut self, pos: usize) -> &mut DirEntryInternal {
517        assert!(pos <= MAX_ENTRIES_IN_DATA_BLOCK as usize);
518        let offset = pos * core::mem::size_of::<DirEntryInternal>();
519        unsafe {
520            ((self.bytes.as_ptr() as usize + offset) as *mut DirEntryInternal)
521                .as_mut()
522                .unwrap_unchecked()
523        }
524    }
525
526    pub fn get_datablock_no_in_meta(&self, data_block_idx: u64) -> u64 {
527        assert!(data_block_idx <= MAX_LINKS_IN_META_BLOCK);
528        let offset = core::mem::size_of::<EntryMetadata>() + ((data_block_idx as usize) << 3);
529        debug_assert!(offset < BLOCK_SIZE as usize);
530        unsafe { *((self.bytes.as_ptr() as usize + offset) as *const u64) }
531    }
532
533    pub fn set_datablock_no_in_meta(&mut self, data_block_idx: u64, block_no: u64) {
534        assert!(data_block_idx <= MAX_LINKS_IN_META_BLOCK);
535        let offset = core::mem::size_of::<EntryMetadata>() + ((data_block_idx as usize) << 3);
536        assert!(offset < BLOCK_SIZE as usize);
537        unsafe {
538            *((self.bytes.as_ptr() as usize + offset) as *mut u64) = block_no;
539        }
540    }
541
542    pub fn get_datablock_no_in_link(&self, data_block_idx: u64) -> u64 {
543        assert!(data_block_idx < 512);
544        let offset = (data_block_idx as usize) << 3;
545        unsafe { *((self.bytes.as_ptr() as usize + offset) as *const u64) }
546    }
547
548    pub fn set_datablock_no_in_link(&mut self, data_block_idx: u64, block_no: u64) {
549        assert!(data_block_idx < 512);
550        let offset = (data_block_idx as usize) << 3;
551        unsafe {
552            *((self.bytes.as_ptr() as usize + offset) as *mut u64) = block_no;
553        }
554    }
555}
556
557pub(crate) struct Superblock {
558    block: Box<Block>,
559}
560
561impl Superblock {
562    pub fn from(block: Box<Block>) -> Result<Self, FsError> {
563        unsafe {
564            let fbh = block.get::<SuperblockHeader>();
565            fbh.validate()?;
566        }
567
568        Ok(Self { block })
569    }
570
571    pub fn header(&self) -> &SuperblockHeader {
572        unsafe { self.block.get::<SuperblockHeader>() }
573    }
574
575    pub fn header_mut(&mut self) -> &mut SuperblockHeader {
576        unsafe { self.block.get_mut::<SuperblockHeader>() }
577    }
578
579    pub fn ___as_bytes(&self) -> &[u8] {
580        self.block.as_bytes()
581    }
582
583    pub fn block(&self) -> &Block {
584        &self.block
585    }
586}
587
588pub(crate) fn validate_filename(name: &str) -> Result<(), FsError> {
589    if name.as_bytes().len() > 255 || name.contains('/') || name == "." || name == ".." {
590        return Err(FsError::InvalidArgument);
591    }
592
593    Ok(())
594}
595
596pub(crate) fn align_up(what: u64, how: u64) -> u64 {
597    debug_assert!(how.is_power_of_two());
598    (what + how - 1) & !(how - 1)
599}