From: nejucomo Date: Tue, 20 Nov 2007 08:21:50 +0000 (-0700) Subject: A hastily written single-threaded read-only fuse client. X-Git-Url: https://git.rkrishnan.org/architecture.txt?a=commitdiff_plain;h=3f0cfc1b0a99a7536b33a6e7e0548f08225dd97d;p=tahoe-lafs%2Ftahoe-lafs.git A hastily written single-threaded read-only fuse client. --- diff --git a/extensions/tahoe-fuse.py b/extensions/tahoe-fuse.py new file mode 100644 index 00000000..11b62d57 --- /dev/null +++ b/extensions/tahoe-fuse.py @@ -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() +