2007-11-20 08:21:50 +00:00
#! /usr/bin/env python
'''
Tahoe thin - client fuse module .
2007-11-20 20:58:42 +00:00
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 - 5 build1 , 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 . ; - )
2007-11-20 08:21:50 +00:00
Goals :
2007-11-20 20:58:42 +00:00
2007-11-20 08:21:50 +00:00
- Delegate to Tahoe webapi as much as possible .
2007-11-20 20:58:42 +00:00
- 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 .
2007-11-20 08:21:50 +00:00
'''
#import bindann
#bindann.install_exception_handler()
import sys , stat , os , errno , urllib
2008-01-08 19:21:21 +00:00
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 ' )
2007-11-20 08:21:50 +00:00
# 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 : ] ) :
2008-01-08 17:40:35 +00:00
if not args :
2008-01-08 19:21:21 +00:00
raise SystemExit ( " Usage: %s MOUNTPOINT \n \n The argument MOUNTPOINT is an empty directory where you want to mount a tahoe filesystem. \n " % ( sys . argv [ 0 ] , ) )
2007-11-20 08:21:50 +00:00
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 ) :
2008-01-08 17:40:35 +00:00
rootdirfn = os . path . join ( self . confdir , ' private ' , ' root-dir.cap ' )
try :
f = open ( rootdirfn , ' r ' )
uri = f . read ( ) . strip ( )
f . close ( )
except EnvironmentError , le :
2008-01-08 19:21:21 +00:00
# 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 when it was not found. \n Please see " The CLI " in " docs/using.html " . \n ' % ( sys . argv [ 0 ] , rootdirfn ) )
else :
raise le
2008-01-08 17:40:35 +00:00
2007-11-20 08:21:50 +00:00
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 ]
2008-01-08 19:21:21 +00:00
# FIXME: is this check correct?
if uri . find ( ' URI:DIR2 ' ) != - 1 :
2007-11-20 08:21:50 +00:00
return TahoeDir ( baseurl , uri )
else :
return TahoeFile ( baseurl , uri )
def __init__ ( self , baseurl , uri ) :
self . burl = baseurl
self . uri = uri
2007-12-20 00:54:40 +00:00
self . fullurl = ' %s /uri/ %s ' % ( self . burl , self . uri )
2007-11-20 08:21:50 +00:00
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 ) :
2008-01-08 19:21:21 +00:00
#assert uri.split(':', 2)[1] in ('DIR2', 'DIR2-RO'), `uri`
2007-11-20 08:21:50 +00:00
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 ( )