Python en tu filesystem: Creando filesystems en userspace con Python y Fuse

../images/fuse_python/roger.png
Autor:Roger Durán
Bio:Linuxero, pythonista y con problemas sociopáticos
Email:roger@elvex.org.ar
Twitter:@roger_duran
Web:http://www.fsck.com.ar

Primero que nada veamos que es un filesystem.

Una forma simple de verlo, seria un método de guardar datos y organizarlos de una forma que sea fácil encontrarlos y accederlos, estos se representan como directorios y archivos, estos usualmente se almacenan en dispositivos como puede ser un disco rígido o una memoria.

Algunos ejemplos conocidos:

¿Que es Fuse?

../images/fuse_python/fuse.png

Fuse (http://fuse.sourceforge.net/) es un modulo de kernel, como podría ser cualquiera de los nombrados anteriormente, pero que en vez de implementar un filesystem, nos expone su API en espacio de usuario. Pero... ¿Qué sistemas operativos puedo usar fuse?

  • En la mayor parte de los sistemas operativos *nix, como puede ser GNU/Linux, MacOS, *bsd..
  • En Windows existe una implementación de fuse llamada "Dokan", no puedo comentar mucho sobre esta, ya que no la he probado.

Algunos ejemplos de uso de fuse

  • En Python:
    • fusql
    • Gmailfs
    • Youtubefs
    • Cuevanafs
    • FatFuse
  • Otros lenguajes:
    • gnomeVFS2
    • zfs-fuse
    • sshfs
    • etc..

Como podemos ver, podemos crear desde un filesystem mapeando recursos de internet/red, hasta filesystem tradicionales como puede ser zfs(muy popular en solaris) o fat.

Fusql es un fs fuera de lo común, lo que lo hace interesante es que mapea una base de datos relacional(Sqlite) como si fuera un filesystem, pudiéndose operar sobre ella completamente.

Las ventajas que tenemos al desarrollar un filesystem con Fuse son:

  • Podemos programar en nuestro lenguaje favorito (en este caso python).
  • Simplemente reiniciando la aplicación, estaremos haciendo pruebas con el codigo actualizado.
  • Podemos acceder a las librerías del sistema para crearlos, por ej el stdlib de python.
  • No tendremos que lidear con kernel panics, reinicios, utilización de maquinas virtuales para las pruebas, etc.
  • Mayor portabilidad, ya que fuse existe en diversos sistemas operativos.
  • Podemos ejecutar nuestros filesystem con permisos de usuario.
  • Facil debugging de los mismos.

Fuse: El API

El API de fuse funciona por callbacks. Por ejemplo: cuando nosotros accedemos a un directorio, en nuestra aplicación se llama a getattr, opendir, readdir, releasedir.

create(path, mode) # creación de un archivo
truncate(path, mode) # achicar o agrandar el tamaño de un archivo
open(path, mode) # apertura de un archivoError: BadDrawable
write(path, data, offset) # escritura de un archivo
read(data, lenght, offset) # lectura de un archivo
release(path) # liberación de un archivo
fsync(path) # sincronización de un archivo
chmod(path, mode) # cambio de permisos
chown(path, uid, gid) # cambio de propietario
mkdir(path, mode) # creación de directorio
unlink(path) # eliminación de un archivolink
rmdir(path) # eliminacón de un directorio
rename(opath, npath) # renombrado
link(srcpath, dstpath) # creación de un link

Como se usa

Este es un ejemplo mínimo de lectura y escritura de un archivo, hagamos de cuenta que estos métodos están en un objeto que tiene un diccionario llamado items con el path como key y los datos de este como valor

# lectura
def read(self, path, offset, length):
    # establecemos el comienzo de nuestra lectura
    start - offset

    # establecemos el fin de la lectura
    end - start + length

    # retornamos la cantidad de datos solicitado
    return self.items[path][start:end]

# escritura
def write(self, path, offset, data):
    # tomamos el tamaño de los datos a escribir
    length - len(data)

    # tomamos los datos actuales de nuestro archivo
    item_data - self.items[path]

    # agregamos/reemplazamos la parte del archivo que se solicito
    item_data - itdat[:offset] + data + item[offset+length:]

    # remplazamos el contenido de nuestro item
    self.items[path] - item_data

    # devolvemos el tamaño de datos que escribimos
    return length

# truncate
def truncate(self, path, length):
    # tomamos los datos de nuestro archivo
    item_data - self.items[path]

    If len(item_data) > length:
        # si el tamaño de nuestros datos es mayor que el solicitado
        # lo acortamos a este
        self.items[path] - item_data[:length]
    else:
        # sino rellenamos con 0 al tamaño solicitado
        self.items[path] +- '0' * len(item_data)

Defuse

Trabajando con python-fuse, una de las cosas que me parecieron incomodas es el manejo de los path, viniendo del mundo de las webs, sobre todo con werkzeug/flask, se me ocurrió implementar algo similar a su manejo de rutas, pero para la escritura de un filesystem, así es como nació defuse https://github.com/Roger/defuse.

Lo que esto nos permite es utilizar decoradores para el manejo de las rutas, separando cada parte de nuestro filesystem como una clase con todos los métodos que nos proporciona fuse.

Un pequeño ejemplo

fs - FS.get()

@fs.route('/')
class Root(object):
    def __init__(self):
        root_mode - S_IRUSR|S_IXUSR|S_IWUSR|S_IRGRP|S_IXGRP|S_IXOTH|S_IROTH
        self.dir_metadata - BaseMetadata(root_mode, True)

    def getattr(self, *args):
        return self.dir_metadata

    def readdir(self, *args):
        for i in xrange(4):
            yield fuse.Direntry('test%s.txt' % i)


@fs.route('/<filename>.<ext>')
class Files(object):
    def __init__(self):
        file_mode - S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH
        self.file_metadata - BaseMetadata(file_mode, False)

    def getattr(self, filename, ext):
        self.file_metadata.st_size - len(filename*4)
        return self.file_metadata

    def read(self, size, offset, filename, ext):
        data - filename * 4
        return data[offset:size+offset]

En el ejemplo anterior podemos ver una implementación funcional de un filesystem que muestra 4 archivos, cuyo contenido es el nombre del archivo repetido otras 4 veces.

Como pueden ver el manejo de los path se hace mediante el uso de decoradores de clase, además de que en cada método, ya no nos llega el path, sino que se agrega a estos las variables que definimos en el decorador.

Por ej: @fs.route('/<dir1>/<dir2>/<archivo>.<ext>') en /root//subdir/test.py nos devolvería las variables:

  • dir1-'root'
  • dir2-'subdir'
  • archivo-'test'
  • ext-'py'

Conclusión

Este articulo mas que enseñarles todo acerca de python-fuse, lo que quiere es mostrarles lo simple que es utilizarlo y entusiasmarlos a que escriban sus propios filesystems.

En el pyday hubo algunas ideas interesantes, como traductores automáticos de archivos, o análisis con nltk.

Espero ver pronto sus filesystems!

Help PET: Donate

blog comments powered by Disqus

Último cambio: Sat Jul 16 15:30:30 2011.  -  Esta revista está bajo una licencia Creative Commons.