A hastily written single-threaded read-only fuse client.

This commit is contained in:
nejucomo 2007-11-20 01:21:50 -07:00
parent 184351d1f7
commit 3f0cfc1b0a

377
extensions/tahoe-fuse.py Normal file
View File

@ -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()