trussed-dev / littlefs2

Idiomatic Rust API for littlefs

Home Page:https://lib.rs/littlefs2

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Shouldn't the `Storage` trait require the implementer to be a singleton?

japaric opened this issue · comments

test/ui/sync-fail.rs shows how trying to close a file on a different filesystem fails at compile time. That compile time protection holds in that example because each filesystem instance has a different type but one can, in safe code, create multiple FS instances of the same type and then close a file on a different filesystem instance (that's still the same type). Variation of test/ui/sync-fail.rs that shows this:

use littlefs2::{
    consts, driver,
    fs::{File, Filesystem},
    io::{Result, Write},
    ram_storage,
};

ram_storage!(
    name=RamStorage,
    backend=Ram,
    trait=driver::Storage,
    erase_value=0xff,
    read_size=20*5,
    write_size=20*7,
    cache_size_ty=consts::U700,
    block_size=20*35,
    block_count=32,
    lookaheadwords_size_ty=consts::U1,
    filename_max_plus_one_ty=consts::U256,
    path_max_plus_one_ty=consts::U256,
    result=Result,
);

fn main() {
    let mut ram1 = Ram::default();
    let mut storage1 = RamStorage::new(&mut ram1);
    let mut alloc1 = Filesystem::allocate();
    Filesystem::format(&mut storage1).unwrap();
    let mut fs1 = Filesystem::mount(&mut alloc1, &mut storage1).unwrap();

    let mut ram2 = Ram::default();
    let mut storage2 = RamStorage::new(&mut ram2);
    let mut alloc2 = Filesystem::allocate();
    Filesystem::format(&mut storage2).unwrap();
    let mut fs2 = Filesystem::mount(&mut alloc2, &mut storage2).unwrap();

    let mut alloc = File::allocate();
    // open file in FS1
    let mut file = File::create("a.txt", &mut alloc, &mut fs1, &mut storage1).unwrap();
    file.write(&mut fs1, &mut storage1, b"Hello!").unwrap();
    // sync the file in FS2
    file.close(&mut fs2, &mut storage2).unwrap();
}

Here ram1 and ram2 point to different to different memory blocks. The above example doesn't appear to trigger UB but it's probably corrupting the second filesystem. A more complex variation of the above example may cause UB though.

A potential solution to this is to make trait Storage unsafe and require that's implemented on singleton types. This way all instances of Ram are handles to the same memory (but then you have be careful about Rust aliasing rules and will likely have to make Ram !Send and !Sync ...)