Saturday, March 7, 2009

FUSE test implementation

















I made a test script with FusePython, referencing a couple of existing scripts (this one and this one). You can make a file e.g. www.google.co.jp and get the contents of the page from it. Internally it uses urllib2 module. Once it downloads the HTML it caches it in the memory.

To mount it,

python hohefs.py mountpoint

To unmount it,
fusermount -u mountpoint


It is not at all a good example. I just made it to see what it would be like to make a file system with FUSE. I didn't implement permission, I omitted lots of error checkings, and unlink equals to deleting a file, etc.etc.etc....
import fuse
from fuse import Fuse
from time import time
import sys
import urllib2

import stat    # for file properties
import os      # for filesystem modes (O_RDONLY, etc)
import errno   # for error number codes (ENOENT, etc)
              # - note: these must be returned as negatives

fuse.fuse_python_api = (0, 2)


class Mystdout():

   def __init__(self, filename):
       self.f = open(filename, 'w')

   def write(self, text):
       self.f.write(text)
       self.f.flush()


class HoheStat(fuse.Stat):

   def __init__(self):
       self.st_mode  = 0
       self.st_ino   = 0
       self.st_dev   = 0
       self.st_nlink = 0
       self.st_uid   = 0
       self.st_gid   = 0
       self.st_size  = 0
       self.st_atime = 0
       self.st_mtime = 0
       self.st_ctime = 0

   def setStat(self, path):
       st =os.stat(path)
       self.st_mode  = st[0]
       self.st_ino   = st[1]
       self.st_dev   = st[2]
       self.st_nlink = st[3]
       self.st_uid   = st[4]
       self.st_gid   = st[5]
       self.st_size  = st[6]
       self.st_atime = st[7]
       self.st_mtime = st[8]
       self.st_ctime = st[9]


class NullFS(Fuse):

   def __init__(self, logfilename, mountpoint, *args, **kw):
       Fuse.__init__(self, *args, **kw)
       sys.stdout = Mystdout(logfilename)
       self.__filestats = {}
       s = self.__filestats['/'] = HoheStat()
       s.setStat(mountpoint)
       self.__docs ={}
       print 'Init complete.'

   def getattr(self, path):
       print '*** getattr', path
       return self.__filestats.get(path, -errno.ENOENT)

   def readdir(self, path, offset):
       print '*** readdir', path, offset
       for r in self.__getFileNames(path) + ['.', '..']:
           yield fuse.Direntry(r)

   def getdir(self, path):
       print '*** getdir', path
       return map(lambda x: (x, 0), self.__getFileNames(path))

   def mknod(self, path, mode, dev):
       print '*** mknod', path, oct(mode), dev
       st = HoheStat()
       st.st_mode  = stat.S_IFREG | mode
       st.st_dev   = dev
       st.st_nlink = 1
       st.st_size  = 0
       st.st_uid   = os.getuid()
       st.st_gid   = os.getgid()
       st.st_utime = time()
       self.__filestats[path] = st

   def utime (self, path, times):
       print '*** utime', path, times
       st = self.__filestats.get(path)
       if st:
           st.st_atime = times[0]
           st.st_utime = times[1]

   def read(self, path, length, offset):
       print '*** read', path, length, offset
       st = self.__filestats[path]
       st.st_atime = time()
       return self.__docs[path][offset:offset +length]

   def open(self, path, flags):
       print '*** open', path, flags
       st = self.__filestats.get(path)
       if not st:
           return -errno.ENOENT

       accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
       if not (flags & accmode) in [os.O_RDONLY, os.O_WRONLY, os.O_RDWR]:
           return -errno.EACCES

       docs = self.__docs
       if path in docs:
           doc = docs[path]
       else:
           f = urllib2.urlopen("http:/" + path)
           doc = f.read()
           st.st_size  = len(doc)
           docs[path] = doc
           #Oops I need to close f

   def mkdir(self, path, mode):
       print '*** mkdir', path, oct(mode)

       st =self.__filestats.get(path)
       if st:
           return -errno.EEXIST

       parentpath = os.path.dirname(path)
       parentst = self.__filestats.get(parentpath)
       if not parentst:
           return -errno.ENOENT
       if not parentst.st_mode & stat.S_IFDIR:
           return -errno.ENOTDIR

       st = HoheStat()
       st.st_mode  = stat.S_IFDIR | mode
       st.st_dev   = 0
       st.st_nlink = 1
       st.st_size  = 0
       st.st_uid   = os.getuid()
       st.st_gid   = os.getgid()
       st.st_utime = time()
       self.__filestats[path] = st

   def unlink(self, path):
       print '*** unlink', path
       if path in self.__filestats:
           del self.__filestats[path]
           docs =self.__docs
           if path in docs:
               del docs[path]
       else:
           return -errno.ENOENT

   def rmdir(self, path):
       print '*** rmdir', path
       if self.__getFileNames(path):
           return -errno.EEXIST
       if path in self.__filestats:
           del self.__filestats[path]
       else:
           return -errno.ENOENT

   def rename(self, oldPath, newPath):
       print '*** rename', oldPath, newPath
       return -errno.ENOENT

   def mythread(self):
       print '*** mythread'
       return -errno.ENOSYS

   def chmod(self, path, mode):
       print '*** chmod', path, oct(mode)
       return -errno.ENOSYS

   def chown(self, path, uid, gid):
       print '*** chown', path, uid, gid
       return -errno.ENOSYS

   def fsync(self, path, isFsyncFile):
       print '*** fsync', path, isFsyncFile
       return -errno.ENOSYS

   def link(self, targetPath, linkPath):
       print '*** link', targetPath, linkPath
       return -errno.ENOSYS

   def readlink(self, path):
       print '*** readlink', path
       return -errno.ENOSYS

   def release(self, path, flags):
       print '*** release', path, flags
       return 0
       return -errno.ENOSYS

   def statfs(self):
       print '*** statfs'
       return -errno.ENOSYS

   def symlink(self, targetPath, linkPath):
       print '*** symlink', targetPath, linkPath
       return -errno.ENOSYS

   def truncate(self, path, size):
       print '*** truncate', path, size
       return -errno.ENOSYS

   def write(self, path, buf, offset):
       print '*** write', path, buf, offset
       return -errno.ENOSYS

   def __getFileNames(self, currentpath):
       filt = lambda(x): os.path.dirname(x) == currentpath
       filesincurrent = filter(filt, self.__filestats)
       if currentpath in filesincurrent:
           filesincurrent.remove(currentpath)
       return map(os.path.basename, filesincurrent)

if __name__ == '__main__':

   usage="""
       Userspace hello example

       """ + Fuse.fusage

   fs = NullFS(
       "hohefs.log",
       sys.argv[1],
       version="%prog " + fuse.__version__,
       usage=usage,
       dash_s_do='setsingle')

   fs.parse(values=fs, errex=1)
   fs.main()

No comments: