There very little FUSE documentation on the FUSE Web site. A bit more, which is unfortunately very outdated, is available from an IBM article from 2006. If you come across anything more complete or more current, I'd appreciate hearing about it so I can add a link to it from this site.
fusexmp.c
or
fusexmp_fh.c
(the latter implements file handles, so it's
a better choice if you're developing a complex filesystem). The
existing clients provide a scaffolding for you to work from, but
you'll still need to understand what all
the functions are supposed to do, and also how to compile and run your
client. That's what this Web page is for.
May of the FUSE functions are closely related to Unix system calls. Rather than repeating the full specification (especially error conditions) here, it's better for you to refer to the Unix man page. You can do this on any Unix machine with the "man" command. By convention, if I refer you to the "stat(2) system call", that means you should type "man 2 stat" to get the necessary information.
Many FUSE functions offer two ways to identify the file being operated
upon. The first, which is always available, is the
"path
" argument, which is the full pathname (relative to
the filesystem root) of the file in question. If you choose to do so,
all your functions can use that argument to locate a file.
However, pathname lookup is often a very expensive operation, so FUSE
sometimes provides you another option: a "file handle" in the
"fuse_file_info
" structure. The file handle is stored in
the "fh
" element of that structure, which is an unsigned
64-bit integer (uint64_t
) uninterpreted by FUSE. If you
choose to use it, you should set that field in your
open
,
create
, and
opendir
functions; other
functions can then use it. In many FUSE implementations, the file
handle is actually a pointer to a useful data structure, which is
typecast to an integer to keep the compiler happy. But you can make
it an index into an array, a hash key, or pretty much anything else
you choose.
For many operations, it is useful to have a relevant "context" in
which to execute them. For historical reasons, the context isn't
passed as an argument; instead you must call
fuse_get_context
with no argument, which returns a
pointer to a struct fuse_context
with the following
usable elements:
uid
gid
pid
private_data
void*
) to the private data
returned by the init
function.
umask
umask
of the process invoking the operation.
The following is a brief description of all the API functions you can
create in a FUSE filesystem. Note that many are unnecessary,
especially if you are implementing a partial filesystem like the one
in this assignment. However, I have tried to provide full
documentation here. Unless otherwise specified, all functions return
an integer 0 or a positive number for
success, or a negative value selected from errno.h
if
there is an error.
All of your functions should be named with a prefix that is closely
related to your filesystem name. For example, in an SSH filesystem
you should use ssh_getattr
, ssh_read
, etc.
void* init(struct fuse_conn_info *conn)
fuse_conn_info
structure gives information
about what features are supported by FUSE, and can be used
to request certain capabilities (see
below for more information).
The return value of this function is available to all file
operations in the private_data
field of
fuse_context
. It
is also passed as a parameter to the destroy() method.
void destroy(void* private_data)
private_data
comes from the return value of
init
.
getattr(const char* path, struct
stat* stbuf)
st_ino
) then it
should be set to 0 or given a "reasonable" value. This
call is pretty much required for a usable filesystem.
fgetattr(const char* path, struct
stat* stbuf)
getattr
, but called when fgetattr(2) is
invoked by the user program.
access(const char* path, mask)
path
doesn't exist, -EACCESS
if the requested permission isn't available, or 0 for
success. Note that it can be called on files,
directories, or any other object that appears in the
filesystem. This call is not required but is highly
recommended.
readlink(const char* path, char*
buf, size_t size)
path
is a symbolic link, fill
buf
with its target, up to
size
. See readlink(2) for how to handle a
too-small buffer and for error codes. Not required if you
don't support symbolic links.
NOTE: Symbolic-link support requires only
readlink
and symlink
. FUSE
itself will take care of tracking symbolic links in paths,
so your path-evaluation code doesn't need to worry about it.
opendir(const char* path, struct
fuse_file_info* fi)
readdir(const char* path, void*
buf, fuse_fill_dir_t filler, off_t offset, struct
fuse_file_info* fi)
struct
dirent
) to the caller. This is one of the most
complex FUSE functions. It is related to, but not
identical to, the readdir(2) and getdents(2) system calls,
and the readdir(3) library function. Because of its
complexity, it is described separately below. Required for essentially any
filesystem, since it's what makes
ls
and a whole bunch of other things work.
mknod(const char* path, mode_t
mode, dev_t rdev)
mkdir(const char* path, mode_t mode)
mode
. See
mkdir(2) for details. This function is needed for any
reasonable read/write filesystem.
unlink(const char* path)
unlink
only deletes the data when the
last hard link is removed. See unlink(2) for
details.
rmdir(const char* path)
symlink(const char* to, const
char* from)
from
" which,
when evaluated, will lead to "to
". Not
required if you don't support symbolic links.
NOTE: Symbolic-link support requires only
readlink
and symlink
. FUSE
itself will take care of tracking symbolic links in paths,
so your path-evaluation code doesn't need to worry about it.
rename(const char* from, const
char* to)
from
" to the target "to
". Note
that the source and target don't have to be in the same
directory, so it may be necessary to move the source to an
entirely new directory. See rename(2) for full details.
link(const char* from, const char*
to)
from
" and
"to
". Hard links aren't required for a
working filesystem, and many successful filesystems don't
support them. If you do implement hard links, be
aware that they have an effect on how unlink
works. See
link(2)
for details.
chmod(const char* path, mode_t mode)
mode
should be examined. See chmod(2) for
details.
chown(const char* path, uid_t uid,
gid_t gid
truncate(const char* path, off_t size)
size
bytes long. See truncate(2) for
details. This call is required for read/write
filesystems, because recreating a file will first truncate
it.
ftruncate(const char* path,
off_t size)
truncate
, but called when ftruncate(2) is
called by the user program.
utimens(const char* path, const
struct timespec ts[2]
open(const char* path, struct
fuse_file_info* fi)
fi->fh
. In addition,
fi
has some other fields that an advanced
filesystem might find useful; see the structure definition in
fuse_common.h
for very brief commentary.
read(const char* path, char *buf,
size_t size, off_t offset, struct fuse_file_info* fi)
size
bytes from the given file into the
buffer buf
, beginning offset
bytes into the file. See read(2) for full details.
Returns the number of bytes transferred, or 0 if
offset
was at or beyond the end of the file.
Required for any sensible filesystem.
write(const char* path, char *buf,
size_t size, off_t offset, struct fuse_file_info* fi)
read
above,
except that it can't return 0.
statfs(const char* path, struct
statvfs* stbuf
path
. Not required, but handy
for read/write filesystems since this is how programs like
df
determine the free space.
release(const char* path, struct
fuse_file_info *fi)
Release
is called when FUSE is
completely done with a file; at that point, you can free
up any temporarily allocated data structures. The IBM
document claims that there is exactly one
release
per open
, but I don't
know if that is true.
releasedir(const char* path,
struct fuse_file_info *fi)
release
, except for directories.
fsync(const char* path, int
isdatasync, struct fuse_file_info* fi)
isdatasync
is nonzero, only data, not
metadata, needs to be flushed. When this call returns,
all file data should be on stable storage. Many
filesystems leave this call unimplemented, although
technically that's a Bad Thing since it risks losing data.
If you store your filesystem inside a plain file on
another filesystem, you can implement this by calling
fsync(2) on that file, which will flush too much data
(slowing performance) but achieve the desired guarantee.
fsyncdir(const char* path, int
isdatasync, struct fuse_file_info* fi)
fsync
, but for directories.
flush(const char* path, struct
fuse_file_info* fi)
flush
call for
each open
. Note: There is
no guarantee that flush
will ever be called
at all!
lock(const char* path, struct
fuse_file_info* fi, int cmd, struct flock* locks)
bmap(const char* path, size_t blocksize,
uint64_t* blockno)
blockno
from a file-relative block number to
a device-relative block. It isn't entirely clear how the
blocksize
parameter is intended to be used.
setxattr(const char* path, const
char* name, const char* value, size_t size, int flags)
HAVE_SETXATTR
is true.
getxattr(const char* path, const
char* name, char* value, size_t size)
HAVE_SETXATTR
is true.
listxattr(const char* path,
const char* list, size_t size)
HAVE_SETXATTR
is true.
ioctl(const char* path, int cmd,
void* arg, struct fuse_file_info* fi, unsigned int flags,
void* data
FUSE_IOCTL_COMPAT
will be set for 32-bit ioctls.
The size and direction of data is determined by
_IOC_*()
decoding of cmd
. For
_IOC_NONE
, data
will be
NULL
; for _IOC_WRITE
data
is being written by the user; for
_IOC_READ
it is being read, and if both are
set the data is bidirectional. In all non-NULL cases, the
area is _IOC_SIZE(cmd)
bytes in size.
poll(const char* path, struct
fuse_file_info* fi, struct fuse_pollhandle* ph, unsigned*
reventsp);
ph
is non-NULL,
when the filesystem is ready for I/O it should call
fuse_notify_poll
(possibly asynchronously)
with the specified ph
; this will clear all
pending poll
s. The callee is responsible for
destroying ph
with
fuse_pollhandle_destroy()
when
ph
is no longer needed.
The initialization function accepts a fuse_conn_info
structure, which can be used to investigate and control the system's
capabilities. The components of this structure are:
proto_major
and proto_minor
async_read
FUSE_CAP_ASYNC_READ
flag;
asynchronous reads are controlled by the
logical OR of the field and the flag. (Yes,
this is a silly hangover from the past.)
max_write
max_readahead
capable
want
The capabilities that can be requested are:
FUSE_CAP_ASYNC_READ
want
flags) and the async_read
field. If synchronous reads are chosen, Fuse will wait
for reads to complete before issuing any other requests.
FUSE_CAP_POSIX_LOCKS
lock
call.
FUSE_CAP_ATOMIC_O_TRUNC
O_TRUNC
open flag.
FUSE_CAP_EXPORT_SUPPORT
FUSE_CAP_BIG_WRITES
FUSE_CAP_DONT_MASK
umask
from being applied
to files on create operations. (Note: as far as I can
tell from examining the code, this flag isn't actually
implemented.)
The readdir
function is somewhat like read
,
in that it starts at a given offset and returns results in a
caller-supplied buffer. However, the offset not a byte offset, and
the results are a series of struct dirent
s rather than
being uninterpreted bytes. To make life easier, FUSE provides a
"filler" function that will help you put things into the buffer.
The general plan for a complete and correct readdir
is:
offset
(see below).
struct stat
that describes
the file as for getattr
(but FUSE only looks at
st_ino
and the file-type bits of
st_mode
).
filler
function with arguments of
buf
, the null-terminated filename, the address of
your struct stat
(or NULL if you have none), and
the offset of the next directory entry.
filler
returns nonzero, or if there are no
more files, return 0.
From FUSE's point of view, the offset
is an uninterpreted
off_t
(i.e., an unsigned integer). You provide an offset
when you call filler
, and it's possible that such an
offset might come back to you as an argument later. Typically, it's
simply the byte offset (within your directory layout) of the directory
entry, but it's really up to you.
It's also important to note that readdir
can return
errors in a number of instances; in particular it can return -EBADF if
the file handle is invalid, or -ENOENT if you use the
path
argument and the path doesn't exist.
The lock
function is somewhat
complex. The cmd
will be one of F_GETLK
,
F_SETLK
, or F_SETLKW
. The fields in
locks
are defined in the fcntl(2) manual page; the
l_whence
field in that structure will always be
SEEK_SET
.
For checking lock ownership, the fi->owner
argument must
be used.
Contrary to what some other documentation states, the FUSE library does not appear to do anything special to help you out with locking. If you want locking to work, you will need to implement the lock function. (Persons who have more knowledge of how FUSE locking works are encouraged to contact me on this topic, since the existing documentation appears to be inaccurate.)
Once you've written your operations, you need some boilerplate.
As mentioned above, all of your functions should be named with a
sensible prefix; here I use "prefix
" to represent that.
Create a fuse_operations
struct that lists the functions
you implemented (for any unimplemented ones, simply delete the
relevant lines):
static struct fuse_operations prefix_oper = { .init = prefix_init, .destroy = prefix_destroy, .getattr = prefix_getattr, .fgetattr = prefix_fgetattr, .access = prefix_access, .readlink = prefix_readlink, .readdir = prefix_readdir, .mknod = prefix_mknod, .mkdir = prefix_mkdir, .symlink = prefix_symlink, .unlink = prefix_unlink, .rmdir = prefix_rmdir, .rename = prefix_rename, .link = prefix_link, .chmod = prefix_chmod, .chown = prefix_chown, .truncate = prefix_truncate, .ftruncate = prefix_ftruncate, .utimens = prefix_utimens, .create = prefix_create, .open = prefix_open, .read = prefix_read, .write = prefix_write, .statfs = prefix_statfs, .release = prefix_release, .opendir = prefix_opendir, .releasedir = prefix_releasedir, .fsync = prefix_fsync, .flush = prefix_flush, .fsyncdir = prefix_fsyncdir, .lock = prefix_lock, .bmap = prefix_bmap, .ioctl = prefix_ioctl, .poll = prefix_poll, #ifdef HAVE_SETXATTR .setxattr = prefix_setxattr, .getxattr = prefix_getxattr, .listxattr = prefix_listxattr, .removexattr = prefix_removexattr, #endif .flag_nullpath_ok = 0, /* See below */ };
Set flag_nullpath_ok
nonzero if your code can accept a
NULL path argument (because it gets file information from
fi->fh
) for the following operations:
fgetattr
,
flush
,
fsync
,
fsyncdir
,
ftruncate
,
lock
,
read
,
readdir
,
release
,
releasedir
, and
write
.
This will allow FUSE to run more efficiently.
Finally, since your client is actually an executable program, you need
a main
:
int main(int argc, char *argv[]) { umask(0); return fuse_main(argc, argv, &prefix_oper, NULL); }
You can do your development on any machine you choose that supports FUSE. Mac users can try macfuse; Linux users should be able to find FUSE as part of their distribution.
Compiling a FUSE program requires a slightly complicated command:
/usr/bin/gcc -g `pkg-config fuse --cflags --libs` my_hello.c -o my_helloA better approach, of course, is to use
make
. This truly
minimal Makefile
will let you type
"make foo
" for any foo.c
. You are
encouraged to use it and extend it to be more sensible.
NOTE: On Wilkes, be sure to use "/usr/bin/gcc" rather
than just "gcc". Wilkes is specially configured so that plain gcc
produces 32-bit code, but for this assignment you need 64-bit code.
To run a FUSE program, you'll need two windows and a scratch directory. You'll run your filesystem under a debugger in window #1; window #2 will be used for testing. The scratch directory is needed because you must have an empty directory on which to mount your shiny new filesystem.
The simplest (and incorrect, for our purposes) way to run a FUSE
program is to make a scratch directory and then pass that as an
argument to the program. For example, if you're running the "hello,
world" filesystem (hello.c
):
$ mkdir testdir $ ./hello testdir $ ls testdir hello $ cat testdir/hello hello, world $ fusermount -u testdir $ rmdir testdir
When you run your program this way, it automatically goes into the
background and starts serving up your filesystem. After you finish
testing, the fusermount
command unmounts your filesystem
and kills the background program.
As a practical matter, it's easier to leave testdir
hanging around rather than making it and removing it every time. Most
systems have a number of empty directories hanging around just in case
you want to mount on top of them (often, either /mnt
or
inside /mnt
).
Of course, it's unlikely that your program will work perfectly the first time, so it's better to run it under the debugger. To do that, you'll need two windows. In window #1, do:
$ mkdir testdir # if necessary $ gdb hello [gdb noise] (gdb) [set breakpoints, etc.] (gdb) run -s -d testdir
The -s
switch means "single-threaded", which makes gdb
behave in a much friendlier fashion. The -d
switch means
"debug"; in addition to printing helpful debugging output, it keeps
the program in the foreground so gdb won't lose track of it.
Now, in window #2 you can do:
$ ls testdir ... # Other trial commands $ fusermount -u testdir
IMPORTANT: You need to do the fusermount
even if your program crashes or you abort it.
If you have set breakpoints, when you do "ls testdir", your window may seem to hang. That's OK; just go over to the gdb window and step through your code. When it returns a result, your test window will come alive again.
Your new FUSE client has a lot of options. The simplest invocation
just specifies a mount point. For example, if your client is named
fuse_client
and you're mounting on "~/foo", use:
./fuse_client ~/foo
There are tons of switches available; use ./fuse_client
-h
to see them all. The important ones are:
norellinks
is given).
© 2010, Geoff Kuenning
This page is maintained by Geoff Kuenning.