Extra Information on Unclear FUSE Functions

The intent of this page is to give a little bit of extra information on calls that seem a little obscure in the FUSE documentation. Two cases requiring extra explanation have come up so far: readdir(), and FUSE's handling of the file creation flags in open().

Directories and readdir()

FUSE provides a mechanism to place entries in a directory structure. The directory structure itself is opaque, so the basic mechanism is to create the data and call a FUSE-supplied function to put it in the structure.

When your readdir() callback is invoked, one of the parameters is a function called filler(). The purpose of this function is to insert directory entries into the directory structure, which is also passed to your callback as buf.

filler()'s prototype looks like this:

int fuse_fill_dir_t(void *buf, const char *name,
				const struct stat *stbuf, off_t off);

You insert an entry into buf (the same buffer that is passed to readdir()) by calling filler() with the filename and optionally a pointer to a struct stat containing the file type.

bb_readdir() uses filler() in as simple a way as possible to just copy the underlying directory's filenames into the mounted directory. Notice that the offset passed to bb_readdir() is ignored, and an offset of 0 is passed to filler(). This tells filler() to manage the offsets into the directory structure for itself. Here's the code:

int bb_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
               struct fuse_file_info *fi)
{
    int retstat = 0;
    DIR *dp;
    struct dirent *de;
    
    log_msg("bb_readdir(path=\"%s\", buf=0x%08x, filler=0x%08x, offset=%lld, fi=0x%08x)\n",
            path, (int) buf, (int) filler,  offset, (int) fi);
    
    dp = (DIR *) (uintptr_t) fi->fh;

    // Every directory contains at least two entries: . and ..  If my
    // first call to the system readdir() returns NULL I've got an
    // error; near as I can tell, that's the only condition under
    // which I can get an error from readdir()
    de = readdir(dp);
    if (de == 0)
        return -errno;

    // This will copy the entire directory into the buffer.  The loop exits
    // when either the system readdir() returns NULL, or filler()
    // returns something non-zero.  The first case just means I've
    // read the whole directory; the second means the buffer is full.
    do {
        log_msg("calling filler with name %s\n", de->d_name);
        if (filler(buf, de->d_name, NULL, 0) != 0)
            return -ENOMEM;
    } while ((de = readdir(dp)) != NULL);
    
    log_fi(fi);
    
    return retstat;
}

File Creation Flags

The open() system call is documented as taking both file access mode and file creation flags (the file creation flags are O_CREAT, O_EXCL, O_NOCTTY, and O_TRUNC). fuse.h documents that O_CREAT and O_EXCL are not passed to your open() function, and further that O_TRUNC is not passed by default.

The reason for this turns out to be that FUSE handles these flags internally, and modifies which of your functions are called depending on their status, and on the results of a call to your getattr() (your getattr() is always called before the file is opened). If those flags are set, the call is handled as follows:

O_CREAT
If the file didn't previously exist, your create() function is called instead of your open() function (if it did exist, then your open() is called). After the call to your create() function, your fgetattr() function is called, though I haven't been able to determine why. One possible use is that you could use this to modify the semantics of creating a file that you yourself don't have access to (note that the standard semantics will only apply the file access mode you specify to subsequent open()s).

If the file did not exist, and the flag is not set, FUSE only calls your getattr() function (so neither your create() nor your open() function is called in this case).

O_EXCL
The behavior of this flag is only defined when O_CREAT is also specified. If the file did not previously exist your create() and fgetatter()functions are called; if it did, FUSE returns failure after the getattr() call (so neither your open() nor your create() is called in this case).

O_NOCTTY
So far as I've been able to determine, this flag is simply discarded.

O_TRUNC
Handling of this flag is determined by whether or not the filesystem is mounted with the -o atomic_o_trunc flag. If not, then FUSE will call your truncate() function before calling your open(). If the atomic_o_trunc option was set, the flag is passed to your open() function instead (note that this means I don't have any code that explicitly handles the flag: if it gets passed to bb_open(), I just pass it along to open().

Security Considerations and Race Conditions


Last modified: Sat Jan 1 21:45:06 MST 2011