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_unlink
(const char *filename)¶
-
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*)¶