MerrimanInd/littlefs-tooling-rs
GitHub: MerrimanInd/littlefs-tooling-rs
一个 Rust 编写的 LittleFS 文件系统工具链,支持将目录打包为镜像、解包、检查内容,并无缝集成到 Cargo 构建流程中自动构建和刷写到嵌入式设备。
Stars: 3 | Forks: 0
# LittleFS Rust 工具
本项目提供了一个工具箱,旨在以尽可能高的确定性和健壮性将 [LittleFS 文件系统](https://github.com/littlefs-project/littlefs) 部署到 Rust 嵌入式项目中。它可以将目录打包成 LittleFS 镜像,将镜像解包回其目录结构,以及检查镜像的内容。它还可以在刷写过程中,通过构建镜像并将其发送到微控制器(如果文件已更改)来将本地目录同步到微控制器。
这些工具基于 Trussed Dev 团队构建的 Rust LittleFS 绑定。其主要的 [`littlefs2`](https://github.com/trussed-dev/littlefs2) 库被固件项目本身用于访问 LittleFS 镜像。这些工具使用 [`littlefs2-sys`](https://github.com/trussed-dev/littlefs2-sys)(底层的 C 绑定)进行实际的打包和解包,以便可以在运行时而非编译时调整镜像的每一个参数。
`littlefs2-pack` 经过测试,与 C++ [`mklittlefs` 项目](https://github.com/earlephilhower/mklittlefs) 兼容。这一点通过 `cross-compat.rs` 测试来保证,该测试使用其中一个工具进行打包,然后用另一个工具进行解包,双向执行。这些测试是针对子模块中的 `mklittlefs` 版本运行的,并且要求在运行测试之前构建好该工具。
# 示例
目前,`/examples` 文件夹中有三个用于部署到 ESP32 和 Raspberry Pi Pico/RP2350 设备的示例。其中最完整的是 `esp32-littlefs-server`,它以出色的 [impl Rust ESP32 教程手册](https://esp32.implrust.com/wifi/web-server/index.html) 为起点。它利用了所有这些工具来打包整个网站目录,将其刷写到 ESP32,并在一个 WiFi 接入点上提供服务。对网站内容的任何更改都会在编译时被检测到,随后镜像会被重新构建并刷写到设备。
# 工具概述
## LittleFS 配置文件
LittleFS 镜像有相当多的配置选项,这些选项在打包镜像和随后在设备上访问该镜像时必须保持一致。需要一个单一的事实来源来维持这种一致性。考虑到无数其他的配置选项,将它们存储在一个配置文件中是合理的。这是一个 TOML 文件,通常存储在项目仓库的根目录下,命名为 `littlefs.toml`。
配置文件有三个主要部分:要创建的镜像、将被打包到其中的目录以及刷写过程。在 `littlefs2-pack/littlefs.toml` 中有一个包含所有选项及其解释注释的完整示例,而一个最小化的示例可能如下所示:
```
[image]
# 这是生成文件的名称。它默认为 "filesystem",但可用于区分多个镜像
name = "filesystem"
block_size = 4096
page_size = 256
read_size = 16
write_size = 512
block_count = 3096
# 请注意,也可以使用 image_size 来定义镜像的总大小
# 但这与 block_count 互斥,并且必须是 block_size 的倍数
# image_size = 15_998_976
cache_size = 256
lookahead_size = 8
[directory]
root = "./image_directory"
depth = -1 # Unlimited recursion
ignore_hidden = true # Ignore hidden dotfiles
gitignore = true # Respect the gitignore files found in the directory
repo_gitignore = true # Respect the repo level gitignore
glob_ignores = ["*.bkup", "build"] # Global ignore patterns
glob_includes = [] # Global includes that supercede all ignore patterns
[flash.firmware]
command = "espflash flash --chip esp32s3 {path}"
[flash.filesystem]
command = "espflash write-bin {address} {path}"
address = "0x200000"
```
其中一些是可选的并且有默认值。大多数应该可以通过注释不言自明。
## CLI 工具
与 LittleFS 镜像交互的最简单方法是通过 CLI 工具。你可以使用 Cargo 安装它:
```
cargo install littlefs2-tool
```
这会安装一个名为 `littlefs` 的二进制文件,它带有打包、解包和检查 LittleFS 镜像的选项。这是该项目中唯一可用于非 Rust 项目的部分!
```
littlefs
Create, unpack, and inspect LittleFSv2 filesystem images
Usage: littlefs [OPTIONS]
Commands:
pack Pack a directory into a LittleFS2 image
unpack Unpack a LittleFS2 image into a directory
list List files in a LittleFS2 image
info Print info about a LittleFS2 image (block count, used space, etc.)
flash Run the flash commands from a TOML config file
help Print this message or the help of the given subcommand(s)
Options:
-c, --config Path to a littlefs.toml configuration file
-h, --help Print help
-V, --version Print version
```
所有命令都可以将配置文件的路径作为输入,或者使用由各个标志(`--block-count`、`--block-size` 等)定义的配置文件。flash 命令适用于不同的用例,将在“Flash Runner”部分进行讨论。
## `build.rs` 集成
使用 `littlefs2-pack` 的第一个也是最好的地方是在 `build.rs` 文件中。此文件在 Rust crate 的其余部分编译之前被编译和运行,这使其成为构建镜像的理想时机。这是一个最小化的 `build.rs` 示例:
```
use std::path::Path;
use littlefs2_pack;
fn main() {
littlefs2_pack::pack_and_generate_config(&Path::new("./littlefs.toml"));
}
```
`pack_and_generate_config()` 函数接收一个 LittleFS 配置文件的路径,然后在构建时执行以下步骤:
- 遍历目录,遵循忽略和包含规则,并列出要包含的文件和目录
- 使用配置文件中的设置在 `OUT_DIR` 中创建并格式化一个 LittleFS 镜像
- 将发现的文件和目录添加到镜像中
- 将镜像向上复制到 `target/` 目录中,以便在运行时更容易访问
- 生成一个带有一些常量和模块的 Rust 文件,供项目使用
每次调用 `cargo build` 时都会运行此过程,从而保持镜像最新。
### ESP-IDF 分区文件生成
乐鑫 (Espressif) ESP 项目可以使用 `partitions.csv` 文件来定义项目的分区集合,通常包括一个或多个用于 LittleFS 镜像的分区。
```
# ESP32-S3 FeatherS3 分区表 — 16 MB flash
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 0x1F0000
littlefs, data, fat, 0x200000, 0xE00000
```
由于该分区所在的地址会在刷写过程中以及有时在固件本身中被使用,因此如果也能将其视为单一事实来源将会非常有用。为了实现这一点,`littlefs2-pack` 还包含一个用于从分区文件生成 Rust 文件的函数:
```
littlefs2_pack::generate_esp_partitions_config(&Path::new("./partitions.csv"), "littlefs");
```
它将生成类似以下内容的代码:
```
// Auto-generated by littlefs2-pack — do not edit.
pub const PARTITION_NAME: &str = "littlefs";
pub const PARTITION_OFFSET: u32 = 0x200000;
pub const PARTITION_SIZE: u32 = 0xE00000;
```
## Rust Config 模块
`build.rs` 生成的 Rust 文件可被固件项目用于与 LittleFS 镜像相关的任何内容。从一个典型的 LittleFS 配置文件中,生成的 Rust 函数可能如下所示:
```
// Auto-generated by littlefs2-pack — do not edit.
use generic_array::typenum;
pub const BLOCK_SIZE: usize = 1024;
pub const BLOCK_COUNT: usize = 3096;
pub const READ_SIZE: usize = 16;
pub const WRITE_SIZE: usize = 512;
pub const CACHE_SIZE: usize = 512;
pub const LOOKAHEAD_SIZE: usize = 392;
pub const TOTAL_SIZE: usize = BLOCK_SIZE * BLOCK_COUNT;
/// Typenum alias for `littlefs2::driver::Storage::CACHE_SIZE`.
pub type CacheSize = typenum::U512;
/// Typenum alias for `littlefs2::driver::Storage::LOOKAHEAD_SIZE`.
/// Note: the littlefs2 crate measures lookahead in units of 8 bytes,
/// so this is `lookahead_size / 8`.
pub type LookaheadSize = typenum::U49;
/// The packed LittleFS image, embedded at compile time.
pub static IMAGE: &[u8] = include_bytes!("filesystem.bin");
pub mod paths {
pub mod directory_a {
pub const DIR: &str = "/directory_a";
pub const FILE_MD: &str = "/directory_a/file.md";
}
pub mod directory_b {
pub const DIR: &str = "/directory_b";
pub const ANOTHER_FILE_TXT: &str = "/directory_b/another_file.txt";
}
}
```
然后它会被项目文件导入:
```
#[allow(unused)]
mod lfs_config {
include!(concat!(env!("OUT_DIR"), "/littlefs_config.rs"));
}
```
### LittleFS 镜像参数
所有与实际镜像相关的参数都作为常量生成,包括以适当的 `typenum` 变体表示的 `CacheSize` 和 `LookaheadSize`。这些可以直接被 `littlefs2` Rust crate 使用以生成存储结构体。例如:
```
struct RamStorage<'a> {
buf: &'a mut [u8],
}
impl Storage for RamStorage<'_> {
type CACHE_SIZE = lfs_config::CacheSize;
type LOOKAHEAD_SIZE = lfs_config::LookaheadSize;
const READ_SIZE: usize = lfs_config::READ_SIZE;
const WRITE_SIZE: usize = lfs_config::WRITE_SIZE;
const BLOCK_SIZE: usize = lfs_config::BLOCK_SIZE;
const BLOCK_COUNT: usize = lfs_config::BLOCK_COUNT;
fn read(&mut self, off: usize, buf: &mut [u8]) -> LfsResult {
buf.copy_from_slice(&self.buf[off..off + buf.len()]);
Ok(buf.len())
}
fn write(&mut self, off: usize, data: &[u8]) -> LfsResult {
self.buf[off..off + data.len()].copy_from_slice(data);
Ok(data.len())
}
fn erase(&mut self, off: usize, len: usize) -> LfsResult {
for byte in &mut self.buf[off..off + len] {
*byte = 0xFF;
}
Ok(len)
}
}
```
该结构体能够使用由 `lfs_config` 模块定义的每一个参数正确地访问 LittleFS 镜像。
### 镜像导入
`pub static IMAGE` 这一行可以作为一种将镜像字节导入到 PSRAM 的便捷方法:
```
// Allocate in PSRAM and copy the image in
let mut storage_buf = vec![0u8; lfs_config::TOTAL_SIZE];
storage_buf[..lfs_config::IMAGE.len()].copy_from_slice(lfs_config::IMAGE);
```
### 路径
Rust 配置文件的最后一部分是打包镜像的完整目录树。这可能会占用很多行,上面的例子只包含了少数几个文件和目录。这个模块的好处在于,镜像中的特定路径可以通过编译时检查来引用。因此,如果文件被移动、重命名或由于忽略规则而未被打包,在构建时将会出现编译器错误。
例如,一个顶层的 `index.html` 文件可以用 `lfs_config::paths::INDEX_HTML` 引用。而一个更深的路径可以是 `lfs_config::paths::img::LOGO_PNG`。文件名和后缀之间的点分隔符被替换为下划线并大写,这也是 Rust 常量的惯例。目录本身可以用 `DIR` 常量引用:`lfs_config::paths::css::DIR`。
## Flash Runner
该过程的最后一步是将固件和文件系统二进制文件都部署到嵌入式设备。在大多数工作流和大多数刷写工具中,写入这两个二进制文件是不同的命令。但在 Rust 项目中,我们非常希望能够通过 `cargo run` 来运行整个项目。
为了将此功能引入 LittleFS 项目,CLI 工具包含了一个 flash 命令 `littlefs flash`。它可以作为 Cargo 项目中的 runner 命令使用,特别是在 `.cargo/config.toml` 中:
```
[target.]
runner = "littlefs flash --config ./littlefs.toml"
```
当调用 `cargo run` 时,Cargo 会将构建镜像的路径附加到 runner 参数的末尾。此工具接收该二进制文件,获取 LittleFS 配置,然后运行这两个命令。首先写入文件系统二进制文件,然后写入固件二进制文件。该工具从配置文件中获取这两个 runner 命令:`[flash]` 部分包含要为固件和文件系统写入运行的命令,以及用于写入文件系统二进制文件的地址。这使得 LittleFS 工具与所使用的特定文件刷写工具无关。
在 ESP 项目的情况下,LittleFS 配置文件除了使用实际路径地址外,还可以引用 `partitions.csv` 文件:
```
[flash.filesystem]
command = "espflash write-bin {address} {path}"
# 用于从中读取地址的 ESP partition table 和名称
partition_table = "./partitions.csv"
partition_name = "littlefs"
```
首次调用 `cargo run` 命令时,镜像将被构建并部署。一个 SHA256 哈希值会被生成并存储在 `target/.flash-cache` 目录中。在连续的调用中,镜像将被构建,计算出一个 SHA256 哈希值,然后将其与缓存的值进行比较。如果它们匹配,则说明镜像目录中的文件都没有更改,因此镜像不会被重新刷写。某些刷写工具通过读取该地址处的数据并进行比较来内置此功能,但这会花费更长的时间,而且并非所有工具都支持。
标签:C++, ESP32, LittleFS, littlefs2, littlefs2-sys, mklittlefs, Python安全, Raspberry Pi Pico, RP2350, Rust, Web服务器, 可视化界面, 固件开发, 固件烧录, 嵌入式开发工具, 嵌入式系统, 底层绑定, 微控制器, 数据同步, 数据擦除, 文件打包, 文件系统, 文件解包, 物联网设备, 网络流量审计, 跨平台兼容, 通知系统, 镜像构建