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! */ } // ... }