There is 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.
Note: Be sure to read the Gotchas list before starting your code, and refer back to it frequently when you run into troubles.
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.
Many 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
that structure's "fh
" element, 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
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 only 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. See umask(2).
The following is a brief description of all the API functions you can
create in a FUSE filesystem. Note that many are not required,
especially if you are implementing a partial filesystem like the ones
in these assignments; especially important functions are highlighted
in yellow. However, I have tried to provide
documentation for all the functions 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
"Init Function" below for more information).
The return value from init
will be made
available to all file
operations in the private_data
field of
fuse_context
. It
is also passed as a parameter to the destroy() method.
(Note: see the warning under Other
Options below, regarding relative pathnames.)
void destroy(void*
private_data)
private_data
comes from the return value of
init
. This function should free any data
that init
allocated with malloc
,
and should make sure everything related to the filesystem
has been flushed to persistent
storage.
getattr(const char* path,
struct stat* stbuf)
st_rdev
) then it
should be set to 0 or given a "reasonable" value. This
call is pretty much required for a usable filesystem.
NOTE: st_dev
and
st_ino
are often used by application programs
to decide whether two file names are aliases for the same
physical storage, so setting them to 0 isn't ideal.
fgetattr(const char* path, struct
stat* stbuf, struct fuse_file_info *fi)
getattr
, but called when fgetattr(2) is
invoked by the user program. Usually it can just call
getattr
.
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 strongly
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 in "Readir Function" 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)
mode
satisfies
S_ISREG
then mknod
should enter
an empty file into its parent directory.
For other file types,
this function is rarely needed,
since it's uncommon to make these objects inside
special-purpose filesystems.
In my implementation, if S_ISREG(mode)
is
false then I just return -EACCESS to reject the call.
create(const char* path, mode_t mode)
mknod
. I implemented it in
one line by passing the call off to mknod
and
adding S_IFREG
to the mode
.
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. This function is needed for almost any
read/write filesystem.
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. Also note that if the target
already exists, it must be atomically replaced; many
application programs depend on this behavior.
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. Usually this can just call
truncate
.
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,
const 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 fsync
by calling
fdatasync(2) on that file, which will flush too much data
(slowing performance) but achieve the desired guarantee.
A higher-performance option would be to open the backing
file with O_DSYNC
and manage a private cache,
but this approach is too complex for most Fuse filesystems.
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 number. 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 defined.
getxattr(const char* path, const
char* name, char* value, size_t size)
HAVE_SETXATTR
defined.
listxattr(const char* path,
const char* list, size_t size)
HAVE_SETXATTR
is defined.
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 data
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 is 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. The meaning
of "offset" is up to you (see below).
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; if you want it to be an index into
an array, that's fine.
An important note: you must support the offset
argument. The way Fuse detects the end of the directory is by calling
readdir
with a nonzero offset
and getting no
results back. So if you don't pay attention to the
offset
that is given to you, you'll never terminate.
Also, you should never provide a zero offset
, since Fuse
will give that back to you and you'll then start over at the front of
the directory!
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.
Here's some sample pseudocode:
for (i = offset; i < max_dir_entries; i++) if (entry[i].valid) if (filler(buf, entry[i].name, NULL, i + 1)) return 0; return 0;The above code makes several assumptions and (in general) won't work well with a multi-block directory, but it should give you an outline of how things should work.
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, you could simply delete the
relevant lines—but see Gotchas below for
why this is a bad idea):
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 OSXFUSE; Linux users should be able to find FUSE as part of their distribution.
Compiling a FUSE program requires a slightly complicated command:
gcc -g -Og `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.
To run a FUSE program, you'll need two windows and an empty 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 # Or on Mac OS, umount 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
lying around rather than making it and removing it every time. (Most
production systems have a number of empty directories hanging around
just in case
you want to mount on top of them—often, either /mnt
or
something 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 # Or on Mac OS, umount testdir
IMPORTANT: You need to do the fusermount
even if your program crashes or you abort it. Otherwise you'll get a
confusing "Transport endpoint not connected" message the next time you
try to mount the test system.
If you have used gdb to set breakpoints, then 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).
There are several common problems that plague programmers new to Fuse. This is a partial list:
-s
switch to avoid this problem.
getattr
getattr
like crazy. Implement it
first, or nothing will work.
truncate
system call to make
writes work correctly.
chdir
is suppressed when you run with the -f switch, so your
code might appear to work fine under the debugger.
So if your code creates a scratch file named simply
"scratch", with -f you'll wind up with "./scratch" but
without -f it'll try to make "/scratch", which you don't
have permission to do.
To
avoid the problem, either (a) use absolute pathnames for
every file name hardwired into your code (e.g.,
"/home/me/fuse/scratch"), or
(b) record your current working directory by calling
get_current_dir_name
before you invoke
fuse_main
, and then convert relative
pathnames into corresponding absolute ones. Obviously,
(b) is the preferred approach.
printf
/fprintf
debugging
code will only work if you run with the -f
switch. Otherwise, Fuse disconnects stdout
and stderr
, and all your output will just
disappear. So if you use printf
for debugging,
be sure to run with -f
or -d
.
(Or open a log file with an absolute pathname and use
fprintf
to print to it.)
fuse_operations
struct,
Fuse will silently generate a failure when it is called,
and you'll never find out that you need to write it.
Instead, write every unimplemented function as a stub that
prints a message to stderr
and returns an
error code (ENOSYS is preferred). When you see the
message, you'll know what extra functions you need to
write.
© 2020, Geoff Kuenning
This page is maintained by Geoff Kuenning.