mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-20 13:33:09 +00:00
A hastily written single-threaded read-only fuse client.
This commit is contained in:
parent
184351d1f7
commit
3f0cfc1b0a
377
extensions/tahoe-fuse.py
Normal file
377
extensions/tahoe-fuse.py
Normal 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()
|
||||
|
Loading…
Reference in New Issue
Block a user