Skip to main content

littlefs2_pack/
lib.rs

1//! # littlefs2-pack
2//!
3//! A Rust crate for building a file system into a LittleFS
4//! binary file to be flashed to an embedded device.
5//!
6//! This crate wraps the [LittleFS C library](https://github.com/littlefs-project/littlefs)
7//! using the [`littlefs2-sys`](https://crates.io/crates/littlefs2-sys) crate. The [`littlefs2`](https://crates.io/crates/littlefs2) crate
8//! might have been an easier starting point but it doesn't
9//! currently allow setting the image configuration dynamically
10//!  at runtime, such as the block size and count.
11//!
12//! `littlefs2-pack` is tested for compatibility with the C++
13//! [`mklittlefs` project](https://github.com/earlephilhower/mklittlefs). This is ensured with the `cross-compat.rs`
14//! test that packs with one tool then unpack with the other,
15//! in both directions. These tests are ran against the version of
16//! `mklittlefs` in the submodule and requires that tool to be built
17//! prior to running the tests.
18//!
19//! ## Example
20//!
21//! ```rust,no_run
22//! use littlefs2_pack::littlefs::LfsImage;
23//! use littlefs2_pack::config::ImageConfig;
24//!
25//! let config = ImageConfig {
26//!     name: String::from("filesystem"),
27//!     block_size: 4096,
28//!     block_count: 128,
29//!     read_size: 256,
30//!     write_size: 256,
31//!     block_cycles: -1,
32//!     cache_size: 256,
33//!     lookahead_size: 8,
34//! };
35//!
36//! let mut image = LfsImage::new(config).unwrap();
37//! image.format().unwrap();
38//!
39//! image.mount_and_then(|fs| {
40//!     fs.create_dir("/data")?;
41//!     fs.write_file("/data/hello.txt", b"Hello from LittleFS!")?;
42//!     Ok(())
43//! }).unwrap();
44//!
45//! let binary = image.into_data();
46//! std::fs::write("filesystem.bin", &binary).unwrap();
47//! ```
48
49use std::path::Path;
50
51use crate::{config::Config, littlefs::LfsImage, partition_table::get_partition};
52
53pub mod config;
54pub mod littlefs;
55pub mod partition_table;
56pub mod walk;
57
58/// Generate a LittleFS image and Rust configuration module from a
59/// `littlefs.toml` file.
60///
61/// Reads the TOML configuration at `littlefs_config`, packs the
62/// directory tree it references into a LittleFS binary image, and
63/// writes two files into `$OUT_DIR`:
64///
65/// - **`filesystem.bin`** — the raw LittleFS image ready to be
66///   flashed to the device.
67/// - **`littlefs_config.rs`** — Rust constants for the image geometry
68///   (`BLOCK_SIZE`, `BLOCK_COUNT`, `TOTAL_SIZE`, etc.), typenum
69///   aliases, an `IMAGE` static that embeds the binary via
70///   `include_bytes!`, and an optional `paths` module mirroring the
71///   packed directory layout.
72///
73/// Also copies the built filesystem image up to the target/<profile> directory.
74/// The `$OUT_DIR` is difficult to access during flash or runtime since it's
75/// a hash encoded build directory. This step makes the image much easier to
76/// find at flash time.
77///
78/// # Usage in `build.rs`
79///
80/// ```rust,no_run
81/// littlefs2_pack::pack_and_generate_config(
82///     std::path::Path::new("littlefs.toml"),
83/// );
84/// ```
85///
86/// Then in your firmware crate:
87///
88/// ```rust,ignore
89/// mod littlefs {
90///     include!(concat!(env!("OUT_DIR"), "/littlefs_config.rs"));
91/// }
92/// ```
93///
94/// # Panics
95///
96/// Panics if the `OUT_DIR` environment variable is not set (i.e. this
97/// function is called outside of a Cargo build script), or if any step
98/// of config parsing, image creation, packing, or file I/O fails. This
99/// panic behavior is because a build should not proceed if this step
100/// doesn't succeed.
101pub fn pack_and_generate_config(littlefs_config: &Path) {
102    // Load the config from the file
103    let config = Config::from_file(littlefs_config).unwrap();
104
105    let image_name = config.image.name.clone();
106    let out_dir = std::env::var("OUT_DIR").unwrap();
107
108    // OUT_DIR is like target/<triple>/<profile>/build/<crate>-<hash>/out
109    // The issue with this directory is that it's only easily accessible
110    // at compile time, it's hard to discern at run time. This script
111    // copies the image up to the target profile directory after build
112    // so the image can be found at flash time
113    let profile_dir = Path::new(&out_dir)
114        .ancestors()
115        .nth(3) // out/ -> <crate>-<hash>/ -> build/ -> <profile>/
116        .unwrap();
117    let img_file_path = format!("{}/{}.bin", out_dir, image_name);
118    let rust_file_path = format!("{}/{}.rs", out_dir, image_name);
119
120    // Create, format, and pack the image
121    let mut image = LfsImage::new(config.image).unwrap();
122    image.format().unwrap();
123    image.pack_from_config(config.directory).unwrap();
124
125    // Generate the Rust config module
126    std::fs::write(&rust_file_path, &image.emit_rust().unwrap()).unwrap();
127
128    // Generate the image binary
129    let binary = image.into_data();
130    std::fs::write(&img_file_path, &binary).unwrap();
131
132    // Copy the binary up to the profile directory
133    std::fs::copy(
134        &img_file_path,
135        profile_dir.join(format!("{}.bin", image_name)),
136    )
137    .unwrap();
138}
139
140/// Generate a Rust module with partition offset and size constants
141/// from an ESP-IDF partition table CSV.
142///
143/// Reads the CSV at `partition_csv`, looks up the partition named
144/// `partition_name`, and writes a `partition_config.rs` file into
145/// `$OUT_DIR` containing:
146///
147/// ```text
148/// pub const PARTITION_NAME: &str = "littlefs";
149/// pub const PARTITION_OFFSET: u32 = 0x200000;
150/// pub const PARTITION_SIZE: u32 = 0xE00000;
151/// ```
152///
153/// # Usage in `build.rs`
154///
155/// ```rust,no_run
156/// littlefs2_pack::generate_esp_partitions_config(
157///     std::path::Path::new("partitions.csv"),
158///     "littlefs",
159/// );
160/// ```
161///
162/// Then in your firmware crate:
163///
164/// ```rust,ignore
165/// mod partition {
166///     include!(concat!(env!("OUT_DIR"), "/partition_config.rs"));
167/// }
168/// ```
169pub fn generate_esp_partitions_config(partition_csv: &Path, partition_name: &str) {
170    let out_dir = std::env::var("OUT_DIR").unwrap();
171
172    let partition = get_partition(partition_csv, partition_name).unwrap();
173    partition.emit_rust(Path::new(&out_dir)).unwrap();
174}