VFS basics

Introduction

The VFS is a large topic. This page will present the basics of how to use the VFS.

VFS is a library

Just like any other library, we must first add it as a dependency to our dfe.conf.

When using the VFS, we will (most of the time) also use some filesystem driver(s) with it. Since these usually come as libraries as well, let's also add them here.

In this example, we will be using the 'vfat' FAT32 driver:

#...
deps:
  - vfs
  - vfat

Initialization

There are two ways we can set up the VFS - by hand or with substrate. Obviously, using the substrate is the recommended way as it will make things much easier.

With substrate

Simply add 'uses :vfs' in your substrate. That's it!

When using the substrate, filesystem drivers will detect the presence of the VFS and automatically initialize themselves.

# Use the VFS
uses :vfs

Manual initialization (without substrate)

If we're not using substrate, the first thing we need to do is initialize the VFS core itself. To accomplish this, simply call 'vfs_init'. Then, we need to initialize each filesystem driver we might want to use:

 1 #include <vfs/vfs.h>
 2 #include <vfat/vfat.h>
 3 
 4 void main()
 5 {
 6     // Initialize VFS Core
 7     vfs_init();
 8 
 9     // Initialize VFAT driver
10     vfat_init();
11 
12     // ...
13 }

Mounting filesystems

Once everything is initialized, we can start mounting some storage devices using file systems (such as FAT). Mountpoints need to be given a short (max 16 chars) name.

For an example of what to mount, checkout the Dooba storage model and the SD Card driver

With substrate

The substrate allows us to specify mountpoints in pretty much the same way as everything else - through the 'uses' keyword (for this example we will assume a storage device 'sdc0' is used):

# Use the VFS
uses :vfs

# Mount 'sdc0' as 'root' using vfat file system
uses :vfs_mountpoint, dev: 'sdc0', as: 'root', fs: :vfat

Parameters 'fs' and 'as' are both optional. If no 'as' (mountpoint name) is specified, the name of the storage device ('sdc0' in this case) will be used. If no 'fs' (file system driver) is provided, every known file system driver will be attempted until one is found to work.

Any mountpoint structure created by substrate will be accessible from the code through a pointer with the name 'xxx_mp' where 'xxx' is the name of the mountpoint. For example: struct vfs_mountpoint *sdc0_mp;

Auto-mounting Everything

In fact, the substrate system takes this simplification one step further and allows you to 'automount everything' with just the following:

# Use the VFS
uses :vfs

# Automount everything
uses :vfs_automount

This will attempt to mount every known storage device using every known file system driver. Any resulting successful mountpoints will be named after their respective storage devices.

Manual mounting (without substrate)

It is possible to mount things without the substrate, though not recommended:

 1 #include <vfs/vfs.h>
 2 #include <vfat/vfat.h>
 3 
 4 // Our example mountpoint
 5 struct vfs_mountpoint mp;
 6 
 7 void main()
 8 {
 9     // Initialize VFS Core
10     vfs_init();
11 
12     // Initialize VFAT driver
13     vfat_init();
14 
15     // ...
16 
17     // Attempt to mount something (assuming 'sdc0' is a pointer to storage device - struct storage *)
18     if(vfs_mount_by_fs_name(&mp, sdc0, 'vfat', 4, 'root', 4))    { /* ERROR: Mounting failed! */ }
19 
20     // ...
21 
22     // Attempt to auto-mount something (assuming 'sdc0' is a pointer to storage device - struct storage *)
23     if(vfs_mount_auto(&mp, sdc0, 'root', 4))    { /* ERROR: Mounting failed! */ }
24 
25     // ...
26 }

Un-mounting

If at any moment your application needs to un-mount something, it can do so by calling 'vfs_unmount' on a pointer to a mountpoint:

 1 #include <vfs/vfs.h>
 2 
 3 void main()
 4 {
 5     // ...
 6 
 7     // Attempt to un-mount
 8     if(vfs_unmount(sdc0_mp))    { /* ERROR: Un-mounting failed! */ }
 9 
10     // ...
11 }

Un-mounting will fail if there are any open handles (files / directories) on the desired mountpoint.

Listing mountpoints

Whenever we need to programmatically run through all available mountpoints, we can use the vfs_mountpoints list:

 1 #include <vfs/vfs.h>
 2 #include <scli/scli.h>
 3 
 4 void main()
 5 {
 6     struct vfs_mountpoint *p;
 7 
 8     // ...
 9 
10     // Run through available mountpoints
11     scli_printf("Available mountpoints:\n");
12     p = vfs_mountpoints;
13     while(p)
14     {
15         // Print out mountpoint
16         scli_printf(" * %t (device '%t' using filesystem '%t')\n", p->name, p->name_len, p->st->name, p->st->name_len, p->fs->name, p->fs->name_len);
17 
18         // Go to next mountpoint
19         p = p->next;
20     }
21     
22     // ...
23 }

Reading and writing files

Now that we have some mountpoint(s), we can start to read and write files.

As for most systems today, the typical pattern of 'open, read/write, close' applies here: you first open a file, then read and/or write whatever is needed, and finally close the file.

Reading

Below are some examples of how to read files:

 1 #include <vfs/vfs.h>
 2 
 3 // A buffer for holding data
 4 uint8_t buf[1024];
 5 
 6 void main()
 7 {
 8     struct vfs_handle h;
 9     uint16_t bytes;
10 
11     // ...
12 
13     // Open file
14     if(vfs_open(&h, "root:/helloworld.txt", 0))    { /* ERROR: Open failed! */ }
15 
16     // Read fixed number (128) of bytes. The number of bytes actually read will be stored in 'bytes' variable
17     if(vfs_read(&h, buf, 128, &bytes))    { /* ERROR: Reading failed! */ }
18 
19     // Read a line of text from file. The size of the line (without the \n character) will be stored in 'bytes' variable
20     if(vfs_gets(&h, buf, 1024, &bytes))   { /* ERROR: Reading failed! */ }
21 
22     // Close file
23     vfs_close(&h);
24 
25     // ...
26 }

Writing

Here are a few examples of how to write files:

 1 #include <vfs/vfs.h>
 2 
 3 // A buffer for holding data
 4 uint8_t buf[1024];
 5 
 6 void main()
 7 {
 8     struct vfs_handle h;
 9     uint16_t bytes;
10 
11     // ...
12 
13     // Open file
14     if(vfs_open(&h, "root:/helloworld.txt", VFS_OPEN_CREATE | VFS_OPEN_TRUNCATE))    { /* ERROR: Open failed! */ }
15 
16     // Write fixed number (128) of bytes. The number of bytes actually read will be stored in 'bytes' variable
17     if(vfs_write(&h, buf, 128, &bytes))    { /* ERROR: Writing failed! */ }
18 
19     // Print text to file
20     if(vfs_print(&h, "Hello, %s! Here is a number: %i\n", "world", 42);
21 
22     // Print line of text to file (same as above, but appends '\n' automatically)
23     if(vfs_puts(&h, "Hello, %s! Here is a number: %i", "world", 42);
24 
25     // Close file
26     vfs_close(&h);
27 
28     // ...
29 }

Open flags

The last parameter to the 'vfs_open' function is a bitmask for 'open flags'. This means that it can be a bitwise combination (bitwise OR) of the following:

  • VFS_OPEN_CREATE - Will create the file if it doesn't exist
  • VFS_OPEN_TRUNCATE - Will truncate the file (to 0 size) after opening

If no flags are needed, simply pass '0' as last parameter.

Object types (OTYPE)

The following object types are defined:

Object type Value
VFS_OTYPE_NONE 0
VFS_OTYPE_FILE 1
VFS_OTYPE_DIR 2

 

Listing directory contents

We can now also start looking at what's present inside any given directory (the example below assumes we have mounted something as 'root'):

 1 #include <vfs/vfs.h>
 2 #include <scli/scli.h>
 3 
 4 void main()
 5 {
 6     struct vfs_handle h;
 7     struct vfs_dirent p;
 8 
 9     // ...
10 
11     // Open Directory
12     if(vfs_open(&h, "root:/", 0))    { /* ERROR: Open failed! */ }
13 
14     // Run through directory contents of 'root:/'
15     scli_printf("Contents of 'root:/'\n");
16     p.otype = VFS_OTYPE_FILE;
17     while(p.otype != VFS_OTYPE_NONE)
18     {
19         // Read Entry
20         if(vfs_readdir(&h, &p))    { /* ERROR: Read directory failed! */ }
21         if(p.otype != VFS_OTYPE_NONE)
22         {
23             // Print out file / directory
24             scli_printf(" * %t (type: %s) - size: %Y\n", p.name, p.name_len, ((p.otype == VFS_OTYPE_FILE) ? "file" : "dir"), p.size);
25         }
26     }
27     
28     // Close directory
29     vfs_close(&h);
30 
31     // ...
32 }

Creating, moving, copying, destroying objects

Objects (files and directories) can also be manipulated using the following functions:

#include <vfs/vfs.h>

void main()
{
    // ...

    // Create directory 'root:/foobar'
    if(vfs_mkobj("root:/foobar", VFS_OTYPE_DIR))    { /* ERROR: Creating directory failed! */ }

    // Create file 'root:/foobar/test.txt' (with fixed path length)
    if(vfs_mkobj_n("root:/foobar/test.txt", 21, VFS_OTYPE_FILE))    { /* ERROR: Creating file failed! */ }

    // Destroy file
    if(vfs_rmobj("root:/foobar/test.txt"))    { /* ERROR: Removing file failed! */ }

    // Destroy directory (with fixed path length) - this will fail unless the directory is empty!
    if(vfs_rmobj_n("root:/foobar", 12))    { /* ERROR: Removing directory failed! */ }

    // Copy a file
    if(vfs_cpobj("root:/test.txt", "root:test-copy.txt"))    { /* ERROR: Copying file failed! */ }

    // Copy a file (with fixed path length)
    if(vfs_cpobj("root:/test.txt", 10, "root:test-copy.txt", 15))    { /* ERROR: Copying file failed! */ }

    // Move a file
    if(vfs_mvobj("root:/test.txt", "root:test-copy.txt"))    { /* ERROR: Moving file failed! */ }

    // Move a file (with fixed path length)
    if(vfs_mvobj("root:/test.txt", 10, "root:test-copy.txt", 15))    { /* ERROR: Moving file failed! */ }

    // ...
}