3 #-----------------------------------------------------------------------------------------------
4 from allmydata.uri import CHKFileURI, NewDirectoryURI, LiteralFileURI
5 from allmydata.scripts.common_http import do_http as do_http_req
6 from allmydata.util.hashutil import tagged_hash
7 from allmydata.util.assertutil import precondition
8 from allmydata.util import base32
9 from allmydata.scripts.common import get_aliases
11 from twisted.python import usage
20 # pull in some spaghetti to make this stuff work without fuse-py being installed
22 import _find_fuse_parts
23 junk = _find_fuse_parts
36 USAGE = 'usage: tahoe fuse [dir_cap_name] [fuse_options] mountpoint'
37 DEFAULT_DIRECTORY_VALIDITY=26
39 if not hasattr(fuse, '__version__'):
41 "your fuse-py doesn't know of fuse.__version__, probably it's too old."
43 fuse.fuse_python_api = (0, 2)
44 fuse.feature_assert('stateful_files', 'has_init')
46 class TahoeFuseOptions(usage.Options):
48 ["node-directory", None, "~/.tahoe",
49 "Look here to find out which Tahoe node should be used for all "
50 "operations. The directory should either contain a full Tahoe node, "
51 "or a file named node.url which points to some other Tahoe node. "
52 "It should also contain a file named private/aliases which contains "
53 "the mapping from alias name to root dirnode URI."
55 ["node-url", None, None,
56 "URL of the tahoe node to use, a URL like \"http://127.0.0.1:8123\". "
57 "This overrides the URL found in the --node-directory ."],
59 "Which alias should be mounted."],
60 ["root-uri", None, None,
61 "Which root directory uri should be mounted."],
62 ["cache-timeout", None, 20,
63 "Time, in seconds, to cache directory data."],
67 usage.Options.__init__(self)
68 self.fuse_options = []
69 self.mountpoint = None
71 def opt_option(self, fuse_option):
73 Pass mount options directly to fuse. See below.
75 self.fuse_options.append(fuse_option)
79 def parseArgs(self, mountpoint=''):
80 self.mountpoint = mountpoint
82 def getSynopsis(self):
83 return "%s [options] mountpoint" % (os.path.basename(sys.argv[0]),)
85 logfile = file('tfuse.log', 'ab')
88 logfile.write("%s: %s\n" % (time.asctime(), msg))
94 def unicode_to_utf8(u):
95 precondition(isinstance(u, unicode), repr(u))
96 return u.encode('utf-8')
98 def do_http(method, url, body=''):
99 resp = do_http_req(method, url, body)
100 log('do_http(%s, %s) -> %s, %s' % (method, url, resp.status, resp.reason))
101 if resp.status not in (200, 201):
102 raise RuntimeError('http response (%s, %s)' % (resp.status, resp.reason))
106 def flag2mode(flags):
107 log('flag2mode(%r)' % (flags,))
108 #md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
109 md = {os.O_RDONLY: 'rb', os.O_WRONLY: 'wb', os.O_RDWR: 'w+b'}
110 m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
112 if flags & os.O_APPEND:
113 m = m.replace('w', 'a', 1)
117 class TFSIOError(IOError):
120 class ENOENT(TFSIOError):
121 def __init__(self, msg):
122 TFSIOError.__init__(self, errno.ENOENT, msg)
124 class EINVAL(TFSIOError):
125 def __init__(self, msg):
126 TFSIOError.__init__(self, errno.EINVAL, msg)
128 class EACCESS(TFSIOError):
129 def __init__(self, msg):
130 TFSIOError.__init__(self, errno.EACCESS, msg)
132 class EEXIST(TFSIOError):
133 def __init__(self, msg):
134 TFSIOError.__init__(self, errno.EEXIST, msg)
136 def logargsretexc(meth):
137 def inner(self, *args, **kwargs):
138 log("%s(%r, %r)" % (meth, args, kwargs))
140 ret = meth(self, *args, **kwargs)
142 log('exception:\n%s' % (traceback.format_exc(),))
144 log("ret: %r" % (ret, ))
146 inner.__name__ = '<logwrap(%s)>' % (meth,)
150 def inner(self, *args, **kwargs):
152 ret = meth(self, *args, **kwargs)
153 except TFSIOError, tie:
154 log('error: %s' % (tie,))
157 log('exception:\n%s' % (traceback.format_exc(),))
160 inner.__name__ = '<logwrap(%s)>' % (meth,)
164 log('exception:\n%s' % (traceback.format_exc(),))
166 class TahoeFuseFile(object):
168 def __init__(self, path, flags, *mode):
169 log("TFF: __init__(%r, %r, %r)" % (path, flags, mode))
171 self._path = path # for tahoe put
173 self.parent, self.name, self.fnode = self.tfs.get_parent_name_and_child(path)
175 log('TFF: flags2(mode) -> %s' % (m,))
178 self.fname = self.tfs.cache.tmp_file(os.urandom(20))
179 if self.fnode is None:
180 log('TFF: [%s] open() for write: no file node, creating new File %s' % (self.name, self.fname, ))
181 self.fnode = File(0, 'URI:LIT:')
182 self.fnode.tmp_fname = self.fname # XXX kill this
183 self.parent.add_child(self.name, self.fnode, {})
184 elif hasattr(self.fnode, 'tmp_fname'):
185 self.fname = self.fnode.tmp_fname
186 log('TFF: [%s] open() for write: existing file node lists %s' % (self.name, self.fname, ))
188 log('TFF: [%s] open() for write: existing file node lists no tmp_file, using new %s' % (self.name, self.fname, ))
189 self.file = os.fdopen(os.open(self.fname, flags|os.O_CREAT, *mode), m)
190 self.fd = self.file.fileno()
191 log('TFF: opened(%s) for write' % self.fname)
192 self.open_for_write = True
195 assert self.fnode is not None
196 uri = self.fnode.get_uri()
198 # XXX make this go away
199 if hasattr(self.fnode, 'tmp_fname'):
200 self.fname = self.fnode.tmp_fname
201 log('TFF: reopening(%s) for reading' % self.fname)
203 log('TFF: fetching file from cache for reading')
204 self.fname = self.tfs.cache.get_file(uri)
206 self.file = os.fdopen(os.open(self.fname, flags, *mode), m)
207 self.fd = self.file.fileno()
208 self.open_for_write = False
209 log('TFF: opened(%s) for read' % self.fname)
215 log("<TFF(%s:%s)> %s" % (os.path.basename(self.fname), self.name, msg))
218 def read(self, size, offset):
219 self.log('read(%r, %r)' % (size, offset, ))
220 self.file.seek(offset)
221 return self.file.read(size)
224 def write(self, buf, offset):
225 self.log("write(-%s-, %r)" % (len(buf), offset))
226 if not self.open_for_write:
228 self.file.seek(offset)
233 def release(self, flags):
234 self.log("release(%r)" % (flags,))
236 if self.open_for_write:
237 size = os.path.getsize(self.fname)
238 self.fnode.size = size
239 file_cap = self.tfs.upload(self.fname)
240 self.fnode.ro_uri = file_cap
241 # XXX [ ] TODO: set metadata
242 # write new uri into parent dir entry
243 self.parent.add_child(self.name, self.fnode, {})
244 self.log("uploaded: %s" % (file_cap,))
250 if 'w' in self.file.mode or 'a' in self.file.mode:
254 def fsync(self, isfsyncfile):
255 self.log("fsync(%r)" % (isfsyncfile,))
257 if isfsyncfile and hasattr(os, 'fdatasync'):
258 os.fdatasync(self.fd)
266 # cf. xmp_flush() in fusexmp_fh.c
267 os.close(os.dup(self.fd))
271 self.log("fgetattr()")
272 s = os.fstat(self.fd)
273 self.log("fgetattr() -> %r" % (s,))
277 def ftruncate(self, len):
278 self.log("ftruncate(%r)" % (len,))
279 self.file.truncate(len)
281 class TahoeFuse(fuse.Fuse):
283 def __init__(self, tfs, *args, **kw):
284 log("TF: __init__(%r, %r)" % (args, kw))
288 class MyFuseFile(TahoeFuseFile):
290 self.file_class = MyFuseFile
291 log("TF: file_class: %r" % (self.file_class,))
293 fuse.Fuse.__init__(self, *args, **kw)
296 #thread.start_new_thread(self.launch_reactor, ())
299 log("<TF> %s" % (msg, ))
302 def readlink(self, path):
303 self.log("readlink(%r)" % (path,))
304 node = self.tfs.get_path(path)
306 raise EINVAL('Not a symlink') # nothing in tahoe is a symlink
308 raise ENOENT('Invalid argument')
311 def unlink(self, path):
312 self.log("unlink(%r)" % (path,))
313 self.tfs.unlink(path)
316 def rmdir(self, path):
317 self.log("rmdir(%r)" % (path,))
318 self.tfs.unlink(path)
321 def symlink(self, path, path1):
322 self.log("symlink(%r, %r)" % (path, path1))
323 self.tfs.link(path, path1)
326 def rename(self, path, path1):
327 self.log("rename(%r, %r)" % (path, path1))
328 self.tfs.rename(path, path1)
331 def link(self, path, path1):
332 self.log("link(%r, %r)" % (path, path1))
333 self.tfs.link(path, path1)
336 def chmod(self, path, mode):
337 self.log("XX chmod(%r, %r)" % (path, mode))
338 #return -errno.EOPNOTSUPP
341 def chown(self, path, user, group):
342 self.log("XX chown(%r, %r, %r)" % (path, user, group))
343 #return -errno.EOPNOTSUPP
346 def truncate(self, path, len):
347 self.log("XX truncate(%r, %r)" % (path, len))
348 #return -errno.EOPNOTSUPP
351 def utime(self, path, times):
352 self.log("XX utime(%r, %r)" % (path, times))
353 #return -errno.EOPNOTSUPP
359 Should return an object with statvfs attributes (f_bsize, f_frsize...).
360 Eg., the return value of os.statvfs() is such a thing (since py 2.2).
361 If you are not reusing an existing statvfs object, start with
362 fuse.StatVFS(), and define the attributes.
364 To provide usable information (ie., you want sensible df(1)
365 output, you are suggested to specify the following attributes:
367 - f_bsize - preferred size of file blocks, in bytes
368 - f_frsize - fundamental size of file blcoks, in bytes
369 [if you have no idea, use the same as blocksize]
370 - f_blocks - total number of blocks in the filesystem
371 - f_bfree - number of free blocks
372 - f_files - total number of file inodes
373 - f_ffree - nunber of free file inodes
376 block_size = 4096 # 4k
377 preferred_block_size = 131072 # 128k, c.f. seg_size
378 fs_size = 8*2**40 # 8Tb
379 fs_free = 2*2**40 # 2Tb
381 s = fuse.StatVfs(f_bsize = preferred_block_size,
382 f_frsize = block_size,
383 f_blocks = fs_size / block_size,
384 f_bfree = fs_free / block_size,
385 f_bavail = fs_free / block_size,
386 f_files = 2**30, # total files
387 f_ffree = 2**20, # available files
388 f_favail = 2**20, # available files (root)
389 f_flag = 2, # no suid
390 f_namemax = 255) # max name length
396 def main(self, *a, **kw):
397 self.log("main(%r, %r)" % (a, kw))
399 return fuse.Fuse.main(self, *a, **kw)
401 ##################################################################
404 def readdir(self, path, offset):
405 log('readdir(%r, %r)' % (path, offset))
406 node = self.tfs.get_path(path)
409 dirlist = ['.', '..'] + node.children.keys()
410 log('dirlist = %r' % (dirlist,))
411 return [fuse.Direntry(d) for d in dirlist]
414 def getattr(self, path):
415 log('getattr(%r)' % (path,))
418 # we don't have any metadata for the root (no edge leading to it)
419 mode = (stat.S_IFDIR | 755)
420 mtime = self.tfs.root.mtime
421 s = TStat({}, st_mode=mode, st_nlink=1, st_mtime=mtime)
422 log('getattr(%r) -> %r' % (path, s))
425 parent, name, child = self.tfs.get_parent_name_and_child(path)
426 if not child: # implicitly 'or not parent'
427 raise ENOENT('No such file or directory')
428 return parent.get_stat(name)
431 def access(self, path, mode):
432 self.log("access(%r, %r)" % (path, mode))
433 node = self.tfs.get_path(path)
436 accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
438 if not node.writable():
439 log('write access denied for %s (req:%o)' % (path, mode, ))
442 #log('access granted for %s' % (path, ))
445 def mkdir(self, path, mode):
446 self.log("mkdir(%r, %r)" % (path, mode))
449 def launch_tahoe_fuse(tfs, argv):
450 sys.argv = ['tahoe fuse'] + list(argv)
451 log('setting sys.argv=%r' % (sys.argv,))
452 config = TahoeFuseOptions()
453 server = TahoeFuse(tfs, version="%prog " +VERSIONSTR+", fuse "+ fuse.__version__,
454 usage=config.getSynopsis(),
455 dash_s_do='setsingle')
456 server.parse(errex=1)
459 def getnodeurl(nodedir):
460 f = file(os.path.expanduser(os.path.join(nodedir, "node.url")), 'rb')
461 nu = f.read().strip()
467 def fingerprint(uri):
470 return base64.b32encode(sha.new(uri).digest()).lower()[:6]
472 class TStat(fuse.Stat):
473 # in fuse 0.2, these are set by fuse.Stat.__init__
474 # in fuse 0.2-pre3 (hardy) they are not. badness unsues if they're missing
486 fields = [ 'st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', 'st_gid', 'st_size',
487 'st_atime', 'st_mtime', 'st_ctime', ]
488 def __init__(self, metadata, **kwargs):
489 # first load any stat fields present in 'metadata'
490 for st in [ 'mtime', 'ctime' ]:
492 setattr(self, "st_%s" % st, metadata[st])
493 for st in self.fields:
495 setattr(self, st, metadata[st])
497 # then set any values passed in as kwargs
498 fuse.Stat.__init__(self, **kwargs)
502 for f in self.fields:
503 d[f] = getattr(self, f, None)
504 return "<Stat%r>" % (d,)
506 class Directory(object):
507 def __init__(self, tfs, ro_uri, rw_uri):
511 assert (rw_uri or ro_uri)
513 self.last_load = None
514 self.last_data = None
518 return "<Directory %s>" % (fingerprint(self.get_uri()),)
520 def maybe_refresh(self, name=None):
522 if the previously cached data was retrieved within the cache
523 validity period, does nothing. otherwise refetches the data
524 for this directory and reloads itself
527 if self.last_load is None or (now - self.last_load) > self.tfs.cache_validity:
530 def load(self, name=None):
532 print 'loading', name or self
533 log('%s.loading(%s)' % (self, name))
534 url = self.tfs.compose_url("uri/%s?t=json", self.get_uri())
535 data = urllib.urlopen(url).read()
536 h = tagged_hash('cache_hash', data)
537 if h == self.last_data:
539 log('%s.load() : no change h(data)=%s' % (self, base32.b2a(h), ))
542 parsed = simplejson.loads(data)
544 log('%s.load(): unable to parse json data for dir:\n%r' % (self, data))
547 assert nodetype == 'dirnode'
548 self.children.clear()
549 for cname,details in d['children'].items():
550 cname = unicode_to_utf8(cname)
551 ctype, cattrs = details
552 metadata = cattrs.get('metadata', {})
553 if ctype == 'dirnode':
554 cobj = self.tfs.dir_for(cname, cattrs.get('ro_uri'), cattrs.get('rw_uri'))
556 assert ctype == "filenode"
557 cobj = File(cattrs.get('size'), cattrs.get('ro_uri'))
558 self.children[cname] = cobj, metadata
562 log('%s.load() loaded: \n%s' % (self, self.pprint(),))
564 def get_children(self):
565 return self.children.keys()
567 def get_child(self, name):
568 return self.children[name][0]
570 def add_child(self, name, child, metadata):
571 log('%s.add_child(%r, %r, %r)' % (self, name, child, metadata, ))
572 self.children[name] = child, metadata
573 url = self.tfs.compose_url("uri/%s/%s?t=uri", self.get_uri(), name)
574 child_cap = do_http('PUT', url, child.get_uri())
575 # XXX [ ] TODO: push metadata to tahoe node
576 assert child_cap == child.get_uri()
577 self.mtime = time.time()
578 log('added child %r with %r to %r' % (name, child_cap, self))
580 def remove_child(self, name):
581 log('%s.remove_child(%r)' % (self, name, ))
582 del self.children[name]
583 url = self.tfs.compose_url("uri/%s/%s", self.get_uri(), name)
584 resp = do_http('DELETE', url)
585 self.mtime = time.time()
586 log('child (%s) removal yielded %r' % (name, resp,))
589 return self.rw_uri or self.ro_uri
592 return self.rw_uri and self.rw_uri != self.ro_uri
594 def pprint(self, prefix='', printed=None, suffix=''):
598 writable = self.writable() and '+' or ' '
600 ret.append(" %s/%s ... <%s> : %s" % (prefix, writable, fingerprint(self.get_uri()), suffix, ))
602 ret.append("[%s] %s/%s : %s" % (fingerprint(self.get_uri()), prefix, writable, suffix, ))
604 for name,(child,metadata) in sorted(self.children.items()):
605 ret.append(child.pprint(' ' * (len(prefix)+1)+name, printed, repr(metadata)))
606 return '\n'.join(ret)
608 def get_metadata(self, name):
609 return self.children[name][1]
611 def get_stat(self, name):
612 child,metadata = self.children[name]
613 log("%s.get_stat(%s) md: %r" % (self, name, metadata))
615 if isinstance(child, Directory):
616 child.maybe_refresh(name)
617 mode = metadata.get('st_mode') or (stat.S_IFDIR | 0755)
618 s = TStat(metadata, st_mode=mode, st_nlink=1, st_mtime=child.mtime)
620 if hasattr(child, 'tmp_fname'):
621 s = os.stat(child.tmp_fname)
622 log("%s.get_stat(%s) returning local stat of tmp file" % (self, name, ))
626 st_size = child.size,
627 st_mode = metadata.get('st_mode') or (stat.S_IFREG | 0444),
628 st_mtime = metadata.get('mtime') or self.mtime,
632 log("%s.get_stat(%s)->%s" % (self, name, s))
636 def __init__(self, size, ro_uri):
643 return "<File %s>" % (fingerprint(self.ro_uri) or [self.tmp_fname],)
645 def pprint(self, prefix='', printed=None, suffix=''):
646 return " %s (%s) : %s" % (prefix, self.size, suffix, )
655 def __init__(self, nodedir, nodeurl, root_uri,
656 cache_validity_period=DEFAULT_DIRECTORY_VALIDITY):
657 self.cache_validity = cache_validity_period
658 self.nodeurl = nodeurl
659 self.root_uri = root_uri
662 cachedir = os.path.expanduser(os.path.join(nodedir, '_cache'))
663 self.cache = FileCache(nodeurl, cachedir)
664 ro_uri = NewDirectoryURI.init_from_string(self.root_uri).get_readonly()
665 self.root = Directory(self, ro_uri, self.root_uri)
666 self.root.maybe_refresh('<root>')
669 log("<TFS> %s" % (msg, ))
672 return self.root.pprint()
674 def compose_url(self, fmt, *args):
675 return self.nodeurl + (fmt % tuple(map(urllib.quote, args)))
677 def get_parent_name_and_child(self, path):
679 find the parent dir node, name of child relative to that parent, and
680 child node within the TFS object space.
681 @returns: (parent, name, child) if the child is found
682 (parent, name, None) if the child is missing from the parent
683 (None, name, None) if the parent is not found
687 dirname, name = os.path.split(path)
688 parent = self.get_path(dirname)
691 child = parent.get_child(name)
692 return parent, name, child
694 return parent, name, None
696 return None, name, None
698 def get_path(self, path):
699 comps = path.strip('/').split('/')
705 if not isinstance(cursor, Directory):
706 self.log('path "%s" is not a dir' % (path,))
708 cursor.maybe_refresh(c_name)
710 cursor = cursor.get_child(comp)
713 self.log('path "%s" not found' % (path,))
715 if isinstance(cursor, Directory):
716 cursor.maybe_refresh(c_name)
719 def dir_for(self, name, ro_uri, rw_uri):
720 #self.log('dir_for(%s) [%s/%s]' % (name, fingerprint(ro_uri), fingerprint(rw_uri)))
725 uri = rw_uri or ro_uri
727 dirobj = self.dirs.get(uri)
729 self.log('dir_for(%s) creating new Directory' % (name, ))
730 dirobj = Directory(self, ro_uri, rw_uri)
731 self.dirs[uri] = dirobj
734 def upload(self, fname):
735 self.log('upload(%r)' % (fname,))
736 fh = file(fname, 'rb')
737 url = self.compose_url("uri")
738 file_cap = do_http('PUT', url, fh)
739 self.log('uploaded to: %r' % (file_cap,))
742 def mkdir(self, path):
743 self.log('mkdir(%r)' % (path,))
744 parent, name, child = self.get_parent_name_and_child(path)
747 raise EEXIST('File exists: %s' % (name,))
749 raise ENOENT('No such file or directory: %s' % (path,))
751 url = self.compose_url("uri?t=mkdir")
752 new_dir_cap = do_http('PUT', url)
754 ro_uri = NewDirectoryURI.init_from_string(new_dir_cap).get_readonly()
755 child = Directory(self, ro_uri, new_dir_cap)
756 parent.add_child(name, child, {})
758 def rename(self, path, path1):
759 self.log('rename(%s, %s)' % (path, path1))
760 src_parent, src_name, src_child = self.get_parent_name_and_child(path)
761 dst_parent, dst_name, dst_child = self.get_parent_name_and_child(path1)
763 if not src_child or not dst_parent:
764 raise ENOENT('No such file or directory')
766 dst_parent.add_child(dst_name, src_child, {})
767 src_parent.remove_child(src_name)
769 def unlink(self, path):
770 parent, name, child = self.get_parent_name_and_child(path)
772 if child is None: # parent or child is missing
773 raise ENOENT('No such file or directory')
774 if not parent.writable():
775 raise EACCESS('Permission denied')
777 parent.remove_child(name)
779 def link(self, path, path1):
780 src = self.get_path(path)
781 dst_parent, dst_name, dst_child = self.get_parent_name_and_child(path1)
784 raise ENOENT('No such file or directory')
785 if dst_parent is None:
786 raise ENOENT('No such file or directory')
787 if not dst_parent.writable():
788 raise EACCESS('Permission denied')
790 dst_parent.add_child(dst_name, src, {})
792 class FileCache(object):
793 def __init__(self, nodeurl, cachedir):
794 self.nodeurl = nodeurl
795 self.cachedir = cachedir
796 if not os.path.exists(self.cachedir):
797 os.makedirs(self.cachedir)
798 self.tmpdir = os.path.join(self.cachedir, 'tmp')
799 if not os.path.exists(self.tmpdir):
800 os.makedirs(self.tmpdir)
803 log("<FC> %s" % (msg, ))
805 def get_file(self, uri):
806 self.log('get_file(%s)' % (uri,))
807 if uri.startswith("URI:LIT"):
808 return self.get_literal(uri)
810 return self.get_chk(uri)
812 def get_literal(self, uri):
813 h = sha.new(uri).digest()
814 u = LiteralFileURI.init_from_string(uri)
815 fname = os.path.join(self.cachedir, '__'+base64.b32encode(h).lower())
817 self.log('writing literal file %s (%s)' % (fname, size, ))
818 fh = open(fname, 'wb')
823 def get_chk(self, uri):
824 u = CHKFileURI.init_from_string(str(uri))
825 storage_index = u.storage_index
827 fname = os.path.join(self.cachedir, base64.b32encode(storage_index).lower())
828 if os.path.exists(fname):
829 fsize = os.path.getsize(fname)
833 self.log('warning file "%s" is too short %s < %s' % (fname, fsize, size))
834 self.log('downloading file %s (%s)' % (fname, size, ))
835 fh = open(fname, 'wb')
836 url = "%suri/%s" % (self.nodeurl, uri)
837 download = urllib.urlopen(''.join([ self.nodeurl, "uri/", uri ]))
839 chunk = download.read(4096)
846 def tmp_file(self, id):
847 fname = os.path.join(self.tmpdir, base64.b32encode(id).lower())
850 _tfs = None # to appease pyflakes; is set in main()
852 log('tree:\n' + _tfs.pprint())
855 log("\n\nmain(%s)" % (argv,))
859 if len(argv) == 1 and argv[0] in ['-h', '--help', '--version']:
860 config = TahoeFuseOptions()
861 print >> sys.stderr, config
862 print >> sys.stderr, 'fuse usage follows:'
863 launch_tahoe_fuse(None, argv)
866 config = TahoeFuseOptions()
868 #print 'parsing', argv
869 config.parseOptions(argv)
870 except usage.error, e:
876 alias = config['alias']
877 #print 'looking for aliases in', config['node-directory']
878 aliases = get_aliases(os.path.expanduser(config['node-directory']))
879 if alias not in aliases:
880 raise usage.error('Alias %r not found' % (alias,))
881 root_uri = aliases[alias]
882 elif config['root-uri']:
883 root_uri = config['root-uri']
885 # test the uri for structural validity:
887 NewDirectoryURI.init_from_string(root_uri)
889 raise usage.error('root-uri must be a valid directory uri (not %r)' % (root_uri,))
891 raise usage.error('At least one of --alias or --root-uri must be specified')
894 nodedir = config['node-directory']
895 nodeurl = config['node-url']
897 nodeurl = getnodeurl(nodedir)
899 # switch to named log file.
901 fname = 'tfuse.%s.log' % (alias,)
902 log('switching to %s' % (fname,))
904 logfile = file(fname, 'ab')
905 log('\n'+(24*'_')+'init'+(24*'_')+'\n')
907 if not os.path.exists(config.mountpoint):
908 raise OSError(2, 'No such file or directory: "%s"' % (config.mountpoint,))
910 cache_timeout = float(config['cache-timeout'])
911 tfs = TFS(nodedir, nodeurl, root_uri, cache_timeout)
914 # make tfs instance accesible to print_tree() for dbg
918 args = [ '-o'+opt for opt in config.fuse_options ] + [config.mountpoint]
919 launch_tahoe_fuse(tfs, args)
921 if __name__ == '__main__':
922 sys.exit(main(sys.argv[1:]))