Tutorial/Guia sobre FUSE

26 06 2009

Tras un tiempo de inactividad debido a exámenes, vuelvo con un pequeño tutorial de FUSE, que ayudara a comprenderlo y a crearnos nuestro propio sistema de ficheros.

1.- Introducción

Para empezar empezaremos explicando FUSE y sus implicaciones. FUSE (Filesystem in Userspace) como su propio nombre indica, nos permitirá implementar sistemas de ficheros que se ejecuten en espacio de usuario, de manera que no deberemos tocar código del kernel para implementar un nuevo sistema de ficheros, ademas de que, en caso de fallo, no ocasionaremos inestabilidad alguna en el sistema. Para ello deberemos cargar un modulo de FUSE especifico en el kernel como muestra el siguiente gráfico:

FUSE arch

FUSE arch

Como vemos, desde espacio de usuario (programa que usa el FS concreto) accederemos al espacio de kernel (a través de la capa de abstracción VFS) y volveremos al espacio de usuario donde tenemos el modulo FUSE de nuestro sistema de ficheros ejecutándose.

2.- Funciones de FUSE (VFS)

Ya que la interfaz con el kernel sigue siendo basada en VFS (como si lo implementasemos en el nucleo), deberemos conocer las operaciones de VFS y su funcion. La API de FUSE no es exactamente VFS, pero esta basadco en la misma y tienen la mismas operaciones. Pasamos a enumerar y describir las funciones de VFS más comunes:

  • int(* getattr )(const char *, struct stat *)
    • Es llamada a la hora de obtener el estado de cada archivo. Su función es devolver el estado para el archivo dado, para ello deberemos devolver una estructura de tipo “struct stat” donde almacenaremos los datos de acceso, tamaño, fechas, etc.ç
    • El primer parametro indica el path del fichero concreto.
  • int(* mkdir )(const char *, mode_t)
    • Es llamada cuando se desea crear una carpeta.
    • Los parámetros indican el path de la carpeta nueva y el modo de creación.
  • int(* unlink )(const char *)
    • Recibe el path de un fichero concreto que se desea eliminar.
  • int(* rmdir )(const char *)
    • Recibe el path completo de un directorio que se desea eliminar.

    int(* rename )(const char *, const char *)

    • Llamada cuando se renombra o se mueve un archivo.
    • Recibe como argumentos el path antiguo y el nuevo.
  • int(* mknod )(const char *, mode_t, dev_t)
    • Crea un nuevo fichero en el path y con los permisos dados.
  • int(* truncate )(const char *, off_t)
    • Cambia el tamaño de un archivo al numero de bytes dado.
    • Se llama al guardar un fichero al que se le ha cambiado el tamaño (pj: un fichero de texto).
  • int(* opendir )(const char *, struct fuse_file_info *)
  • int(* open )(const char *, struct fuse_file_info *)
    • Se llama al abrir un directorio (opendir) o  un fichero (open).
    • Deberemos devolver si se autoriza el permiso o no. Mas tarde veremos los posibles valores a devolver.
  • int(* read )(const char *, char *, size_t, off_t, struct fuse_file_info *)
  • int(* write )(const char *, const char *, size_t, off_t, struct fuse_file_info *)
    • Se llaman al leer/escribir en un fichero.
    • En el caso de lectura, deberemos escribir en el buffer los datos recuperados del fichero.
    • En caso de escritura deberemos leer del buffer la información a escribir en el fichero.
  • int(* readdir )(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *)
    • Se llama al listar el contenido de un directorio, por lo tanto deberemos rellenar la lista (void *) con una funcion filler que nos proporcionan de tipo “fuse_fill_dir_t”.
    • Para ello, por cada archivo deberemos llamar al filler:
      • filler (buffer, file->filename, &file->stat, 0);
        • Donde “buffer” es el buffer que nos proporcionan, “file->filename” es el nombre del fichero y “&file->stat” es la dirección de memoria de una estructura de tipo “struct stat” que contendrá el estado del fichero.
  • int(* flush )(const char *, struct fuse_file_info *)
  • int(* fsync )(const char *, int, struct fuse_file_info *)
    • Se llaman al hacer un flush sobre un archivo (flush) o al cerrar un archivo (fsync).
    • Deberemos confirmar cambios en el FS o realizar otro tipo de acción al cerrar cada archivo.
  • void(* destroy )(void *)
    • Se llama al desmontar un volumen.
    • Deberemos liberar memoria.

Vamos a ver que valores podemos devolver desde estas funciones para hacerlo mas completo:

  • -ENOENT: El fichero/directorio solicitado no existe.
  • -EPERM: No dispone de los permisos adecuados para realizar la operación solicitada.
  • -EEXIST: El fichero/directorio ya existe.
  • -EACCES: No tiene permisos de acceso.
  • -ENOSPC: No hay espacio disponible.
  • -EINVAL: Argumento no válido.
  • 0: Operación finalizada con éxito.

Y con esto ya podemos implementar un sistema de ficheros simple y completo.

3.- Ejemplo de estructura

Veamos un ejemplo de estructura principal de una implementación simple en FUSE:

static int readDirectory (const char *path, void *buffer, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
[........]
}

[...............]

int main(int argc, char **argv) {

static struct fuse_operations _operations;
_operations.getattr = readAttr;
_operations.readdir = readDirectory;
_operations.open = openFile;
_operations.read = readFile;
_operations.write = writeFile;
_operations.statfs = getFSStats;
_operations.destroy = cleanFS;
_operations.unlink = removeFile;
_operations.flush = syncFlushFS;
_operations.fsync = syncFS;
_operations.mknod = createNode;
_operations.rename = renameFile;

return fuse_main (argc, argv, &_operations, NULL);
}

Posteriormente lo compilares con un simple:

gcc main.c  -o fusexample -lfuse

Una vez lo hayamos compilado podremos ejecutarlo como:

./fusexample mount_point

En caso de que queramos pasarle un parametro propio, como un dispositivo (/dev/sdb1) deberiamos ejecutarlo como:

./fusexample /dev/sdb1 mount_point

y en el codigo interno deberemos filtrar el primer parametro (/dev/sdb1) para no madnarselo a FUSE a traves de la llamada a fuse_main, por ejemplo usando esto: “return fuse_main (argc-1, &argv[1], &_operations, NULL);”.

Nada mas por hoy, espero que haya sido de ayuda y haya ayudado a comprender un poco mas el funcionamiento de FUSE.

Enlaces:

Advertisement

Acciones

Información

2 respuestas

26 06 2009
Jorge Suárez de Lis

Ole, mu claro todo :) aunque ¿qué compilo con eso? Bueno, es una tontería, imagino que una biblioteca que se acople a fuse. Un enlacillo a la doc que explique qué hacer con eso que compilas molaría un rato, o algo.

Ahora con toda esta info habría que ver por qué operaciones empezar :D Imagino que aunque tuvieras una implementación en forma de biblioteca para el de la wii ese no harías todo a saco, ¿o sí?

Si tienes ganas de escribir, creo que un pequeño footage sobre las primeras operaciones a implementar y cosas así estaría bien, pero supongo que todo es ponerse y si tienes ganas acabas enterándote. O algo. Yo ahora mismo no sabría por donde empezar xD

26 06 2009
BerfengeR

Ya lo he corregido, ahora pongo el como usarlo. Realmente genera un ejecutable directamente.

Respecto a como empezar, deberias tener por ejemplo una lista doblemente enlazada o un hashmap con estructuras que definan a cada archivo para poder acceder a sus atributos desde las funciones. Asi mismo deberias definir como leer o escribir en un offset determinado para cada archivo. Bien sea en un dispositivo a traves de una tabla LBA (por ejemplo, como hice en la implementacion de WBFS) o conectandote remotamente a un sitio X si lo archivos son remotos y quieres hacer algo transparente. Segun como sea el FS, vamos.

De entrada puede parecer algo complejo, pero una vez coges el concepto es muy sencillo.

Deja un comentario

Fill in your details below or click an icon to log in:

Logo de WordPress.com

You are commenting using your WordPress.com account. Log Out / Cambiar )

Twitter picture

You are commenting using your Twitter account. Log Out / Cambiar )

Facebook photo

You are commenting using your Facebook account. Log Out / Cambiar )

Connecting to %s




Seguir

Get every new post delivered to your Inbox.