]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
A hastily written single-threaded read-only fuse client.
authornejucomo <nejucomo@gmail.com>
Tue, 20 Nov 2007 08:21:50 +0000 (01:21 -0700)
committernejucomo <nejucomo@gmail.com>
Tue, 20 Nov 2007 08:21:50 +0000 (01:21 -0700)
extensions/tahoe-fuse.py [new file with mode: 0644]

diff --git a/extensions/tahoe-fuse.py b/extensions/tahoe-fuse.py
new file mode 100644 (file)
index 0000000..11b62d5
--- /dev/null
@@ -0,0 +1,377 @@
+#! /usr/bin/env python
+'''
+Tahoe thin-client fuse module.
+
+Goals:
+- Delegate to Tahoe webapi as much as possible.
+- Thin as possible.
+- This is a proof-of-concept, not a usable product.
+'''
+
+
+#import bindann
+#bindann.install_exception_handler()
+
+import sys, stat, os, errno, urllib
+
+import simplejson
+
+# FIXME: Currently uses the old, silly path-based (non-stateful) interface:
+import fuse
+fuse.fuse_python_api = (0, 1) # Use the silly path-based api for now.
+
+
+### Config:
+TahoeConfigDir = '~/.tahoe'
+MagicDevNumber = 42
+
+
+def main(args = sys.argv[1:]):
+    fs = TahoeFS(os.path.expanduser(TahoeConfigDir))
+    fs.main()
+
+
+### Utilities just for debug:
+def debugdeco(m):
+    def dbmeth(self, *a, **kw):
+        pid = self.GetContext()['pid']
+        print '[%d %r]\n%s%r%r' % (pid, get_cmdline(pid), m.__name__, a, kw)
+        try:
+            r = m(self, *a, **kw)
+            if (type(r) is int) and (r < 0):
+                print '-> -%s\n' % (errno.errorcode[-r],)
+            else:
+                repstr = repr(r)[:256]
+                print '-> %s\n' % (repstr,)
+            return r
+        except:
+            sys.excepthook(*sys.exc_info())
+            
+    return dbmeth
+
+
+def get_cmdline(pid):
+    f = open('/proc/%d/cmdline' % pid, 'r')
+    args = f.read().split('\0')
+    f.close()
+    assert args[-1] == ''
+    return args[:-1]
+
+
+class ErrnoExc (Exception):
+    def __init__(self, eno):
+        self.eno = eno
+        Exception.__init__(self, errno.errorcode[eno])
+
+    @staticmethod
+    def wrapped(meth):
+        def wrapper(*args, **kw):
+            try:
+                return meth(*args, **kw)
+            except ErrnoExc, e:
+                return -e.eno
+        wrapper.__name__ = meth.__name__
+        return wrapper
+
+
+### Heart of the Matter:
+class TahoeFS (fuse.Fuse):
+    def __init__(self, confdir):
+        fuse.Fuse.__init__(self)
+        self.confdir = confdir
+        
+        self.flags = 0 # FIXME: What goes here?
+        self.multithreaded = 0
+
+        # silly path-based file handles.
+        self.filecontents = {} # {path -> contents}
+
+        self._init_url()
+        self._init_bookmarks()
+
+    def _init_url(self):
+        f = open(os.path.join(self.confdir, 'webport'), 'r')
+        contents = f.read()
+        f.close()
+
+        fields = contents.split(':')
+        proto, port = fields[:2]
+        assert proto == 'tcp'
+        port = int(port)
+        self.url = 'http://localhost:%d' % (port,)
+
+    def _init_bookmarks(self):
+        f = open(os.path.join(self.confdir, 'fuse-bookmarks.uri'), 'r')
+        uri = f.read().strip()
+        f.close()
+        
+        self.bookmarks = TahoeDir(self.url, uri)
+
+    def _get_node(self, path):
+        assert path.startswith('/')
+        if path == '/':
+            return self.bookmarks.resolve_path([])
+        else:
+            parts = path.split('/')[1:]
+            return self.bookmarks.resolve_path(parts)
+    
+    def _get_contents(self, path):
+        node = self._get_node(path)
+        contents = node.open().read()
+        self.filecontents[path] = contents
+        return contents
+    
+    @debugdeco
+    @ErrnoExc.wrapped
+    def getattr(self, path):
+        node = self._get_node(path)
+        return node.getattr()
+                
+    @debugdeco
+    @ErrnoExc.wrapped
+    def getdir(self, path):
+        """
+        return: [(name, typeflag), ... ]
+        """
+        node = self._get_node(path)
+        return node.getdir()
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def mythread(self):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def chmod(self, path, mode):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def chown(self, path, uid, gid):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def fsync(self, path, isFsyncFile):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def link(self, target, link):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def mkdir(self, path, mode):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def mknod(self, path, mode, dev_ignored):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def open(self, path, mode):
+        IgnoredFlags = os.O_RDONLY | os.O_NONBLOCK | os.O_SYNC | os.O_LARGEFILE 
+        # Note: IgnoredFlags are all ignored!
+        for fname in dir(os):
+            if fname.startswith('O_'):
+                flag = getattr(os, fname)
+                if flag & IgnoredFlags:
+                    continue
+                elif mode & flag:
+                    print 'Flag not supported:', fname
+                    raise ErrnoExc(errno.ENOSYS)
+
+        self._get_contents(path)
+        return 0
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def read(self, path, length, offset):
+        return self._get_contents(path)[offset:length]
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def release(self, path):
+        del self.filecontents[path]
+        return 0
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def readlink(self, path):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def rename(self, oldpath, newpath):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def rmdir(self, path):
+        return -errno.ENOSYS
+
+    #@debugdeco
+    @ErrnoExc.wrapped
+    def statfs(self):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def symlink ( self, targetPath, linkPath ):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def truncate(self, path, size):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def unlink(self, path):
+        return -errno.ENOSYS
+
+    @debugdeco
+    @ErrnoExc.wrapped
+    def utime(self, path, times):
+        return -errno.ENOSYS
+
+
+class TahoeNode (object):
+    NextInode = 0
+    
+    @staticmethod
+    def make(baseurl, uri):
+        typefield = uri.split(':', 2)[1]
+        if typefield.startswith('DIR'):
+            return TahoeDir(baseurl, uri)
+        else:
+            return TahoeFile(baseurl, uri)
+        
+    def __init__(self, baseurl, uri):
+        self.burl = baseurl
+        self.uri = uri
+        self.fullurl = '%s/uri/%s' % (self.burl, self.uri.replace('/', '!'))
+        self.inode = TahoeNode.NextInode
+        TahoeNode.NextInode += 1
+
+    def getattr(self):
+        """
+        - st_mode (protection bits)
+        - st_ino (inode number)
+        - st_dev (device)
+        - st_nlink (number of hard links)
+        - st_uid (user ID of owner)
+        - st_gid (group ID of owner)
+        - st_size (size of file, in bytes)
+        - st_atime (time of most recent access)
+        - st_mtime (time of most recent content modification)
+        - st_ctime (platform dependent; time of most recent metadata change on Unix,
+                    or the time of creation on Windows).
+        """
+        # FIXME: Return metadata that isn't completely fabricated.
+        return (self.get_mode(),
+                self.inode,
+                MagicDevNumber,
+                self.get_linkcount(),
+                os.getuid(),
+                os.getgid(),
+                self.get_size(),
+                0,
+                0,
+                0)
+
+    def get_metadata(self):
+        f = self.open('?t=json')
+        json = f.read()
+        f.close()
+        return simplejson.loads(json)
+        
+    def open(self, postfix=''):
+        url = self.fullurl + postfix
+        print '*** Fetching:', `url`
+        return urllib.urlopen(url)
+
+
+class TahoeFile (TahoeNode):
+    def __init__(self, baseurl, uri):
+        assert uri.split(':', 2)[1] in ('CHK', 'LIT'), `uri`
+        TahoeNode.__init__(self, baseurl, uri)
+
+    # nonfuse:
+    def get_mode(self):
+        return stat.S_IFREG | 0400 # Read only regular file.
+
+    def get_linkcount(self):
+        return 1
+    
+    def get_size(self):
+        return self.get_metadata()[1]['size']
+    
+    def resolve_path(self, path):
+        assert type(path) is list
+        assert path == []
+        return self
+    
+
+class TahoeDir (TahoeNode):
+    def __init__(self, baseurl, uri):
+        assert uri.split(':', 2)[1] in ('DIR', 'DIR-RO'), `uri`
+        TahoeNode.__init__(self, baseurl, uri)
+
+        self.mode = stat.S_IFDIR | 0500 # Read only directory.
+
+    # FUSE:
+    def getdir(self):
+        d = [('.', self.get_mode()), ('..', self.get_mode())]
+        for name, child in self.get_children().items():
+            if name: # Just ignore this crazy case!
+                d.append((name, child.get_mode()))
+        return d
+
+    # nonfuse:
+    def get_mode(self):
+        return stat.S_IFDIR | 0500 # Read only directory.
+
+    def get_linkcount(self):
+        return len(self.getdir())
+    
+    def get_size(self):
+        return 2 ** 12 # FIXME: What do we return here?  len(self.get_metadata())
+    
+    def resolve_path(self, path):
+        assert type(path) is list
+
+        if path:
+            head = path[0]
+            child = self.get_child(head)
+            return child.resolve_path(path[1:])
+        else:
+            return self
+        
+    def get_child(self, name):
+        c = self.get_children()
+        return c[name]
+
+    def get_children(self):
+        flag, md = self.get_metadata()
+        assert flag == 'dirnode'
+
+        c = {}
+        for name, (childflag, childmd) in md['children'].items():
+            if childflag == 'dirnode':
+                cls = TahoeDir
+            else:
+                cls = TahoeFile
+
+            c[str(name)] = cls(self.burl, childmd['ro_uri'])
+        return c
+        
+        
+
+if __name__ == '__main__':
+    main()
+