]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
A patch to make tahoe-fuse.py work with 0.7.0 plus a howto README.
authornejucomo <nejucomo@gmail.com>
Sun, 13 Jan 2008 00:06:39 +0000 (17:06 -0700)
committernejucomo <nejucomo@gmail.com>
Sun, 13 Jan 2008 00:06:39 +0000 (17:06 -0700)
contrib/fuse/README [new file with mode: 0644]
contrib/fuse/tahoe-fuse.py [new file with mode: 0644]
contrib/tahoe-fuse.py [deleted file]

diff --git a/contrib/fuse/README b/contrib/fuse/README
new file mode 100644 (file)
index 0000000..f79107c
--- /dev/null
@@ -0,0 +1,90 @@
+
+Welcome to the tahoe fuse interface prototype!
+
+
+Dependencies:
+
+In addition to a working tahoe installation, this interface depends
+on the python-fuse interface.  This package is available on Ubuntu
+systems as "python-fuse".  It is only known to work with ubuntu
+package version "2.5-5build1".  The latest ubuntu package (version
+"1:0.2-pre3-3") appears to not work currently.
+
+Unfortunately this package appears poorly maintained (notice the wildy
+different version strings and changing API semantics), so if you know
+of a good replacement pythonic fuse interface, please let tahoe-dev know
+about it!
+
+
+Configuration:
+
+Currently tahoe-fuse.py uses the same ~/.tahoe/private/root_dir.cap
+file (which is also the CLI default).  This is not configurable yet.
+Place a directory cap in this file.  (Hint: If you can run "tahoe ls"
+and see a directory listing, this file is properly configured.)
+
+
+Commandline:
+
+The usage is "tahoe-fuse.py <mountpoint>".  The mount point needs to
+be an existing directory which should be empty.  (If it's not empty
+the contents will be safe, but unavailable while the tahoe-fuse.py
+process is mounted there.)
+
+
+Usage:
+
+To use the interface, use other programs to poke around the
+mountpoint.  You should be able to see the same contents as you would
+by using the CLI or WUI for the same directory cap.
+
+
+Runtime Behavior Notes:
+
+Read-only:
+Only reading a tahoe grid is supported, which is reflected in
+the permission modes.  With Tahoe 0.7.0, write access should be easier
+to implement, but is not yet present.
+
+In-Memory File Caching:
+Currently requesting a particular file for read causes the entire file to
+be retrieved into tahoe-fuse.py memory before the read operation returns!
+This caching is reused for subsequent reads.  Beware large files.
+When transitioning to a finer-grained fuse api, this caching should be
+replaced with straight-forward calls to the wapi.  In my opinion, the
+Tahoe node should do all the caching tricks, so that extensions such as
+tahoe-fuse.py can be simple and thin.
+
+Backgrounding Behavior:
+When using the 2.5-5build1 ubuntu package, and no other arguments
+besides a mountpoint to tahoe-fuse.py, the process should remain in
+the foreground and print debug information.   Other python-fuse
+versions appear to alter this behavior and may fork the process to
+the background and obscure the log output.  Bonus points to whomever
+discovers the fate of these poor log messages in this case.
+
+"Investigative Logging":
+This prototype is designed to aide in further fuse development, so
+currently *every* fuse interface call figures out the process from
+which the file system request originates, then it figures out that
+processes commandline (this uses the /proc file system).  This is handy
+for interactive inspection of what kinds of behavior invokes which
+file system operations, but may not work for you.  To disable this
+inspection, edit the source and comment out all of the "@debugcall"
+[FIXME: double check python ref name] method decorators by inserting a
+'#' so it looks like "#@debugcall" (without quotes).
+
+Not-to-spec:
+The current version was not implemented according to any spec and
+makes quite a few dubious "guesses" for what data to pass the fuse
+interface.  You may see bizarre values, which may potentialy confuse
+any processes visiting the files under the mount point.
+
+Serial, blocking operations:
+Most fuse operations result in one or more http calls to the WAPI.
+These are serial and blocking (at least for the tested python-fuse
+version 2.5-5build1), so access to this file system is quite
+inefficient.
+
+
+Good luck!
diff --git a/contrib/fuse/tahoe-fuse.py b/contrib/fuse/tahoe-fuse.py
new file mode 100644 (file)
index 0000000..8e35291
--- /dev/null
@@ -0,0 +1,433 @@
+#! /usr/bin/env python
+'''
+Tahoe thin-client fuse module.
+
+
+Usage Notes:
+
+This is a proof-of-concept, and not production quality.  It uses the
+FUSE interface, and where bugs or unimplemented features are encountered
+the file system may become confused.
+
+In my experience with ubuntu's linux version 2.6.20-16-generic, and
+python-fuse version 2.5-5build1, the worst behavior is that processes
+which are accessing the fuse filesystem when some bugs occur hang.
+Also, the filesystem is currently single-threaded and blocking, so one
+bug interrupts all filesystem client processes.
+
+The rest of my system seems stable even in these cases (the rest of the
+filesystem and other processes function).
+
+The current design caches EACH FILE ENTIRELY IN MEMORY as long as any
+process has that file open.  Expect horrible memory usage.  (But also, subsequent reads after the first should be fast.  ;-)
+
+
+Goals:
+
+- Delegate to Tahoe webapi as much as possible.
+- Thin rather than clever.  (Even when that means clunky.)
+
+
+Status Quo:
+
+- Reads cache entire file contents, violating the thinness goal.  Can we GET spans of files?
+- Single threaded.
+'''
+
+
+#import bindann
+#bindann.install_exception_handler()
+
+import sys, stat, os, errno, urllib
+
+try:
+    import simplejson
+except ImportError, e:
+    raise SystemExit('Could not import simplejson, which is bundled with Tahoe.  Please update your PYTHONPATH environment variable to include the tahoe "support/lib/python<VERSION>/site-packages" directory.')
+    
+
+try:
+    import fuse
+except ImportError, e:
+    raise SystemExit('Could not import fuse, the pythonic fuse bindings.  This dependency of tahoe-fuse.py is *not* bundled with tahoe.  Please install it.  On debian/ubuntu systems run: sudo apt-get install python-fuse')
+
+# FIXME: Currently uses the old, silly path-based (non-stateful) interface:
+fuse.fuse_python_api = (0, 1) # Use the silly path-based api for now.
+
+
+### Config:
+TahoeConfigDir = '~/.tahoe'
+MagicDevNumber = 42
+UnknownSize = -1
+
+
+def main(args = sys.argv[1:]):
+    if not args:
+        raise SystemExit("Usage: %s MOUNTPOINT\n\nThe argument MOUNTPOINT is an empty directory where you want to mount a tahoe filesystem.\n" % (sys.argv[0],))
+    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_rootdir()
+
+    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_rootdir(self):
+        # For now we just use the same default as the CLI:
+        rootdirfn = os.path.join(self.confdir, 'private', 'root_dir.cap')
+        try:
+            f = open(rootdirfn, 'r')
+            cap = f.read().strip()
+            f.close()
+        except EnvironmentError, le:
+            # FIXME: This user-friendly help message may be platform-dependent because it checks the exception description.
+            if le.args[1].find('No such file or directory') != -1:
+                raise SystemExit('%s requires a directory capability in %s, but it was not found.\nPlease see "The CLI" in "docs/using.html".\n' % (sys.argv[0], rootdirfn))
+            else:
+                raise le
+
+        self.rootdir = TahoeDir(self.url, canonicalize_cap(cap))
+
+    def _get_node(self, path):
+        assert path.startswith('/')
+        if path == '/':
+            return self.rootdir.resolve_path([])
+        else:
+            parts = path.split('/')[1:]
+            return self.rootdir.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]
+        # FIXME: is this check correct?
+        if uri.find('URI:DIR2') != -1:
+            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)
+        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` # fails as of 0.7.0
+        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):
+        rawsize = self.get_metadata()[1]['size']
+        if type(rawsize) is not int: # FIXME: What about sizes which do not fit in python int?
+            assert rawsize == u'?', `rawsize`
+            return UnknownSize
+        else:
+            return rawsize
+    
+    def resolve_path(self, path):
+        assert type(path) is list
+        assert path == []
+        return self
+    
+
+class TahoeDir (TahoeNode):
+    def __init__(self, baseurl, 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
+        
+        
+def canonicalize_cap(cap):
+    i = cap.find('URI:')
+    assert i != -1, 'A cap must contain "URI:...", but this does not: ' + cap
+    return cap[i:]
+    
+
+if __name__ == '__main__':
+    main()
+
diff --git a/contrib/tahoe-fuse.py b/contrib/tahoe-fuse.py
deleted file mode 100644 (file)
index 400e7a1..0000000
+++ /dev/null
@@ -1,414 +0,0 @@
-#! /usr/bin/env python
-'''
-Tahoe thin-client fuse module.
-
-
-Usage Notes:
-
-This is a proof-of-concept, and not production quality.  It uses the
-FUSE interface, and where bugs or unimplemented features are encountered
-the file system may become confused.
-
-In my experience with ubuntu's linux version 2.6.20-16-generic, and
-python-fuse version 2.5-5build1, the worst behavior is that processes
-which are accessing the fuse filesystem when some bugs occur hang.
-Also, the filesystem is currently single-threaded and blocking, so one
-bug interrupts all filesystem client processes.
-
-The rest of my system seems stable even in these cases (the rest of the
-filesystem and other processes function).
-
-The current design caches EACH FILE ENTIRELY IN MEMORY as long as any
-process has that file open.  Expect horrible memory usage.  (But also, subsequent reads after the first should be fast.  ;-)
-
-
-Goals:
-
-- Delegate to Tahoe webapi as much as possible.
-- Thin rather than clever.  (Even when that means clunky.)
-
-
-Status Quo:
-
-- Reads cache entire file contents, violating the thinness goal.  Can we GET spans of files?
-- Single threaded.
-'''
-
-
-#import bindann
-#bindann.install_exception_handler()
-
-import sys, stat, os, errno, urllib
-
-try:
-    import simplejson
-except ImportError, e:
-    raise SystemExit('Could not import simplejson, which is bundled with Tahoe.  Please update your PYTHONPATH environment variable to include the tahoe "support/lib/python<VERSION>/site-packages" directory.')
-    
-
-try:
-    import fuse
-except ImportError, e:
-    raise SystemExit('Could not import fuse, the pythonic fuse bindings.  This dependency of tahoe-fuse.py is *not* bundled with tahoe.  Please install it.  On debian/ubuntu systems run: sudo apt-get install python-fuse')
-
-# FIXME: Currently uses the old, silly path-based (non-stateful) interface:
-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:]):
-    if not args:
-        raise SystemExit("Usage: %s MOUNTPOINT\n\nThe argument MOUNTPOINT is an empty directory where you want to mount a tahoe filesystem.\n" % (sys.argv[0],))
-    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_rootdir()
-
-    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.rootdir = TahoeDir(self.url, uri)
-
-    def _get_node(self, path):
-        assert path.startswith('/')
-        if path == '/':
-            return self.rootdir.resolve_path([])
-        else:
-            parts = path.split('/')[1:]
-            return self.rootdir.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]
-        # FIXME: is this check correct?
-        if uri.find('URI:DIR2') != -1:
-            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)
-        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 ('DIR2', 'DIR2-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()
-