Filesystems¶
EVFS supports multiple filesystem interface backends. In addition to system level file access via C stdio and POSIX calls, the library can access
FatFs and littlefs filesystems either directly from their storage media or mounted as images within another EVFS filesystem. There are also two filesystems that can mount an archive in tar format stored on an existing VFS or as statically linked data. The Linux Romfs image format can be used as a more compact read-only filesystem. Each filesystem has a registration function that wraps evfs_register()
, adding the necessary arguments for configuration and constructing dynamic structures.
Stdio¶
EVFS implements a special “stdio” filesystem which uses the C file access API. Essentially everything that works with a FILE*. Directory operations are not supported by the C standard library so POSIX API calls are used for those features. If your target system does not support POSIX you can undefine EVFS_USE_STDIO_POSIX
in ‘evfs_config.h’ and work with limited functionality. There is only one “stdio” filesystem per process so you just need to register this once for all common file I/O.
-
int
evfs_register_stdio
(bool default_vfs)¶ Register a stdio instance.
This VFS is always named “stdio”. There should only be one instance per application.
- Parameters
default_vfs – Make this the default VFS when true
- Returns
EVFS_OK on success
FatFs¶
To access a FatFs filesystem you need to call evfs_register_fatfs()
with the FatFs volume number. Because FatFs uses global state to track its
filesystems you need to handle configuring storage access through its ‘disk_*()’ callback functions. The callbacks don’t need to be functioning before registration.
-
int
evfs_register_fatfs
(const char *vfs_name, uint8_t pdrv, bool default_vfs)¶ Register a new FatFs instance.
- Parameters
vfs_name – Name of new VFS
pdrv – FatFs volume number
default_vfs – Make this the default VFS when true
- Returns
EVFS_OK on success
A FatFs filesystem stored in an image file can be mounted using the helper functions in ‘fatfs_image.c’. The following functions will let you mount an image. The provided FatFs callback methods will need to be customized if additional volumes need to be mounted at the same time.
-
int
fatfs_make_image
(const char *img_path, uint8_t pdrv, evfs_off_t img_size)¶ Make a FatFs image file if it doesn’t exist.
If a new image is created it will be formatted.
- Parameters
img_path – Path to the image file
pdrv – Any unused FatFs volume number
img_size – Size of the image to generate
- Returns
EVFS_OK on success
-
int
fatfs_mount_image
(const char *img_path, uint8_t pdrv)¶ Mount a FatFs image.
- Parameters
img_path – Path to the image file
pdrv – FatFs volume number where this image will be mounted
- Returns
EVFS_OK on success
-
void
fatfs_unmount_image
(uint8_t pdrv)¶ Unmount a FatFs image.
- Parameters
pdrv – FatFs volume number where this image is mounted
If you need a new image, one can be prepared using fatfs_make_image()
. This will not alter an existing image file. When the image is ready you mount it within FatFs using fatfs_mount_image()
. At this point the image is associated with a FatFs volume number. You can then register a FatFs instance using that volume number. The contents of the image can then be read and modified as usual using the EVFS API. When done with the image you should unmount it within FatFs using fatfs_unmount_image()
.
#include <stdio.h>
#include <string.h>
#include "evfs.h"
#include "evfs/stdio_fs.h"
#include "evfs/fatfs_fs.h"
#include "ff.h"
#include "evfs/fatfs_image.h"
int main() {
evfs_init();
evfs_register_stdio(/*default_vfs*/ true);
// <<Configure FatFs volume handling via callbacks>>
int pdrv = 0; // FatFs volume number
fatfs_make_image("fatfs.img", pdrv, 512*1024); // No effect if image exists
fatfs_mount_image("fatfs.img", pdrv); // Image stored on base filesystem
// Add FatFs as default VFS
evfs_register_fatfs("vol0", pdrv, /*default_vfs*/ true);
// Access the image like a normal fielsystem
EvfsFile *fh;
evfs_open("hello.txt", &fh, EVFS_WRITE | EVFS_OVERWRITE);
char buf[100];
sprintf(buf, "Hello, world\n");
evfs_file_write(fh, buf, strlen(buf));
evfs_file_close(fh);
fatfs_unmount_image(pdrv);
evfs_unregister_all();
return 0;
}
littlefs¶
Use evfs_register_littlefs()
to access a littlefs filesystem. Littlefs keeps track of filesystem state through an lfs_t
struct. It is easy to attach multiple littlefs volumes with their own EVFS filesystem name. Each one can be registered as an independent VFS.
-
int
evfs_register_littlefs
(const char *vfs_name, lfs_t *lfs, bool default_vfs)¶ Register a Littlefs instance
- Parameters
vfs_name – Name of new VFS
lfs – Mounted littlefs object
default_vfs – Make this the default VFS when true
- Returns
EVFS_OK on success
A littlefs filesystem stored in an image file can be mounted using the helper functions in ‘littlefs_image.c’. The following functions will let you mount an image. The provided littlefs callback methods can be used as is. Wear leveling has no useful purpose in an image file stored on another filesystem. The lfs_config
struct should have its block_cycles
member set to -1 to disable this feature.
-
int
littlefs_make_image
(const char *img_path, struct lfs_config *cfg)¶ Make a Littlefs image file if it doesn’t exist.
Image geometry is taken from the configuration data.
- Parameters
img_path – Path to the image file
cfg – Configuration struct for the Littlefs this is mounted on
- Returns
EVFS_OK on success
-
int
littlefs_mount_image
(const char *img_path, struct lfs_config *cfg, lfs_t *lfs)¶ Mount a Littlefs image
- Parameters
img_path – Path to the image file
cfg – Configuration struct for the Littlefs this is mounted on
lfs – Littlefs filesystem object to mount onto
- Returns
EVFS_OK on success
-
void
littlefs_unmount_image
(lfs_t *lfs)¶ Unmount a Littlefs image.
- Parameters
lfs – Mounted Littlefs filesystem object
Image handling functions are similar to those for FatFs. All callback functions are supplied in the lfs_config
struct.
#include <stdio.h>
#include <string.h>
#include "evfs.h"
#include "evfs/stdio_fs.h"
#include "evfs/littlefs_fs.h"
#include "lfs.h"
#include "evfs/littlefs_image.h"
int main() {
lfs_t lfs;
LittlefsImage s_lfs_img = {0};
#define KB *1024UL
#define LFS_VOL_SIZE (512 KB)
#define LFS_BLOCK_SIZE 4096
struct lfs_config s_lfs_cfg = {
// Must have a valid LittlefsImage object for context data
.context = &s_lfs_img,
// Block device operations
.read = littlefs_image_read,
.prog = littlefs_image_prog,
.erase = littlefs_image_erase,
.sync = littlefs_image_sync,
// Block device configuration
.read_size = 16,
.prog_size = 16,
.block_size = LFS_BLOCK_SIZE,
.block_count = LFS_VOL_SIZE / LFS_BLOCK_SIZE,
.cache_size = 1024,
.lookahead_size = 16,
.block_cycles = -1, // Disable wear leveling // 500,
};
evfs_init();
evfs_register_stdio(/*default_vfs*/ true);
littlefs_make_image("littlefs.img", &s_lfs_cfg); // No effect if image exists
littlefs_mount_image("littlefs.img", &s_lfs_cfg, &lfs); // Image stored on base filesystem
// Add littlefs as default VFS
evfs_register_littlefs("lfs", &lfs, /*default_vfs*/ true);
// Access the image like a normal fielsystem
EvfsFile *fh;
evfs_open("hello.txt", &fh, EVFS_WRITE | EVFS_OVERWRITE);
char buf[100];
sprintf(buf, "Hello, world\n");
evfs_file_write(fh, buf, strlen(buf));
evfs_file_close(fh);
littlefs_unmount_image(&lfs);
evfs_unregister_all();
return 0;
}
Tar FS¶
Use evfs_register_tar_fs()
to mount an uncompressed tar file as a virtual filesystem. Data in the tar file is read only. Only normal files are supported. You cannot open a directory object to get a file listing.
You will need to have an existing VFS registered to open the tar file needed by the registration function. When generating a tar file you should reduce the blocking factor to 1 to minimize wasted padding after each file. This will impose an overhead of 512 bytes for each file header plus an average 256 bytes of padding after each file (assuming random file sizes).
-
int
evfs_register_tar_fs
(const char *vfs_name, EvfsFile *tar_file, bool default_vfs)¶ Register a Tar FS instance
- Parameters
vfs_name – Name of new VFS
tar_file – EVFS file of tar data
default_vfs – Make this the default VFS when true
- Returns
EVFS_OK on success
Tar resource FS¶
This is an alternative to the Tar FS that lets you use in memory data resources encoded in tar format in place of a normal filesystem. Use evfs_register_tar_rsrc_fs()
to mount a static array in tar format as a virtual filesystem.
Data in the tar resource is read only. Only normal files are supported. You cannot open a directory object to get a file listing.
The tar resource data should be statically statically linked into your program. This can be generated by using “xxd -i” to convert a tar file into a C header. You can also create an assembly wrapper that uses the
.incbin
directive or you can configure a linker script to link a tar file directly into its own section.
> tar cfb foo.tar 1 src
> xxd -i foo.tar > foo_tar.h
#include "evfs.h"
#include "evfs/tar_rsrc_fs.h"
#include "foo_tar.h"
// Mount the tar resource from array in foo_tar.h
// xxd generates an array and length variable in the header
evfs_register_tar_rsrc_fs("tarfs", foo_tar, foo_tar_len, /*default*/ true);
EvfsFile fh;
evfs_open("bar.txt", &fh, EVFS_READ);
...
evfs_file_close(fh);
File data can be accessed using the usual EVFS API with evfs_file_read()
. You can also directly access the resource data for a file by passing the EVFS_CMD_GET_RSRC_ADDR command to evfs_file_ctrl()
and using evfs_file_size()
for the in-memory array bounds.
EvfsFile fh;
evfs_open("bar.txt", &fh, EVFS_READ);
// Get direct pointer to resource data for "bar.txt"
uint8_t *bar_txt_data;
evfs_file_ctrl(fh, EVFS_CMD_GET_RSRC_ADDR, &bar_txt_data);
size_t bar_txt_len = evfs_file_size(fh);
-
int
evfs_register_tar_rsrc_fs
(const char *vfs_name, uint8_t *resource, size_t resource_len, bool default_vfs)¶ Register a Tar resource FS instance
- Parameters
vfs_name – Name of new VFS
resource – Array of Tar resource data
resource_len – Length of the resource array
default_vfs – Make this the default VFS when true
- Returns
EVFS_OK on success
Romfs¶
The Romfs driver provides the ability to mount Linux Romfs images. This is a simple read-only filesystem that has less overhead than the tar file filesystem and has a fully navigable directory structure. The images can either be accessed as files via an existing VFS or as an in-memory array of data.
The evfs_register_romfs()
function is used to mount a Romfs image from a VFS. You need to pass it a pointer to an opened image file containing the filesystem data. Romfs images are created with the genromfs program which is available in most Linux distros.
The evfs_register_rsrc_romfs()
function is used to mount an in-memory array as a Romfs. You will need to link a Romfs image into the program similar to the methods described for the tar resource fs.
There is a configuration option EVFS_USE_ROMFS_FAST_INDEX
that lets you control the generation of a hash table index. When enabled, path lookups are O(1) through a hash table. When disabled, files are found by walking the directory tree.
Unlike the tar fs implementation, the Romfs driver supports directory operations. You can create an EvfsDir
object and list directory contents like any other filesystem.
> genromfs -d image_dir -f my_image.romfs -V MyImage -v
#include "evfs.h"
#include "evfs/romfs_fs.h"
evfs_register_stdio(/*default*/ true);
EvfsFile *image;
evfs_open("my_image.romfs", &image, EVFS_READ); // Open image on stdio
// Mount image and make it default for future VFS access
evfs_register_romfs("romfs", image, /*default*/ true);
EvfsFile fh;
evfs_open("foo.txt", &fh, EVFS_READ); // Open file on Romfs image
...
evfs_file_close(fh);
// Image file is closed when VFS is unregistered
Mounting a Romfs resource is similar:
> genromfs -d image_dir -f my_image.romfs -V MyImage -v
> xxd -i my_image.romfs > my_image.h
#include "evfs.h"
#include "evfs/romfs_fs.h"
#include "my_image.h"
// Mount resource and make it default for future VFS access
evfs_register_rsrc_romfs("romfs", my_image_romfs, my_image_romfs_len, /*default*/ true);
Converting a large filesystem image into a header generates an unnecessary amount of text for the compiler to process. To avoid this you can generate an assembly stub that uses the .incbin
directive for direct inclusion of the image data:
.section .rodata
# Include filesystem image
.global my_image
.type my_image, @object
.align 4
my_image:
.incbin "my_image.romfs"
my_image_end:
.byte 0
# Compute size of the image
.global my_image_size
.type my_image_size, @object
.align 4
my_image_size:
.4byte my_image_end - my_image
This can be saved as my_image.s and linked with a compiler that suports gcc assembly directives. The labels “my_image” and “my_image_size” are referenced from C code when registering the filesystem:
extern const uint8_t my_image[];
extern const uint32_t my_image_size;
...
evfs_register_rsrc_romfs("romfs", my_image, my_image_size, /*default*/ true);
You can pass the EVFS_CMD_GET_RSRC_ADDR command to evfs_file_ctrl()
to directly access in-memory resource data as shown above for the tar resource FS.
-
int
evfs_register_romfs
(const char *vfs_name, EvfsFile *image, bool default_vfs)¶ Register a Romfs instance using an image file
- Parameters
vfs_name – Name of new VFS
image – Mounted Romfs image
default_vfs – Make this the default VFS when true
- Returns
EVFS_OK on success
-
int
evfs_register_rsrc_romfs
(const char *vfs_name, uint8_t *resource, size_t resource_len, bool default_vfs)¶ Register a Romfs instance using an in-memory resource array
- Parameters
vfs_name – Name of new VFS
resource – Array of Romfs resource data
resource_len – Length of the resource array
default_vfs – Make this the default VFS when true
- Returns
EVFS_OK on success