LittleFS Storage Wrappers
CFGPack provides optional LittleFS-based convenience wrappers for embedded systems with flash storage. These functions handle the file open/read/write/close lifecycle while preserving cfgpack’s zero-heap-allocation guarantee.
Unlike the POSIX io_file.h wrappers (which use FILE* operations), the LittleFS wrappers use lfs_file_opencfg() with a caller-provided file cache buffer, making them compatible with LFS_NO_MALLOC builds.
To use these functions, compile with -DCFGPACK_LITTLEFS and link src/io_littlefs.c and the vendored LittleFS sources with your project.
API
#include "cfgpack/io_littlefs.h"
/* Encode context to a LittleFS file using caller scratch buffer (no heap).
* scratch must be >= cfg->cache_size + encoded size. */
cfgpack_err_t cfgpack_pageout_lfs(const cfgpack_ctx_t *ctx,
lfs_t *lfs,
const char *path,
uint8_t *scratch,
size_t scratch_cap);
/* Decode context from a LittleFS file using caller scratch buffer (no heap).
* scratch must be >= cfg->cache_size + file size. */
cfgpack_err_t cfgpack_pagein_lfs(cfgpack_ctx_t *ctx,
lfs_t *lfs,
const char *path,
uint8_t *scratch,
size_t scratch_cap);
Parameters:
ctx— Initialized cfgpack context.lfs— Mounted LittleFS instance (caller-owned). The caller is responsible for mounting and unmounting.path— File path within the LittleFS filesystem.scratch— Scratch buffer that serves double duty (see layout below).scratch_cap— Total capacity of the scratch buffer in bytes.
Scratch Buffer Layout
The scratch buffer is split internally into two regions:
scratch buffer:
┌─────────────────────────────┬──────────────────────────────────────┐
│ LittleFS file cache │ serialized config data │
│ [0 .. cache_size-1] │ [cache_size .. scratch_cap-1] │
└─────────────────────────────┴──────────────────────────────────────┘
First
cache_sizebytes: Passed tolfs_file_opencfg()asstruct lfs_file_config.buffer. This is the LittleFS file cache, sized to matchlfs->cfg->cache_size.Remaining bytes: Used by
cfgpack_pageout()/cfgpack_pagein_buf()for the serialized MessagePack data.
Minimum scratch size: cache_size + max(encoded_config_size, stored_file_size).
If scratch_cap <= cache_size, the functions return CFGPACK_ERR_BOUNDS immediately. The cache_size value comes from your LittleFS configuration (lfs->cfg->cache_size), which is typically set to the flash block or page size.
This design avoids calling lfs_malloc(), so the wrappers work in LFS_NO_MALLOC builds without any changes.
Usage Example
#include "cfgpack/cfgpack.h"
#include "cfgpack/io_littlefs.h"
/* Assume lfs_cfg.cache_size == 256 */
#define SCRATCH_SIZE (256 + 1024) /* cache + room for encoded config */
static uint8_t scratch[SCRATCH_SIZE];
lfs_t lfs;
/* ... mount LittleFS (caller-owned) ... */
lfs_mount(&lfs, &lfs_cfg);
/* Write config to flash */
cfgpack_err_t err = cfgpack_pageout_lfs(&ctx, &lfs, "/config.bin",
scratch, sizeof(scratch));
if (err != CFGPACK_OK) { /* handle error */ }
/* Read config back from flash */
err = cfgpack_pagein_lfs(&ctx, &lfs, "/config.bin",
scratch, sizeof(scratch));
if (err != CFGPACK_OK) { /* handle error */ }
lfs_unmount(&lfs);
Composable I/O Pattern
cfgpack_pagein_lfs() wraps cfgpack_pagein_buf() only — it does not wrap cfgpack_pagein_remap(). This means it loads data directly into the current schema without any index remapping or type widening.
For cross-version migration, you need to manually read the raw blob from LittleFS, detect the schema version, and call cfgpack_pagein_remap() yourself:
/* 1. Split scratch the same way the wrappers do internally */
lfs_size_t cache_size = lfs.cfg->cache_size;
uint8_t *file_cache = scratch;
uint8_t *data_buf = scratch + cache_size;
size_t data_cap = sizeof(scratch) - cache_size;
/* 2. Read raw blob from LittleFS */
struct lfs_file_config fcfg = {.buffer = file_cache};
lfs_file_t file;
lfs_file_opencfg(&lfs, &file, "/config.bin", LFS_O_RDONLY, &fcfg);
lfs_ssize_t size = lfs_file_size(&lfs, &file);
lfs_file_read(&lfs, &file, data_buf, size);
lfs_file_close(&lfs, &file);
/* 3. Detect schema version */
char name[64];
cfgpack_peek_name(data_buf, (size_t)size, name, sizeof(name));
/* 4. Apply remap if needed */
if (strcmp(name, "sensor_v1") == 0) {
cfgpack_pagein_remap(&ctx_v2, data_buf, (size_t)size,
v1_to_v2_remap, remap_count);
} else {
cfgpack_pagein_buf(&ctx_v2, data_buf, (size_t)size);
}
See examples/flash_config/ for a complete working example of this pattern.
Building
src/io_littlefs.c and the vendored LittleFS sources are compiled into libcfgpack.a (the default CFLAGS set -DCFGPACK_LITTLEFS). LittleFS is built with LFS_NO_DEBUG, LFS_NO_WARN and LFS_NO_ERROR so its logging does not pull <stdio.h> into the archive. LFS_NO_MALLOC is deliberately left to the integrator: cfgpack’s wrappers supply every buffer LittleFS needs through the scratch argument, so the choice only affects the application’s own LittleFS calls. If you build the sources yourself rather than linking the archive:
$(CC) -DCFGPACK_LITTLEFS -DCFGPACK_HOSTED \
-Iinclude -Ithird_party/littlefs \
myapp.c src/io_littlefs.c \
third_party/littlefs/lfs.c third_party/littlefs/lfs_util.c \
-Lbuild/out -lcfgpack -o myapp
For embedded builds, omit -DCFGPACK_HOSTED and adjust the toolchain as needed.
Error Codes
Return |
Condition |
|---|---|
|
Success |
|
|
|
Data portion too small for |
|
LittleFS file open, read, write, or size failure |
|
CRC-32C integrity check failed (pagein only) |
|
Invalid MessagePack payload (pagein only) |
Working Example
The examples/flash_config/ example demonstrates a complete LittleFS + LZ4 + schema migration workflow:
RAM-backed LittleFS block device — runs on any desktop without real flash hardware
LZ4-compressed msgpack binary schemas — built by a pipeline:
.map→cfgpack-schema-pack→.msgpack→cfgpack-compress lz4→.msgpack.lz4Schema migration v1 → v2 using
cfgpack_pagein_remap()with all five migration scenarios: keep, widen, move, remove, and addComposable I/O pattern — manual LFS read followed by
cfgpack_pagein_remap()for cross-version migration
cd examples/flash_config && make run
Third-Party Library
LittleFS is vendored in third_party/littlefs/ for self-contained builds:
third_party/littlefs/
lfs.h, lfs.c # Core filesystem implementation
lfs_util.h, lfs_util.c # Utility functions
LICENSE.md # BSD-3-Clause license
LittleFS is a little fail-safe filesystem designed for microcontrollers. It provides power-loss resilience, wear leveling, and bounded RAM/ROM usage — making it a natural companion for cfgpack’s zero-allocation architecture.