Follow us on Google+ Follow us on Facebook Follow us on Twitter

Version 7 (modified by Jakub Jermář, 8 months ago) (diff)

Filesystem API Tutorial

This tutorial will guide you through the steps that are necessary to work with files on the standard HelenOS API level in an idiomatic way. Besides of the native APIs illustrated in this tutorial, HelenOS also provides a limited set of legacy APIs that are built on top of the native ones. These include the directory browsing functions declared in dirent.h and the I/O stream functions declared stdio.h. We will not cover those here.

Preparing to use the API

HelenOS filesystem API is implemented in the standard library, so there is no need to link against any additional library in order to be able to use it. HelenOS only requires filesystem clients to include vfs/vfs.h in order to get declarations of all the APIs:

#include <vfs/vfs.h>

Paths and File handles

The API is designed to use file handles as a primary and prefered way to refer to files. One may obtain a file handle by looking up a filesytem path, creating a new file or directory or attaching a new filesystem into the filesystem hierarchy.

Note that there is no global filesystem root in HelenOS and equal paths may refer to different files if walked by different HelenOS tasks.

Reading an existing file

Suppose we want to read the contents of file /foo/bar. We first need to lookup the file and convert it to a file handle:

int file = vfs_lookup("/foo/bar", WALK_REGULAR);
if (file < 0)
        return file;

This will only succeed if the file exists and is a regular file, i.e. not a directory, in which case vfs_lookup() receives a file handle. Otherwise we receive a negative error code and bail out.

Because our intention is to perform a read operation on file, we must first open it for reading:

int rc = vfs_open(file, MODE_READ);
if (rc != EOK) {
        vfs_put(file);
        return rc;
}

Note that vfs_open() does not take a path as an argument and does not return a file handle. Instead it takes a pre-existing file handle as an argument, internally marks the file as open for reading and returns a result code, which is EOK on success and negative on error. Also note that in case of error, the file handle we are using needs to be returned to the system before bailing out.

We are now ready to do a couple of synchronous reads from the file. Let's say we first want to read a 4KiB-buffer from offset 0x1000:

uint8_t buffer[4096];
aoff64_t pos = 0x1000;

ssize_t size = vfs_read(file, &pos, buffer, sizeof(buffer));
if (size < 0) {
        vfs_put(file);
        return size;
}
if (size == 0) {
        /* handle EOF */
}

/* buffer is now guaranteed to contain the desired data */
...

The position to read from was passed to vfs_read() explicitly. Moreover, pos was updated by vfs_read() to reflect the updated position. We are now ready to read an additional 4KiB-buffer from offset 0x2000:

size = vfs_read(file, &pos, buffer, sizeof(buffer));
if (size < 0) {
        vfs_put(file);
        return size;
}
if (size == 0) {
        /* handle EOF */
}

/* buffer is now guaranteed to contain the desired data */
...

Finally, if we wanted to continue reading at offset 0x4000, we could simply do:

pos = 0x4000;
size = vfs_read(file, &pos, buffer, sizeof(buffer));
...

When we are finished with file, we must not forget to return it to the system:

vfs_put(file);

Creating a new directory with a file

Imagine we now want to create a new directory /foo/foobar, then create a file named hello.txt inside of it and finally write the string "Hello world!" to it. Let's start with creating the directory:

int foobar;
int rc = vfs_link_path("/foo/foobar", KIND_DIRECTORY, &foobar);
if (rc != EOK)
        return rc;

vfs_link_path() used here is a convenience wrapper around vfs_link(), which we will shortly use too. It allows us not to care about /foo's handle (but directory /foo must already exist). If the return code is EOK, we can be sure that a new directory named /foo/foobar was created and also that there was no file or directory of the same name standing in its way. Moreover, the variable foobar now contains a file handle of the newly created directory. We will use it now to create the file:

int hello;
rc = vfs_link(foobar, "hello", KIND_FILE, &hello);
(void) vfs_put(foobar);
if (rc != EOK)
        return rc;

As in the previous scenario, the file handle needs to be opened before we can write to it:

rc = vfs_open(hello, MODE_WRITE);
if (rc != EOK) {
        vfs_put(hello);
        return rc;
}

Finally, we can write our string. This time we use a compound literal to pass our initial position:

char msg[] = "Hello world!";
ssize_t size = vfs_write(hello, (aoff64_t []) {0}, msg, sizeof(msg));
if (size < 0) {
        vfs_put(hello);
        return size;
}

Finally, we put the file handle:

rc = vfs_put(hello);
if (rc != EOK)
        return rc;

Here we are interested in the return code of vfs_put() because we made modifications to the file. An error at this point might indicate our data didn't get entirely through.

Removing a file

In general, files may have multiple names - so called hardlinks. To remove one of these hardlinks (including the last one), we must unlink it. We could either use a convenience wrapper (eg. vfs_unlink_path()) or, if we already have a file handle of the parent directory and optionally a file handle of the unlinked file, like in the example above, we use vfs_unlink(). So imagine we want to remove the file created in the previous example and that neither of foobar or hello have been put by vfs_put() yet:

rc = vfs_unlink(parent, "hello", hello);
if (rc != EOK)
        return rc;

By passing the third argument to vfs_unlink() above, we are making sure that we won't accidentally remove some other link called hello created by someone else after the original hello had been removed. Note that this argument can be -1 if we don't care.

Renaming a file

In order to rename a file, one needs to use vfs_rename_path():

int rc = vfs_rename_path("/foo/bar", "/foo/bar.txt");
if (rc != EOK)
        return rc;

In this case, this is not a convenience wrapper, but the actual first-class API. The reason there is no vfs_rename() that would operate on file handles in a similar way that vfs_link() and vfs_unlink() do is security considerations. The VFS server needs to prevent pathologies such as renaming /a/b to /a/b/c (creates a loop in the filestem). The VFS server detects such attempts lexically so it needs to know full paths in this case.