Expand description
§Extended fs
An OS and architecture independent implementation of some Unix filesystems in Rust.
This crate is provided as is and do not offer any guaranty. It is still in early development so bugs are excepted to occur. If you find one, please report it at https://codeberg.org/RatCornu/efs/issues. In all cases, please do NOT use this library for important data, and make sure to backup your data before using it.
§Details
This crate provides a general interface to deal with some UNIX filesytems, and adds supports for some of them.
Currently, the Second Extended Filesystem (ext2) and the Simple File System (sfs) is supported, but you can implement your own filesystem with this interface.
This crate does NOT provide a virtual filesystem: you can either make one or use another crate on top on this one.
Every structure, trait and function in this crate is documented and contains source if needed. If you find something unclear, do not hesitate to create an issue at https://codeberg.org/RatCornu/efs/issues.
This library sticks as much as possible with the POSIX specification, fully available online on https://pubs.opengroup.org/onlinepubs/9799919799/.
§File interfaces
-
As defined in POSIX, a file can either be a
Regular, aDirectory, aSymbolicLink, aFifo, aCharacterDevice, aBlockDeviceor aSocket. Traits are available for each one of them, with basicreadandwriteoperations. Thereadandwriteoperations are in separated traits (FileReadandFilefor example) to be able to define read-only filesystems. -
Fileis the base trait of all other file traits. It provides an interface to retrieve and modify general attributes of a POSIX file (basically everything returned by thestatcommand on a UNIX OS). -
A
Regular(file) is a basic file containing a sequence of bytes, which can be read into a string (or not, depending on its content). -
A
Directoryis a node in the tree-like hierarchy of a filesystem. You can retrieve, add and remove entries (which are other files). -
A
SymbolicLinkis a file pointing an other file. It can be interpreted as the symbolic link or the pointed file in theFilesystemtrait. -
Other file types are defined but cannot be much manipulated as their implementation depends on the virtual file system and on the OS.
§Filesystem interface
All the manipulations needed in a filesystem can be made through the file traits. The
Filesystem is here to provide two things : an entry point to the filesystem with the
root method, and high-level functions to make the file manipulations easier.
You can read the documentation in the fs module for more information on Filesystems
and on how to implement them.
§Paths
As the Rust’s native Path implementation is in std::path, this crates provides an other
Path interface. It is based on UnixStr, which are the equivalent of
OsStr with a guarantee that: it is never empty nor contains the <NUL> character (‘\0’).
§Devices
In this crate, a Device is a sized structure that can be read, written directly at any
point.
You can read the documentation in the dev module for more information on Devices and on how to
implement them.
§Usage
§High-level usage
You always need to provide two things to use this crate: a filesystem and a device.
For the filesystem, you can use the filesystems provided by this crate or make one by yourself (see the how to
implement a filesystem section). The usage of a filesystem does not depend on
whether you are in a no_std environment or not.
For the devices, all the objects implementing Read, Write and
Seek automatically derive the Device trait. Then, all the common
structures (such as File, …) can be directly used. See the part on how to implement a
device if needed.
use std::fs::File;
use efs::dev::Device;
let file = File::options().read(true).write(true).open("./tests/fs/ext2/example.ext2").unwrap();
// `file` is a `Device`
§Concurrency
This library do not offer any guaranty for the behaviour of file manipulations when an other program is making
write operations on the same device at the same time in a general context. If you really need to, each filesystem
implementation documentation contains a paragraph describing exactly what structures are cached: updating by hand
those structures allow you to handle concurrency correctly.
In concrete terms, in particular for OS developers, it’s your duty, and more precisely the duty of the kernel to handle the case where two programs tries to modify the same data at the same time.
§Example
Here is a complete example of what can be do with the interfaces provided.
You can find this test file on efs’s codeberg repo.
use core::str::FromStr;
use deku::no_std_io::{Read, Write}; // Same as `no_std_io2::io::{Read, Write}`
use efs::fs::FilesystemRead;
use efs::fs::ext2::Ext2Fs;
use efs::fs::file::*;
use efs::fs::permissions::Permissions;
use efs::fs::types::{Gid, Uid};
use efs::path::{Path, UnixStr};
let device_id = 0_u32;
// `device` now contains a `Device`
let device = std::fs::File::options()
.read(true)
.write(true)
.open("./tests/fs/ext2/example2.ext2")
.unwrap();
let fs = Ext2Fs::new(device, device_id).unwrap();
// `fs` now contains a `FileSystem` with the following structure:
// /
// ├── bar.txt -> foo.txt
// ├── baz.txt
// ├── folder
// │ ├── ex1.txt
// │ └── ex2.txt -> ../foo.txt
// ├── foo.txt
// └── lost+found
/// The root of the filesystem
let root = fs.root().unwrap();
// We retrieve here `foo.txt` which is a regular file
let Some(TypeWithFile::Regular(mut foo_txt)) =
root.entry(UnixStr::new("foo.txt").unwrap()).unwrap()
else {
panic!("foo.txt is a regular file in the root folder");
};
// We read the content of `foo.txt`.
assert_eq!(foo_txt.read_all().unwrap(), b"Hello world!\n");
// We retrieve here `folder` which is a directory
let Some(TypeWithFile::Directory(mut folder)) =
root.entry(UnixStr::new("folder").unwrap()).unwrap()
else {
panic!("folder is a directory in the root folder");
};
// In `folder`, we retrieve `ex1.txt` as `/folder/ex1` points to the same
// file as `../folder/ex1.txt` when `/folder` is the current directory.
//
// Here, it is done by the complete path using the `FileSystem` trait.
let Ok(TypeWithFile::Regular(mut ex1_txt)) =
fs.get_file(&Path::from_str("../folder/ex1.txt").unwrap(), folder.clone(), false)
else {
panic!("ex1.txt is a regular file at /folder/ex1.txt");
};
// We read the content of `foo.txt`.
ex1_txt.write_all(b"Hello earth!\n").unwrap();
// We can also retrieve/create/delete a subentry with the `Directory`
// trait.
let TypeWithFile::SymbolicLink(mut boo) = folder
.add_entry(
UnixStr::new("boo.txt").unwrap(),
Type::SymbolicLink,
Permissions::from_bits_retain(0o777),
Uid(0),
Gid(0),
)
.unwrap()
else {
panic!("Could not create a symbolic link");
};
// We set the pointed file of the newly created `/folder/boo.txt` to
// `../baz.txt`.
boo.set_pointed_file("../baz.txt").unwrap();
// We ensure now that if we read `/folder/boo.txt` while following the
// symbolic links we get the content of `/baz.txt`.
let TypeWithFile::Regular(mut baz_txt) =
fs.get_file(&Path::from_str("/folder/boo.txt").unwrap(), root, true).unwrap()
else {
panic!("Could not retrieve baz.txt from boo.txt");
};
assert_eq!(ex1_txt.read_all().unwrap(), baz_txt.read_all().unwrap());
// Here is the state of the filesystem at the end of this example:
// /
// ├── bar.txt -> foo.txt
// ├── baz.txt
// ├── folder
// │ ├── boo.txt -> ../baz.txt
// │ ├── ex1.txt
// │ └── ex2.txt -> ../foo.txt
// ├── foo.txt
// └── lost+found