macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

MacFUSE: New Frontiers in File Systems
Pages: 1, 2

read

Next, we'll implement the read function so that users can read from our file system.

static int
hello_read(const char *path, char *buf, size_t size, off_t offset,
           struct fuse_file_info *fi)

When we get a read call, we need to do what the parameters tell us to do. The path parameter gives the name of the file, buf is a buffer to put bytes into, size gives the number of bytes to return, and offset lets the read begin at a specific byte offset within the file.

Here's the first part of the read code:

{
    if (strcmp(path, file_path) != 0) {
        return -ENOENT;
    }

We first ensure that we're reading from our one and only file, else we return an error.

Next, we check the number of bytes to be read:

    if (offset >= file_size) { /* Trying to read past the end of file. */
        return 0;
    }

Here we test whether we're trying to read at an offset that's greater than the number of bytes in the file. If so, there are no bytes to return, but also no error, so we return 0 to indicate that no bytes were read.

The next test makes sure we're not going to read more bytes than are available:

    if (offset + size > file_size) { /* Trim the read to the file size. */
        size = file_size - offset;
    }

This code checks to see whether reading the given number of bytes from the desired offset will take us past the end of the file. If so, we modify the number of bytes to be read so that we read to the end of file and no further.

Now we're ready to read the file:

    memcpy(buf, file_content + offset, size); /* Provide the content. */

Finally, we read the requested bytes. We use memcpy() to move bytes into the buffer from the file, starting at the given offset location in the file.

    return size;
}

And we're done, returning the number of bytes read.

getattr

The last operation we need to implement is getattr, which simply returns the attributes of a file or directory:

static int
hello_getattr(const char *path, struct stat *stbuf)

We start by zeroing out the buffer we've been given that will hold the attributes structure:

memset(stbuf, 0, sizeof(struct stat));

This prevents uninitialized memory from ruining our day.

Next, we fill in the attributes structure, pretty much by brute force. There are only two possible entities we can be asked about: the root directory and the L file. We'll deal with the root directory first:

if (strcmp(path, "/") == 0) { /* The root directory of our file system. */
        stbuf->st_mode = S_IFDIR | 0755;
        stbuf->st_nlink = 3;

After learning that the first parameter is the root directory (it's just a slash), we set certain fields of the incoming stat structure. Specifically, we set the st_mode field to S_IFDIR | 0755, which specifies that this is a directory with its Unix permissions being 0755 (or "rwxr-xr-x"). We set the st_nlink field to 3. For a directory, the st_nlink field should be set to the total number of entries within the directory. Here, we say 3 because besides our hello.txt file, which accounts for one entry, we technically also have the "dot" and "dot-dot" entries, for a total of 3. If you think that's strange, well, that's just Unix convention.

The only other legal case is hello.txt. Let's handle that one now.

   } else if (strcmp(path, file_path) == 0) { /* The only file we have. */
        stbuf->st_mode = S_IFREG | 0444;
        stbuf->st_nlink = 1;
        stbuf->st_size = file_size;

First, we make sure the path parameter is hello.txt. If it is, we return S_IFREG | 0444 for the st_mode field (regular file, permissions being 0444 or "r--r--r--"), 1 for the st_nlink field, and our file_size constant as the size. Note that in the case of a regular file, the st_nlink field should contain the number of hard links to the file. Here, we don't even implement hard links in our file system, so we have to say 1 as the only possible sane value.

Because those are the only files we have, we need to return an error for any other filenames we might somehow be asked about:

} else { /* We reject everything else. */
        return -ENOENT;
    }

Otherwise, all is well, so we return 0, and we're done:

    return 0;
}

And the Rest

Here's the final bit of code for our HelloWorld file system. First is a structure that points to our file system's operations (note that we only enumerate the operations we implement):

static struct fuse_operations hello_filesystem_operations = {
    .getattr = hello_getattr, /* To provide size, permissions, etc. */
    .open    = hello_open,    /* To enforce read-only access.       */
    .read    = hello_read,    /* To provide file content.           */
    .readdir = hello_readdir, /* To provide directory listing.      */
};

We kick things off with our main function, which takes the operations structure as a parameter:

int
main(int argc, char **argv)
{
    return fuse_main(argc, argv, &hello_filesystem_operations, NULL);
}

That's it. We've implemented an actual file system in very little code. Although our file system is just a demo that doesn't do anything useful, it's real and it works, and its simplicity has been known to make kernel extension programmers weep with joy.

Trying It Out

OK, enough looking at source: it's time to actually use this stuff. The first step is to get and install MacFUSE. This part is very easy: there's even a true Mac installer available on the MacFUSE project site's download section. You should download the latest version of the "MacFUSE Core Installer Package" from the aforementioned page, and run the installer to get MacFUSE up and running.

Adding our HelloWorldFS requires a little more work, because we have to build it. First, grab the hellofs.c source by going to http://code.google.com/p/macfuse/wiki/HELLOWORLDFS and copying the source, then pasting it into a text file named hellofs.c. Then, assuming you have Apple's Developer Tools (Xcode) installed (version 2.4.x), compile it like this from the Terminal command line:

$ gcc -o hellofs hellofs.c -lfuse

That's it! Now we're ready to create a volume and mount it with HelloWorldFS:

$ mkdir /tmp/hello
$ ./hellofs /tmp/hello -oping_diskarb,volname=Hello

Note that we're specifying a couple of options when we mount the volume. The ping_diskarb option causes the volume to appear in the Finder, and the volname option gives the name to use for the volume.

After executing these commands, you should see the volume Hello in the Finder. If you look at the volume, it contains one file: hello.txt. You can double-click hello.txt to open it in TextEdit and see that it contains the text "Hello World!". You can even edit the text, but if you try to save the document, TextEdit complains, because HelloWorldFS is a read-only file system. (Of course, you can save it to a regular volume.)

When you're done having fun with HelloWorldFS, you can unmount the volume in any of the usual ways from the Finder, or like this from Terminal:

$ umount /tmp/hello

MacFUSE Belongs to You

Now that MacFUSE has been released (although it's not quite final quality yet), you can start using it on your own Mac. You can begin with the MacFUSE Quicker Start Guide and the Downloads page. There are over a hundred FUSE file systems out there for Linux, and as long as an existing FUSE file system doesn't use Linux-specific functionality, it should work with MacFUSE. Several popular file systems have in fact been tested with MacFUSE. Two of the most popular are ntfs-3g, a read/write version of NTFS, and sshfs, which lets you access a remote computer's storage as a file system, assuming you have an SSH connection to that computer.

Because MacFUSE makes file system coding much easier, you can think very creatively about what kinds of data might be represented as useful file systems. For example, Greg Miller created SpotlightFS, a file system that represents Spotlight queries as folders in a file system, with the query results appearing as files in folders. For example, you can create a folder named "Brian Wilson," and the folder contents will be all files that Spotlight finds when you send it the same "Brian Wilson" query. In fact, you can even use complex Spotlight queries, such as this:

   kMDItemKind == "*PDF*" && kMDItemAuthors == "Scott"w && kMDItemNumberOfPages > 1

And because SpotlightFS is a real file system, the folders and files on a SpotlightFS volume are real as well, and can be manipulated with file-handling applications (such as the Finder) and by file I/O code that you write. SpotlightFS is just an example of what you can do with MacFUSE. You can use your own creativity to think up other clever and useful file systems that can be brought to life with FUSE. Now go out and create something great!

More Information

To keep up with the pulse of MacFUSE, or to help out on the project, see the project home page. For fun and inspiration, be sure to check out this video that shows several MacFUSE tech demos in action.

Late Breaking News

The MacFUSE project now includes an Objective-C library that supplies a template MacFUSE file system. To use it, all you have to do is implement the particular methods you're interested in, such as open, read, write, and so on. This library handles details like volume icons and resource forks as well. You can find the Objective-C library and an example here: http://macfuse.googlecode.com/svn/trunk/filesystems-objc/.

Scott Knaster is a technical writer on the Mac team at Google.


Return to the Mac DevCenter.