EVFS library API

The EVFS library has a number of sub-components that are described in the following sections:

Basic usage

Your first task is to initialize EVFS with the set of filesystems and shims you want to use. This is described below in more detail but we’ll start with this skeleton:

#include "evfs.h"
#include "evfs/stdio_fs.h"

int main() {

  evfs_init();
  evfs_register_stdio(/*default_vfs*/ true);

  ...

From this point you have access to the C stdio filesystem using EVFS. You can perform filesystem level operations using the top level filesystem access functions. Among them are evfs_open_ex() and evfs_open_dir_ex() which create EvfsFile and EvfsDir objects respectively. These objects each have a set of methods for operating on the underlying file or directory.

Most of the library functions return an integer error code. Success is indicated by EVFS_OK which is 0. Error conditions are always negative. Some functions will have additional positive values for non-error conditions. You can convert error codes into a string using evfs_err_name():

EvfsFile *fh;
int status = evfs_open("foobar/test.txt", &fh, EVFS_READ);
printf("evfs_open() returned '%s'\n", evfs_err_name(status));

Access mode flags

When you open a file you must supply a set of flags to identify what mode you want to open the file in. EVFS provides the following set of flags:

EVFS_READ

Open in read mode

EVFS_WRITE

Open in write mode

EVFS_RDWR

Combination of EVFS_READ and EVFS_WRITE

EVFS_OPEN_OR_NEW

Open an existing file or create a new one

EVFS_NO_EXIST

Do not open if the file exists

EVFS_OVERWRITE

Truncate all existing file content

EVFS_APPEND

Append writes to the end of file

These can be merged together in combination to achieve different results.

// Open a new file to write into
status = evfs_open("file.txt", &fh, EVFS_WRITE | EVFS_NO_EXIST);

// Open file to append data and read back its content
status = evfs_open("file.txt", &fh, EVFS_RDWR | EVFS_APPEND);

Files are always opened in binary mode for every filesystem. There is no special handling of newline characters.

To read and write from a file you need to have a buffer to hold the data going in or out. Reads and writes may be partial so you should be prepared to repeat an operation if necessary.

status = evfs_open("file.txt", &fh, EVFS_RDWR);

char buf[100];
int read = evfs_file_read(fh, buf, sizeof(buf));
if(read >= 0)
  printf("Read %d bytes from file\n", read);

evfs_file_rewind(fh); // Go back to start of the file

int wrote = evfs_file_write(fh, buf, sizeof(buf));
if(wrote >= 0)
  printf("Wrote %d bytes to file\n", wrote);

evfs_file_close(fh);

File metadata

EVFS uses a common EvfsInfo struct to work with file and directory metadata.

struct EvfsInfo

Metadata for files and directories

  • char *name - Name of a file or directory

  • time_t mtime - Modification time

  • evfs_off_t size - File size

  • uint8_t type - Object type (file or directory)

These info structs are returned by evfs_stat_ex() and evfs_dir_read(). Not all fields will be populated by these functions. Some data may be unavailable depending on the underlying filesystem. You can check which fields are valid by calling evfs_vfs_ctrl_ex() with the EVFS_CMD_GET_STAT_FIELDS and EVFS_CMD_GET_DIR_FIELDS commands. This will return a bitfield value where set bits correspond to the members of EvfsInfo that are valid for the current VFS. Unused fields will always be 0.

// Query for supported stat fields
unsigned stat_fields;
evfs_vfs_ctrl_ex(EVFS_CMD_GET_STAT_FIELDS, &stat_fields, "stdio");

// Get stat
EvfsInfo info;
evfs_stat_ex("/file.txt", &info, "stdio");

// Check each supported field before access

// Name is always present so this particular check isn't strictly required
if(stat_fields & EVFS_INFO_NAME)  printf("File name is: %s\n", info.name);

// Mtime is missing on filesystems without time metadata and systems that don't
// report time in directory listings.
if(stat_fields & EVFS_INFO_MTIME) {
  char mtime[30];
  ctime_r(info.mtime, mtime);
  printf("Last modified: %s\n", mtime);
}

Directory listing

Directories can be listed by opening a EvfsDir with evfs_open_dir_ex(). The directory access methods are then used to scan through the contents of a directory. evfs_dir_read() will return EVFS_DONE when there are no more entries to read in a directory.

EvfsDir *dh;
evfs_open_dir("path/to/dir", &dh);

EvfsInfo info;
int status = evfs_dir_read(dh, &info);
while(status != EVFS_DONE) {
  // Work with this entry

  ...

  status = evfs_dir_read(dh, &info);
}

evfs_dir_close(dh);

Directory entries are returned in whatever order the underlying filesystem produces them. Some filesystems may add entries for the current (“.”) and parent (“..”) directories. These can be suppressed by configuring the VFS with an EVFS_CMD_SET_NO_DIR_DOTS command:

unsigned no_dots = 1;
evfs_vfs_ctrl(EVFS_CMD_SET_NO_DIR_DOTS, &no_dots);

EVFS architecture

EVFS uses a design that takes inspiration from the SQLite VFS driver mechanism. You can register multiple filesystem interfaces and stack optional shims on top of them to alter behavior.

../../_images/evfs_system.svg

Every VFS interface or shim is assigned a name used to refer to the VFS object at a later time. File and directory handles are opened against one of these named VFS objects. Shims must be registered on top of an existing interface or another shim. One of the VFS objects is designated as the default. All API functions with the “_ex” suffix will target the default if the vfs_name argument is NULL.

EVFS does not directly interact with storage media. You need to have the filesystem driver required to access your storage. The necessary driver code is provided by your operating system or an auxilliary library outside of EVFS.

Mounting image files

EVFS is capable of mounting filesystem images stored on a host filesystem and then accessing them as an independent VFS. To accomplish this you need to incorporate a filesystem driver into your application. This driver layer normally expects to interact with the storage device. You need to add an I/O adapter that translates low level storage operations into EVFS API calls on the opened image file. An EVFS filesystem interface is registered to the new filesystem driver. The EVFS API can then be used to access the contents of the image file as if it were a normal filesystem.

../../_images/evfs_images.svg

EVFS includes code that manages image files for the FatFs and littlefs filesystems.

Library management

The evfs_init() function must be called before anything can be done with EVFS. This ensures initialization of internal data structures has been performed. When threading is enabled it also creates a library-wide lock to protect accesss to the data structures. If EVFS_USE_ATEXIT is enabled in ‘evfs_config.h’, an atexit() handler will also be installed. If this isn’t enabled you must call evfs_unregister_all() before the process exits to ensure that all VFSs have flushed their data and released any resources.

The mechanism for adding a new VFS or shim is to use evfs_register(). Normally you will not call this directly but instead call a wrapper function provided by each VFS or shim driver. This function performs a secondary purpose of changing the default status of a VFS. If the VFS already exists, the make_default argument will change the VFS to the new default if true. If it is set false, another VFS will be found to serve as the new default.

evfs_register_stdio(/*default_vfs*/ true); // Add a filesystem
// stdio is now the default

evfs_register_trace("t_stdio", "stdio", report, stderr, /*default_vfs*/ true); // Add a debug tracing shim
// Default is now the trace shim on top of stdio


// Bypass the shim and make stdio the default again
Evfs *stdio_vfs = evfs_find_vfs("stdio");
evfs_register(stdio_vfs, /*make_default*/ true);


// Remove default from stdio. "t_stdio" will become the new default.
evfs_register(stdio_vfs, /*make_default*/ false);

You can unregister a VFS or shim at runtime using evfs_unregister(). You must ensure that no open file or directory handles exist that reference back to a VFS being unregistered. The library does not track this for you.

void evfs_init(void)

Initialize the EVFS library.

Evfs *evfs_find_vfs(const char *vfs_name)

Search for a VFS by name.

Parameters
  • vfs_name – VFS to search for

Returns

The matching VFS object or NULL

const char *evfs_vfs_name(Evfs *vfs)

Get the name of a VFS object.

Parameters
  • vfs – The VFS to get name from

Returns

The name this VFS is registered under

const char *evfs_default_vfs_name(void)

Get the name of the default VFS object.

Returns

The name of the default VFS

int evfs_register(Evfs *vfs, bool make_default)

Register a new VFS or change the default status of an existing VFS.

If the VFS has already been registered the make_default argument will update the status of this VFS. If there is only one registered VFS, make_default is ignored.

This will fail if evfs_init() hasn’t been called.

Parameters
  • vfs – The new VFS to register

  • make_default – Set this VFS to be default or not

Returns

EVFS_OK on success

int evfs_unregister(Evfs *vfs)

Unregister a VFS object.

Parameters
  • vfs – The VFS to remove

Returns

EVFS_OK on success

void evfs_unregister_all(void)

Unregister all registered VFS objects.

Filesystem access

The top level of the EVFS API involves functions that directly interact with a VFS to perform operations. You can open, delete, rename, copy, and query for the status on files and directories. The evfs_open_ex() and evfs_open_dir_ex() functions return a handle that is used to perform further operations on those entities.

Most of these functions have an “_ex” suffix indicating that their last parameter is an optional VFS name. All of these forms have a macro defined without the suffix that uses the default VFS. This is the same as passing NULL for the vfs_name argument to the “_ex” forms.

EvfsFile *fh;
evfs_open_ex(path, &fh, EVFS_READ, "stdio"); // Open on named VFS

evfs_open_ex(path, &fh, EVFS_READ, NULL);    // Open on default VFS
evfs_open(path, &fh, EVFS_READ);             // Same as above using macro

There is a general purpose interface for passing named operations to the VFS via evfs_vfs_ctrl_ex(). It takes a command ID and a command specific argument to pass and return values that can alter or query internal VFS behavior. For example, you can use the EVFS_CMD_SET_READONLY command to write protect a VFS from any EVFS functions. See the listing of commands in ‘evfs.h’ for what is available and the types they take as an argument.

unsigned readonly = 1;
evfs_vfs_ctrl(EVFS_CMD_SET_READONLY, &readonly); // Make the default VFS readonly

unsigned no_dots = 1;
evfs_vfs_ctrl(EVFS_CMD_SET_NO_DIR_DOTS, &no_dots); // Strip "." and ".." from the output of evfs_dir_read()
int evfs_open_ex(const char *path, EvfsFile **fh, int flags, const char *vfs_name)

Open a file.

Parameters
  • path – Filesystem path to the file

  • fh – Handle for successfully opened file

  • flags – Open mode flags

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_stat_ex(const char *path, EvfsInfo *info, const char *vfs_name)

Get file or directory status.

Different VFS backends may only support a partial set of EvfsInfo fields. Use the EVFS_CMD_GET_STAT_FIELDS command with evfs_vfs_ctrl_ex() to query which fields are valid on a particular VFS. Unsupported fields will be 0.

Parameters
  • path – Filesystem path to the file

  • info – Information reported on the file

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

bool evfs_existing_file_ex(const char *path, const char *vfs_name)

Test if a file exists.

Parameters
  • path – Filesystem path to the file

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

true if the file exists

bool evfs_existing_dir_ex(const char *path, const char *vfs_name)

Test if a directory exists.

Parameters
  • path – Filesystem path to the directory

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

true if the file exists

int evfs_delete_ex(const char *path, const char *vfs_name)

Delete a file or directory.

Parameters
  • path – Filesystem path to the file

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_rename_ex(const char *old_path, const char *new_path, const char *vfs_name)

Rename a file or directory.

No validation or transformation is made to the path arguments. Absolute paths should match the same parent directory or this is likely to fail.

Parameters
  • old_path – Filesystem path to existing file

  • new_path – New path to the file

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_make_dir_ex(const char *path, const char *vfs_name)

Make a new directory.

Parameters
  • path – Filesystem path to the directory

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_make_path_ex(const char *path, const char *vfs_name)

Make a complete path to a nested directory.

Any missing directories in the path will be created.

Parameters
  • path – Filesystem path to a directory

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_make_path_range_ex(StringRange *path, const char *vfs_name)

Make a complete path to a nested directory.

Any missing directories in the path will be created.

This variant takes a StringRange object as the path. This allows the directory portion of a file path as generated by evfs_path_dirname_ex() to be referenced as a substring without making a copy.

Parameters
  • path – Filesystem path to a directory

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_open_dir_ex(const char *path, EvfsDir **dh, const char *vfs_name)

Open a directory.

Parameters
  • path – Filesystem path to the directory

  • dh – Handle for successfully opened directory

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_vfs_open_dir(Evfs *vfs, const char *path, EvfsDir **dh)

Open a directory from a VFS object.

Parameters
  • vfs – The VFS to open on

  • path – Filesystem path to the directory

  • dh – Handle for successfully opened directory

Returns

EVFS_OK on success

int evfs_get_cur_dir_ex(StringRange *cur_dir, const char *vfs_name)

Get the current working directory for a VFS.

Note that EVFS does not have any mechanism for handling DOS/Windows-style drive volumes. The reported working directory will be for the active volume.

The returned StringRange should not be modified as it may point into internal EVFS data structures.

Parameters
  • cur_dir – Reference to the current working directory

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_set_cur_dir_ex(const char *path, const char *vfs_name)

Set the current working directory for a VFS.

Relative paths will be based in this directory until it is changed.

Parameters
  • path – Path to the new working directory

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_vfs_ctrl_ex(int cmd, void *arg, const char *vfs_name)

Generic configuration control for a VFS.

See the definition of commands in evfs.h for the expected type to pass as arg.

Parameters
  • cmd – Command number for the operation to perform

  • arg – Variable argument data to write or read associated with the command

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

int evfs_copy_to_file_ex(const char *dest_path, EvfsFile *fh, char *buf, size_t buf_size, const char *vfs_name)

Copy contents of an open file to a new file.

This allows you to transfer files across different VFSs.

Parameters
  • dest_path – Path to the new copy

  • fh – Open file to copy from

  • buf – Buffer to use for transfers. Use NULL to malloc a temp buffer.

  • buf_size – Size of buf array. When buf is NULL this is the size to allocate.

  • vfs_name – VFS to work on. Use default VFS if NULL

Returns

EVFS_OK on success

File object methods

All of the functions with an “evfs_file” prefix are methods of an EvfsFile object created by evfs_open_ex().

int evfs_file_close(EvfsFile *fh)

Close a file.

Parameters
  • fh – The file to close

Returns

EVFS_OK on success

int evfs_file_ctrl(EvfsFile *fh, int cmd, void *arg)

Generic configuration control for a file object.

See the definition of commands in evfs.h for the expected type to pass as arg.

Parameters
  • fh – The file to receive the command

  • cmd – Command number for the operation to perform

  • arg – Variable argument data to write or read associated with the command

Returns

EVFS_OK on success

size_t evfs_file_read(EvfsFile *fh, void *buf, size_t size)

Read data from a file.

Parameters
  • fh – The file to read

  • buf – Buffer for read data

  • size – Size of buf

Returns

Number of bytes read on success or negative error code on failure

size_t evfs_file_write(EvfsFile *fh, const void *buf, size_t size)

Write data to a file.

Parameters
  • fh – The file to write

  • buf – Buffer for write data

  • size – Size of buf

Returns

Number of bytes written on success or negative error code on failure

int evfs_file_truncate(EvfsFile *fh, evfs_off_t size)

Truncate the length of a file.

Parameters
  • fh – The file to truncate

  • size – New truncated size

Returns

EVFS_OK on success

int evfs_file_sync(EvfsFile *fh)

Sync a file to the underlying filesystem.

Parameters
  • fh – The file to sync

Returns

EVFS_OK on success

evfs_off_t evfs_file_size(EvfsFile *fh)

Get the size of a file.

This will perform a sync to guarantee that intermediate write buffers are emptied before checking the size.

Parameters
  • fh – The file to size up

Returns

Size of the open file

int evfs_file_seek(EvfsFile *fh, evfs_off_t offset, EvfsSeekDir origin)

Seek to a new offset in a file.

Origin is one of the following:

  • EVFS_SEEK_TO - Absolute position from the start of the file

  • EVFS_SEEK_REL - Position relative to current file offset

  • EVFS_SEEK_REV - Position from the end of the file

EVFS_SEEK_REL uses negative values to seek backward and positive to go forward. The other origin types use positive offset values.

Use the evfs_file_rewind macro to seek back to offset 0.

Parameters
  • fh – The file to seek on

  • offset – New position relative to the origin

  • origin – Start position for the seek

Returns

EVFS_OK on success

evfs_off_t evfs_file_tell(EvfsFile *fh)

Get the current position within a file.

Parameters
  • fh – The file to report on

Returns

Current offset into the file from the start

bool evfs_file_eof(EvfsFile *fh)

Identify end of file.

Parameters
  • fh – The file to report on

Returns

true if file is at the end

Directory object methods

All of the functions with an “evfs_dir” prefix are methods of an EvfsDir object created by evfs_open_dir_ex().

int evfs_dir_close(EvfsDir *dh)

Close a directory.

Parameters
  • dh – The directory to close

Returns

EVFS_OK on success

int evfs_dir_read(EvfsDir *dh, EvfsInfo *info)

Read the next directory entry.

Different VFS backends may only support a partial set of EvfsInfo fields. Use the EVFS_CMD_GET_DIR_FIELDS command with evfs_vfs_ctrl_ex() to query which fields are valid on a particular VFS. Unsupported fields will be 0.

Parameters
  • dh – The directory to read from

  • info – Information reported on the file

Returns

EVFS_OK on success. EVFS_DONE when iteration is complete.

int evfs_dir_rewind(EvfsDir *dh)

Rewind a directory iterator to the beginning.

Parameters
  • dh – The directory to rewind

Returns

EVFS_OK on success

int evfs_dir_find(EvfsDir *dh, const char *pattern, EvfsInfo *info)

Find a file matching a glob pattern.

This scans a directory for a file until a match to pattern is found. Repeated calls will find the next file that matches the pattern.

Parameters
  • dh – The directory to search

  • pattern – Glob pattern to be matched

  • info – Information reported on the file

Returns

EVFS_OK on success. EVFS_DONE when iteration is complete.

Miscellaneous

const char *evfs_err_name(int err)

Translate an error code into a string.

Parameters
  • err – EVFS error code

Returns

The corresponding string or “<unknown>”

const char *evfs_cmd_name(int cmd)

Translate a command code into a string.

Parameters
  • cmd – EVFS command code

Returns

The corresponding string or “<unknown>”

int evfs_file_printf(EvfsFile *fh, const char *fmt, ...)

Print a formatted string to a file..

Parameters
  • fh – Open file to print into

  • fmt – Format string

  • args – Variable argument list

Returns

Number of bytes written on success or negative error code on failure

int evfs_file_puts(EvfsFile *fh, const char *str)

Write a string to a file.

Parameters
  • fh – Open file to print into

  • str – String to write

Returns

Number of bytes written on success or negative error code on failure