struct fuse_operations
A FUSE filesystem is a program that listens on a socket for file
operations to perform, and performs them. The FUSE library
(libfuse
) provides the communication with the socket, and
passes the requests on to your code. It accomplishes this by using a
"callback" mechanism.
The callbacks are a set of functions you write to implement the
file operations, and a struct fuse_operations
containing pointers to them.
In the case of BBFS, the callback struct is named
bb_oper
. There are a total of 34 file operations
defined in bbfs.c
with pointers in bb_oper
.
The initialization uses a syntax that not everyone is familiar with;
looking at a part of the initialization of the struct
we
see
struct fuse_operations bb_oper = {
.getattr = bb_getattr,
.readlink = bb_readlink,
.open = bb_open,
.read = bb_read
};
(this isn't the complete struct, of course — just enough to get
a sense of what's going on)
This indicates that bb_oper.getattr
points to
bb_getattr()
, bb_oper.readlink
points to
bb_readlink()
, bb_oper.open
points to
bb_open()
, and bb_oper.read
points to
bb_read()
. Each of these functions is my
re-implementation of the corresponding filesystem function: when a
user program calls read()
, my bb_read()
function ends up getting called. In general, what all of my
reimplementations do is to log some information about the call,
and then call the original system implementation of the operation on
the underlyng filesystem.
Let's take a look at two of these in particular:
bb_open()
(my re-implementation of open()
,
and bb_read()
(my re-implementation of
read()
.
Here's bb_open()
:
int bb_open(const char *path, struct fuse_file_info *fi)
{
int retstat = 0;
int fd;
char fpath[PATH_MAX];
bb_fullpath(fpath, path);
log_msg("bb_open(fpath\"%s\", fi=0x%08x)\n",
fpath, (int) fi);
fd = open(fpath, fi->flags);
if (fd < 0)
retstat = bb_error("bb_open open");
fi->fh = fd;
log_fi(fi);
return retstat;
}
When the function is called, it is passed two parameters: a
file path (which is relative to the root of the mounted file
system), and a pointer to a
struct fuse_file_info
which is used to maintain
information about the file.
bb_open()
starts by translating the relative path it was given
to a full path in the underlying filesystem using my
bb_fullpath()
function. It then logs the full path, and
the address of the fi
pointer. It passes the call on
down to the underlying fileystem, and sees if it was successful. If
it was, it stores away the file descriptor returned by
open()
(so I'll be able to use it later), and returns 0.
If it failed, it returns -errno
. About the return value:
-i
, a -1 will be returned to the caller and
errno
is set to i
. My
bb_error()
function looks up errno
as set
by the system open()
call, logs the error, and returns
-errno
to this function so I can pass it to the user.
Notice that FUSE performs some translations. The
open()
system call is documented as returning a file
descriptor (behavior I'm depending on), not 0 — so when my
return is passed to the original caller, FUSE recognizes that I sent a
0 and returns an appropriate file descriptor (not necessarily the same
one I got from my call to open()
!). Meanwhile, I've got
the underlying file open, and I've got its file descriptor in
fi
. Future calls to my code will include this pointer, so
I'll be able to get the file descriptor and work with it. So... the
user program has an open file in the mounted filesystem, and a file
descriptor that it is keeping track of. Whenever that program tries
to do anything with that file descriptor, the operation is intercepted
by the kernel and sent to the bbfs
program. Within my
program, I also have a file open in the underlying directory, and a
file descriptor. When the operation is sent to my program, I'll log
it and then perform the same operation on my file.
To make this concrete, let's take a look at bb_read()
:
int bb_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
int retstat = 0;
log_msg("bb_read(path=\"%s\", buf=0x%08x, size=%d, offset=%lld, fi=0x%08x)\n",
path, (int) buf, size, offset, (int) fi);
retstat = pread(fi->fh, buf, size, offset);
if (retstat < 0)
retstat = bb_error("bb_read read");
return retstat;
}
This function allows us to read data from some specified offset from
the beginning of a file (so it corresponds more directly to the
pread()
function than to read()
).
The main thing to point out about this function is that I use my file
descriptor, which I put in fi
when I opened the file, to
read it. Also, if I get a non-error return from pread()
,
I pass this value up to the caller. In this case FUSE
doesn't perform any translations, it just returns the value I gave
it. To return an error, I use the same technique as in
bb_open()
.
Next: Parsing the Command Line and Initializing FUSE