File System Abstraction

Mynewt provides a file system abstraction layer (fs/fs) to allow client code to be file system agnostic. By accessing the file system via the fs/fs API, client code can perform file system operations without being tied to a particular implementation. When possible, library code should use the fs/fs API rather than accessing the underlying file system directly.

Description

Applications should aim to minimize the amount of code which depends on a particular file system implementation. When possible, only depend on the fs/fs package. In terms of the Mynewt hierarchy, an app package must depend on a specific file system package, while library packages should only depend on fs/fs.

Applications wanting to access a filesystem are required to include the necessary packages in their applications pkg.yml file. In the following example, the Newtron Flash File System is used.

# repos/apache-mynewt-core/apps/slinky/pkg.yml

pkg.name: repos/apache-mynewt-core/apps/slinky
pkg.deps:
    - "@apache-mynewt-core/fs/fs"         # include the file operations interfaces
    - "@apache-mynewt-core/fs/nffs"       # include the NFFS filesystem implementation
# repos/apache-mynewt-core/apps/slinky/syscfg.yml
# [...]
 # Package: apps/<example app>
# [...]
    CONFIG_NFFS: 1  # initialize and configure NFFS into the system
#   NFFS_DETECT_FAIL: 1   # Ignore NFFS detection issues
#   NFFS_DETECT_FAIL: 2   # Format a new NFFS file system on failure to detect

# [...]

Consult the nffs documentation for a more detailed explanation of NFFS_DETECT_FAIL

Code which uses the file system after the system has been initialized need only depend on fs/fs. For example, the libs/imgmgr package is a library which provides firmware upload and download functionality via the use of a file system. This library is only used after the system has been initialized, and therefore only depends on the fs/fs package.

# repos/apache-mynewt-core/libs/imgmgr/pkg.yml
pkg.name: libs/imgmgr
pkg.deps:
    - "@apache-mynewt-core/fs/fs"

# [...]

The libs/imgmgr package uses the fs/fs API for all file system operations.

Support for multiple filesystems

When using a single filesystem/disk, it is valid to provide paths in the standard unix way, eg, /<dir-name>/<file-name>. When trying to run more than one filesystem or a single filesystem in multiple devices simultaneosly, an extra name has to be given to the disk that is being used. The abstraction for that was added as the fs/disk package which is a dependency of fs/fs. It adds the following extra user function:

int disk_register(const char *disk_name, const char *fs_name, struct disk_ops *dops)

As an example os usage:

disk_register("mmc0", "fatfs", &mmc_ops);
disk_register("flash0", "nffs", NULL);

This registers the name mmc0 to use fatfs as the filesystem and mmc_ops for the low-level disk driver and also registers flash0 to use nffs. nffs is currently strongly bound to the hal_flash interface, ignoring any other possible disk_ops given.

struct disk_ops

To support a new low-level disk interface, the struct disk_ops interface must be implemented by the low-level driver. Currently only read and write are effectively used (by fatfs).

struct disk_ops {
    int (*read)(uint8_t, uint32_t, void *, uint32_t);
    int (*write)(uint8_t, uint32_t, const void *, uint32_t);
    int (*ioctl)(uint8_t, uint32_t, void *);
    SLIST_ENTRY(disk_ops) sc_next;
}

Thread Safety

All fs/fs functions are thread safe.

Header Files

All code which uses the fs/fs package needs to include the following header:

#include "fs/fs.h"

Data Structures

All fs/fs data structures are opaque to client code.

struct fs_file;
struct fs_dir;
struct fs_dirent;

Examples

Example 1 below opens the file /settings/config.txt for reading, reads some data, and then closes the file.

int
read_config(void)
{
    struct fs_file *file;
    uint32_t bytes_read;
    uint8_t buf[16];
    int rc;

    /* Open the file for reading. */
    rc = fs_open("/settings/config.txt", FS_ACCESS_READ, &file);
    if (rc != 0) {
        return -1;
    }

    /* Read up to 16 bytes from the file. */
    rc = fs_read(file, sizeof buf, buf, &bytes_read);
    if (rc == 0) {
        /* buf now contains up to 16 bytes of file data. */
        console_printf("read %u bytes\n", bytes_read)
    }

    /* Close the file. */
    fs_close(file);

    return rc == 0 ? 0 : -1;
}

Example 2 below iterates through the contents of a directory, printing the name of each child node. When the traversal is complete, the code closes the directory handle.

int
traverse_dir(const char *dirname)
{
    struct fs_dirent *dirent;
    struct fs_dir *dir;
    char buf[64];
    uint8_t name_len;
    int rc;

    rc = fs_opendir(dirname, &dir);
    if (rc != 0) {
        return -1;
    }

    /* Iterate through the parent directory, printing the name of each child
     * entry.  The loop only terminates via a function return.
     */
    while (1) {
        /* Retrieve the next child node. */
        rc = fs_readdir(dir, &dirent);
        if (rc == FS_ENOENT) {
            /* Traversal complete. */
            return 0;
        } else if (rc != 0) {
            /* Unexpected error. */
            return -1;
        }

        /* Read the child node's name from the file system. */
        rc = fs_dirent_name(dirent, sizeof buf, buf, &name_len);
        if (rc != 0) {
            return -1;
        }

        /* Print the child node's name to the console. */
        if (fs_dirent_is_dir(dirent)) {
            console_printf(" dir: ");
        } else {
            console_printf("file: ");
        }
        console_printf("%s\n", buf);
    }
}

Example 3 below demonstrates creating a series of nested directories.

int
create_path(void)
{
    int rc;

    rc = fs_mkdir("/data");
    if (rc != 0) goto err;

    rc = fs_mkdir("/data/logs");
    if (rc != 0) goto err;

    rc = fs_mkdir("/data/logs/temperature");
    if (rc != 0) goto err;

    rc = fs_mkdir("/data/logs/temperature/current");
    if (rc != 0) goto err;

    return 0;

err:
    /* Clean up the incomplete directory tree, if any. */
    fs_unlink("/data");
    return -1;
}

Example 4 below demonstrates reading a small text file in its entirety and printing its contents to the console.

int
print_status(void)
{
    uint32_t bytes_read;
    uint8_t buf[16];
    int rc;

    /* Read up to 15 bytes from the start of the file. */
    rc = fsutil_read_file("/cfg/status.txt", 0, sizeof buf - 1, buf,
                          &bytes_read);
    if (rc != 0) return -1;

    /* Null-terminate the string just read. */
    buf[bytes_read] = '\0';

    /* Print the file contents to the console. */
    console_printf("%s\n", buf);

    return 0;
}

Example 5 creates a 4-byte file.

int
write_id(void)
{
    int rc;

    /* Create the parent directory. */
    rc = fs_mkdir("/cfg");
    if (rc != 0 && rc != FS_EALREADY) {
        return -1;
    }

    /* Create a file and write four bytes to it. */
    rc = fsutil_write_file("/cfg/id.txt", "1234", 4);
    if (rc != 0) {
        return -1;
    }

    return 0;
}

API

Defines

FS_ACCESS_READ

File access flags.

FS_ACCESS_WRITE
FS_ACCESS_APPEND
FS_ACCESS_TRUNCATE
FS_EOK

File access return codes.

FS_ECORRUPT
FS_EHW
FS_EOFFSET
FS_EINVAL
FS_ENOMEM
FS_ENOENT
FS_EEMPTY
FS_EFULL
FS_EUNEXP
FS_EOS
FS_EEXIST
FS_EACCESS
FS_EUNINIT
FS_NMGR_ID_FILE
FS_NMGR_MAX_NAME

Functions

int fs_open(const char *filename, uint8_t access_flags, struct fs_file**)
int fs_close(struct fs_file*)
int fs_read(struct fs_file*, uint32_t len, void *out_data, uint32_t *out_len)
int fs_write(struct fs_file*, const void *data, int len)
int fs_seek(struct fs_file*, uint32_t offset)
uint32_t fs_getpos(const struct fs_file*)
int fs_filelen(const struct fs_file*, uint32_t *out_len)
int fs_rename(const char *from, const char *to)
int fs_mkdir(const char *path)
int fs_opendir(const char *path, struct fs_dir**)
int fs_readdir(struct fs_dir*, struct fs_dirent**)
int fs_closedir(struct fs_dir*)
int fs_dirent_name(const struct fs_dirent*, size_t max_len, char *out_name, uint8_t *out_name_len)
int fs_dirent_is_dir(const struct fs_dirent*)

Functions

int fsutil_read_file(const char *path, uint32_t offset, uint32_t len, void *dst, uint32_t *out_len)
int fsutil_write_file(const char *path, const void *data, uint32_t len)