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().

Using a FatFs image file
#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.

Using a littlefs image file
#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