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.
  1. download fuse-1.2, unzip and untar it.
  2. cd to fuse-1.2 and type
    1. ./configure
    2. make
    3. 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:
  1. It is possible to write a kernel module so things like inodes and directories run your functions instead of the standard ones
  2. 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: Free counter and web stats