Getting started, running the fusexmp example.
How FUSE works: 3 layers
Adding more VFS functionality to FUSE
by Alisa Neeman
aneeman[at]cse[dot]ucsc[dot]edu
Getting started with FUSE and the example, fusexmp
So first to install.
- download fuse-1.2, unzip and untar it.
- cd to fuse-1.2 and type
- ./configure
- make
- make install
When you start FUSE, you specify a mountpoint. What happens is your own
root directory ( / ), is copied into the mountpoint you specify.
For example, you could start fusexmp as follows, if you install
fuse-1.2
in / and
cd to /fuse-1.2/example:
./fusexmp
/fuse-1.2/example/
-d
NOTE: It seems to break if you mount FUSE in a directory
not owned by ROOT. If it breaks you can always reboot.
Also, you won't be able to use the terminal you started fusexmp in.
-d gives verbose behavior so you know fusexmp is working.
Open a new terminal and type
ls -al
/fuse-1.2/example
You should see the contents of your root directory.
To turn off fusexmp type:
fusermount -u
/fuse-1.2/example
For a series of
compiles and reinstalls, you have to take the fuse module out of the
kernel.
You can do this using the command
/sbin/rmmod
fuse
Then the next fusexmp will install your latest module.
Here's what I've been able to figure out about
how FUSE works. FUSE is
built in 3 levels: a kernel module that captures VFS calls, an
intermediate level that replies to the module and resolves paths and
other issues, and your own user level implementation that can behave in
unique ways.
At the kernel level: There are 2 linux things that
help us:
- It is possible to write a kernel module so things like inodes and
directories run your functions instead of the standard ones
- There is a thing called proc that gives us I/O from user space to
kernel space via a file that lives in memory.
So, to start, fuse has a kernel module that implements the standard VFS
functions and manages to get its functions called. For example, there
is a
struct like this in kernel/dir.c:
static
struct
inode_operations fuse_dir_inode_operations =
{
.lookup = fuse_lookup,
.create = fuse_create,
.mknod = fuse_mknod,
.mkdir = fuse_mkdir,
.symlink = fuse_symlink,
.setattr = fuse_setattr,
:
:
};
then what happens in the function fuse_setattr, for example, is that a
data structure with all necessary info is created. This includes a code
for which function we would like called at the user level (e.g.
FUSE_SETATTR).
The structure is then pushed into a proc file.
Meanwhile, the main fuse thread at the intermediate level (actually
user space) is polling the proc file
for
requests. A request can carry two chunks of data, an in structure (in from the kernel),
and an out structure. The in structure carries the type of
the request and a blob of bytes that is the parameters for the function
call. The out structure
carries a return value and blob. This intermediate level (lib/fuse.c)
interprets the command and parses the blob data. Things like inode
numbers are converted to paths.
So any stat call
does some strange
things. FUSE has its own internalrepresentation of files. It has two hash tables, one
for names and one for inos. Inos are unsigned
longs, under the covers.
If you do a
rmdir, for instance, the name and ino are removed for the thing you
are removing. This is because FUSE is imitating the dcache in user
space.
A node in the hash table has a couple of the same entries as a real
Linux inode object, namely
rdev (real device node?)
mode (access permissions.?)
ino (ino number)
and the additional most mysterious, version, and the non-inode field,
name.
Once paths are resolved, the intermediate fuse layer makes the
appropriate user level
call with parameters as strings rather than inodes or dentries.
8
Steps for adding additional VFS
functionality to FUSE:
1. in /kernel/dir.c add your function to the structures
inode_operations
fuse_file_inode_operations
inode_operations
fuse_symlink_inode_operations
inode_operations
fuse_dir_inode_operations
e.g.
.setxattr
=
fuse_setxattr
2. Create your own op code so your new command can be recognized by the
intermediate level
in /include/linux/fuse.h:
enum fuse_opcode {
:
FUSE_SETXATTR = 22
};
3. Define a structure in /include/linux/fuse.h so that the parameters
can be passed to the intermediate level and interpreted easily. Passing
pointers doesn't seem to work so you have to copy things like strings.
struct
fuse_setxattr_in {
unsigned int x_value_size;
unsigned int x_flags;
char x_name[FUSE_NAME_MAX];
char x_value[FUSE_NAME_MAX];
};
4. write your function in kernel/dir.c using the VFS prototype
static
int
fuse_setxattr(struct dentry * d_ptr,
const char * x_name, const void * x_value, size_t x_size, int
x_flags)
{
struct fuse_conn *fc;
struct fuse_setxattr_in inargs; /* parameters to send */
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
in.h.opcode = FUSE_SETXATTR;
in.h.ino = d_ptr->d_inode->i_ino;
in.numargs = 1;
in.args[0].size = sizeof( inargs ); /* size
offuse_setattr_in*/
in.args[0].value = (void *) &inargs;
/* save string length of parameter */
inargs.x_value_size = x_size + 1;
if( ( strlen(x_name) + 1 ) > FUSE_NAME_MAX ||
inargs.x_value_size > FUSE_NAME_MAX )
return -ENAMETOOLONG;
inargs.x_flags = x_flags;
/* clear out garbage */
memset( inargs.x_name, 0, FUSE_NAME_MAX );
memset( inargs.x_value, 0, FUSE_NAME_MAX );
/* copy names into buffers */
strcpy( inargs.x_name, x_name );
strcpy( inargs.x_value, x_value );
/* send buffers and request to user level */
fc = INO_FC( d_ptr->d_inode );
request_send( fc, &in, &out );
if( out.h.error )
return out.h.error;
return 0;
}
5. Add the debug string in lib/fuse.c
static
const char *opname(enum fuse_opcode opcode) {
:
case
FUSE_SETXATTR: return "SETXATTR";
}
6. Add a
function call in the process_command function in lib/fuse.c
void
__fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd) {
:
case FUSE_SETXATTR:
do_setxattr(f, in, inarg);
:
}
7. Write the call function in lib/fuse.c
static void
do_setxattr(struct fuse *f, struct fuse_in_header *in, void *arg)
{
struct
fuse_setxattr_in * x_in;
char *
path;
/*
change blob into my struct described in /include/linux/fuse.h */
x_in
= ( struct fuse_setxattr_in * ) arg;
/*
resolve path based on inode # using local function*/
path =
get_path( f, in->ino );
/* if
function exists in user implementation,
* call it with our parameters from the kernel */
if(
f->op.setxattr )
f->op.setxattr(path, x_in->x_name, x_in->x_value,
x_in->x_value_size,
x_in->x_flags);
}
8. Add the user level prototype to include/fuse.h
struct
fuse_operations {
:
int (*setxattr) (const char *path, const char *name,
const char *value, size_t size, int flags);
};
9. Add the function to the user level implementation,
e.g.
example/fusexmp.c as shown in the example.
Tips:
- You can't pass a pointer from kernel to user space &
expect to be able to dereference it
- There a limit on the size of kernel variables: 1 KB is ok;
8 KB is too big, it makes the kernel panic.
- Under fedora core 2, FUSE (or the OS) expects you to return a stat64
structure instead of a stat structure. (Thanks, Niki!)