Note: Be sure to read the Gotchas list before starting your code, and refer back to it frequently when you run into troubles.
passthrough.c
or
passthrough_fh.c
(the latter implements file handles, so it's
a better choice if you're developing a complex filesystem, but the
file handles definitely make the code harder to understand). To
compile either of those you'll also need passthrough_helpers.h. 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 nonnegative integer for
success, or a negative value selected from
/usr/include/asm-generic/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, struct fuse_config *cfg)
fuse_conn_info
structure gives information
about what features are supported by FUSE, and can also be used
to request certain capabilities.
The fuse_config
structure describes further options; the reason for having
two different option arguments is historical. For full
documentation, refer to the commentary in
/usr/include/fuse3/fuse_common.h
(for
fuse_conn_info
) and
/usr/include/fuse3/fuse.h
(for
fuse_config
).
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, struct fuse_file_info *fi)
stat
(2) manual
page. For the
given pathname, this should fill in the elements of the
"stat" structure. If a field is meaningless or
semi-meaningless (e.g., 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
.
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)
default_permissions
option is enabled. If
opendir
sets a file handle in
fi->fh
, it will be passed to
readdir
, releasedir
, and
fsyncdir
.
readdir(const char *path,
void *buf, fuse_fill_dir_t filler, off_t offset, struct
fuse_file_info *fi, enum fuse_readdir_flags flags)
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 "Readdir 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)
mknod
(2) for most details.
This function is rarely needed,
since it's uncommon to make these objects inside
special-purpose filesystems.
create(const char *path, mode_t
mode, struct fuse_file_info *info)
mknod
. I implemented it in
two lines by passing the call off to mknod
and
adding S_IFREG
to the mode
, and
then calling open
. But if you don't
implement it, Fuse will do the same.
mkdir(const char *path,
mode_t mode)
mode
. See
mkdir
(2) for details. This function is needed for any
reasonable read/write filesystem. Note: the
mode
argument might or might not contain type
bits; use mode | S_IFDIR
if you need those
bits to be correct.
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)
rmdir
(2) for details.
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, unsigned int flags)
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. If the target already exists and
flags
is RENAME_NOREPLACE
, the
rename should return an error (presumably
EEXIST
). If flags
is
RENAME_EXCHANGE
, both files must exist and
they must be atomically swapped. Otherwise, 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, struct fuse_file_info *fi)
mode
should be examined. See chmod
(2) for
details. If the file is not currently open,
fi
will be NULL
; if it
is open fi
may still be
NULL
.
chown(const char *path, uid_t uid,
gid_t gid, struct fuse_file_info *fi)
chown
(2) for details. As with
chmod
(q.v.) fi
might be
NULL
. If the capability
FUSE_CAP_HANDLE_KILLPRIV
is not
enabled, this function must clear the setuid and setgid
bits.
NOTE: if the FUSE program is passing calls through to an underlying filesystem (as opposed to a backing device), FUSE doesn't deal particularly well with file ownership, since it usually runs as an unprivileged user and this call is restricted to the superuser. It's often easier to pretend that all files are owned by the user who mounted the filesystem, and to skip implementing this function.
truncate(const
char *path, off_t size, struct fuse_file_info *fi)
size
bytes long. See truncate
(2) for
details. This call is required for read/write
filesystems, because recreating a file will first truncate
it. If the file is not currently open, fi
will be NULL
; if it is open fi
might still be NULL
. If the capability
FUSE_CAP_HANDLE_KILLPRIV
is not
enabled, this function must clear the setuid and setgid
bits.
utimens(const char *path, const
struct timespec ts[2], struct fuse_file_info *fi)
utimensat
(2) for full details. Note that the time
specifications are allowed to have certain special values;
however, I don't know if FUSE functions have to support
them. This function isn't necessary but is nice to have
in a fully functional filesystem.
open(const char *path,
struct fuse_file_info *fi)
fi->flags
.
If you aren't using file handles, this
function should just check for existence and permissions,
perform truncation (see below),
and return either success or an error code. If you use
file handles, you should also allocate any necessary
structures and set 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.
Some important rules about open
:
O_TRUNC
flag is set in
fi->flags
, the file should be
truncated to a length of zero as part of opening
it. Strictly speaking, O_TRUNC
should only be honored when the open mode is
either O_RDWR
or
O_WRONLY
, but the kernel will never
generate those combinations.
O_CREAT
,
O_EXCL
, and O_NOCTTY
)
flags will be filtered out because they are
handled by the kernel.
O_RDONLY
, O_WRONLY
,
O_RDWR
, O_EXEC
,
O_SEARCH
) to check permissions. If
the filesystem was run with the
-o default_permissions
option, the
kernel will do the check for you and you can
safely skip it.
O_WRONLY
. The
filesystem should be prepared to handle this.
O_APPEND
flag and ensure
that each write is appending to the end of the file.
O_APPEND
. However, unless all
changes to the file come through the kernel this
will not work reliably. The filesystem should
thus either ignore the O_APPEND
flag
(and let the kernel handle it), or return an error
indicating that reliable O_APPEND
is
not available).
fi->fh
and use it in all other
file operations (read, write, flush, release,
fsync). Alternatively, the filesystem may
implement stateless file I/O and not store
anything in fi->fh
.
fi
:
direct_io
indicates that
direct I/O (see open
(2))
should be used on this file.
keep_cache
should be set to
1 if it's OK for the kernel to keep cached
data even after the file is closed. If
it's 0, then the kernel will invalidate its
internal cache upon close.
nonseekable
can be to 1 to
indicate that lseek
is not
supported on this file.
no_flush
indicates that it
is not necessary to flush dirty cache
contents when the file is closed.
If you do not wish to give any special handling to
open
(e.g., if you are writing a stateless
filesystem) then you should return ENOSYS
and
also make sure that init
sets
FUSE_CAP_NO_OPEN_SUPPORT
in fuse_conn_info
. In this case the "error"
of ENOSYS
will be treated as success.
See the comments in
/usr/include/fuse3/fuse_common.h
for more information.
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.
Except when EOF or an error is encountered, or when the
direct_io
option is enabled, read
must transfer
exactly the number of bytes requested.
Required for nearly 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
statvfs
(2)
for a description of the structure contents. Usually, you
can ignore the path
. Not required, but handy
for read/write filesystems since this is how programs like
df
determine the free space. FUSE ignores the
f_favail
, f_fsid
, and
f_flag
fields.
release(const char *path, struct
fuse_file_info *fi)
close
(2)
is related. Release
is called when FUSE is
completely done with a file, i.e. when the last
open
of the file is closed. At that point,
you can free
up any temporarily allocated data structures.
There is exactly one
release
per open
.
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)
close
so that the filesystem
has a chance to flush dirty data. Notes:
flush
are not
guaranteed to be propagated back to the user.
flush
calls for each open. It is not possible to
associate flush
es with
open
s. That's what
release
is for.
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)
bmap
(9). If the
filesystem
is backed by a block device, it converts
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)
setxattr
(2).
getxattr(const char *path, const
char *name, char *value, size_t size)
getxattr
(2).
listxattr(const char *path,
const char *list, size_t size)
listxattr
(2).
removexattr(const char *path,
const char *name)
removexattr
(2).
ioctl(const char *path, unsigned int cmd,
void *arg, struct fuse_file_info *fi, unsigned int flags,
void *data)
ioctl
(2) system call. As such, almost
everything is up to the filesystem. On a 64-bit machine,
FUSE_IOCTL_COMPAT
will be set in
flags
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.
If FUSE_IOCTL_DIR
is set in
flags
, then the fuse_file_info
or path
refers to a directory.
Note: the application's
request
parameter to ioctl
(2)
will be truncated to 32 bits when passing cmd
to the filesystem.
poll(const char *path, struct
fuse_file_info *fi, struct fuse_pollhandle *ph, unsigned
*reventsp);
select
(2) system call). If 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.
write_buf(const char *path,
struct fuse_bufvec *buf, off_t off, struct fuse_file_info
*fi)
write
, except that
buf
is a structure that describes multiple
areas of memory. If implemented, this function allows the
client to write from a number of different areas of memory
(called a "gather" write) in a single operation.
read_buf(const char *path,
struct fuse_bufvec *buf, off_t off, struct fuse_file_info
*fi)
read
, except that
buf
is a structure that describes multiple
areas of memory. If implemented, this function allows the
client to read to a number of different areas of memory
(called a "scatter" read) in a single operation.
fallocate(const char *path, int
mode, off_t offset, off_t len, struct fuse_file_info *fi)
fallocate
(2) manual page for descriptions of
the arguments.
copy_file_range(const
char *path_in, struct fuse_file_info *fi_in, off_t
offset_in, const char *path_out, struct fuse_file_info
*fi_out, off_t offset_out, size_t size, int flags)
copy_file_range
(2) for details.
lseek(const char *path, off_t
off, int whence, struct fuse_file_info *fi)
lseek
(2)
for details. Note that this function only needs to
support SEEK_DATA
and SEEK_HOLE
;
the kernel handles the other values of whence
internally.
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. If
the flags
argument to readdir
contains the
FUSE_READDIR_PLUS
bit, it is best to pass
FUSE_FILL_DIR_PLUS
when calling the filler function and
set all fields in stbuf
, below. (But if you don't do so,
your filesystem will still work; it'll just be a bit slower on very
large directories.)
readdir
can operate in one of two modes. In mode 1, the
entire directory is returned by a single readdir
call.
In that case, you should call the filler function in a loop, always
passing an offset
argument of 0, until you have processed
all directory entries. The filler will return 1 only if an error
happens.
In mode 2, the directory is read piecewise through multiple
readdir
calls. Each call to the filler should supply a
nonzero offset
. The filler will return 1 when it can't
accept any more data, and a later call to readdir
with
the same offset
will pick up where the previous one left
off.
In both modes, directory entries are returned to the user by calling the filler function. Its prototype is:
int fuse_fill_dir_t (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags);
The general plan for a complete and correct mode-2 readdir
is:
offset
(see below).
struct stat
that describes
the file as for getattr
(but unless the
flags
passed to the filler function contain
FUSE_FILL_DIR_PLUS
, 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),
the offset of the next directory entry, and the flags.
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: in mode 2, 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 that is being accessed by several processes, 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 $ fusermount3 -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 fusermount3
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 -f -d testdir
The -s
and -f
switches mean
"single-threaded" and "run in foreground", respectively, which makes gdb
behave in a much friendlier fashion. The -d
switch means
"debug", which causes FUSE to report the details of every operation
it's doing. (The
-d
switch actually implies -f
; I included it
above because sometimes the debugging output clutters things, in which
case you need -s -f
.)
Now, in window #2 you can do:
$ ls testdir ... # Other trial commands $ fusermount3 -u testdir # Or on Mac OS, umount testdir
IMPORTANT: You need to do the fusermount3
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 -s -h .
to see them all.
The important ones are:
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.
© 2022, Geoff Kuenning
This page is maintained by Geoff Kuenning.