self.args = args
def fuse(config, stdout, stderr):
- import macfuse.tahoefuse
- macfuse.tahoefuse.main(config.args)
+ import tahoefuse
+ tahoefuse.main(config.args)
class FuseRunnerExtension(object):
subCommands = [
--- /dev/null
+#
+# Copyright (C) 2001 Jeff Epler <jepler@unpythonic.dhs.org>
+# Copyright (C) 2006 Csaba Henk <csaba.henk@creo.hu>
+#
+# This program can be distributed under the terms of the GNU LGPL.
+# See the file COPYING.
+#
+
+
+# suppress version mismatch warnings
+try:
+ import warnings
+ warnings.filterwarnings('ignore',
+ 'Python C API version mismatch',
+ RuntimeWarning,
+ )
+except:
+ pass
+
+from string import join
+import sys
+from errno import *
+from os import environ
+import re
+from fuseparts import __version__
+from fuseparts._fuse import main, FuseGetContext, FuseInvalidate
+from fuseparts._fuse import FuseError, FuseAPIVersion
+from fuseparts.subbedopts import SubOptsHive, SubbedOptFormatter
+from fuseparts.subbedopts import SubbedOptIndentedFormatter, SubbedOptParse
+from fuseparts.subbedopts import SUPPRESS_HELP, OptParseError
+from fuseparts.setcompatwrap import set
+
+
+##########
+###
+### API specification API.
+###
+##########
+
+# The actual API version of this module
+FUSE_PYTHON_API_VERSION = (0, 2)
+
+def __getenv__(var, pattern = '.', trans = lambda x: x):
+ """
+ Fetch enviroment variable and optionally transform it. Return `None` if
+ variable is unset. Bail out if value of variable doesn't match (optional)
+ regex pattern.
+ """
+
+ if var not in environ:
+ return None
+ val = environ[var]
+ rpat = pattern
+ if not isinstance(rpat, type(re.compile(''))):
+ rpat = re.compile(rpat)
+ if not rpat.search(val):
+ raise RuntimeError("env var %s doesn't match required pattern %s" % \
+ (var, `pattern`))
+ return trans(val)
+
+def get_fuse_python_api():
+ if fuse_python_api:
+ return fuse_python_api
+ elif compat_0_1:
+ # deprecated way of API specification
+ return (0,1)
+
+def get_compat_0_1():
+ return get_fuse_python_api() == (0, 1)
+
+# API version to be used
+fuse_python_api = __getenv__('FUSE_PYTHON_API', '^[\d.]+$',
+ lambda x: tuple([int(i) for i in x.split('.')]))
+
+# deprecated way of API specification
+compat_0_1 = __getenv__('FUSE_PYTHON_COMPAT', '^(0.1|ALL)$', lambda x: True)
+
+fuse_python_api = get_fuse_python_api()
+
+##########
+###
+### Parsing for FUSE.
+###
+##########
+
+
+
+class FuseArgs(SubOptsHive):
+ """
+ Class representing a FUSE command line.
+ """
+
+ fuse_modifiers = {'showhelp': '-ho',
+ 'showversion': '-V',
+ 'foreground': '-f'}
+
+ def __init__(self):
+
+ SubOptsHive.__init__(self)
+
+ self.modifiers = {}
+ self.mountpoint = None
+
+ for m in self.fuse_modifiers:
+ self.modifiers[m] = False
+
+ def __str__(self):
+ return '\n'.join(['< on ' + str(self.mountpoint) + ':',
+ ' ' + str(self.modifiers), ' -o ']) + \
+ ',\n '.join(self._str_core()) + \
+ ' >'
+
+ def getmod(self, mod):
+ return self.modifiers[mod]
+
+ def setmod(self, mod):
+ self.modifiers[mod] = True
+
+ def unsetmod(self, mod):
+ self.modifiers[mod] = False
+
+ def mount_expected(self):
+ if self.getmod('showhelp'):
+ return False
+ if self.getmod('showversion'):
+ return False
+ return True
+
+ def assemble(self):
+ """Mangle self into an argument array"""
+
+ self.canonify()
+ args = [sys.argv and sys.argv[0] or "python"]
+ if self.mountpoint:
+ args.append(self.mountpoint)
+ for m, v in self.modifiers.iteritems():
+ if v:
+ args.append(self.fuse_modifiers[m])
+
+ opta = []
+ for o, v in self.optdict.iteritems():
+ opta.append(o + '=' + v)
+ opta.extend(self.optlist)
+
+ if opta:
+ args.append("-o" + ",".join(opta))
+
+ return args
+
+ def filter(self, other=None):
+ """
+ Same as for SubOptsHive, with the following difference:
+ if other is not specified, `Fuse.fuseoptref()` is run and its result
+ will be used.
+ """
+
+ if not other:
+ other = Fuse.fuseoptref()
+
+ return SubOptsHive.filter(self, other)
+
+
+
+class FuseFormatter(SubbedOptIndentedFormatter):
+
+ def __init__(self, **kw):
+ if not 'indent_increment' in kw:
+ kw['indent_increment'] = 4
+ SubbedOptIndentedFormatter.__init__(self, **kw)
+
+ def store_option_strings(self, parser):
+ SubbedOptIndentedFormatter.store_option_strings(self, parser)
+ # 27 is how the lib stock help appears
+ self.help_position = max(self.help_position, 27)
+ self.help_width = self.width - self.help_position
+
+
+class FuseOptParse(SubbedOptParse):
+ """
+ This class alters / enhances `SubbedOptParse` so that it's
+ suitable for usage with FUSE.
+
+ - When adding options, you can use the `mountopt` pseudo-attribute which
+ is equivalent with adding a subopt for option ``-o``
+ (it doesn't require an option argument).
+
+ - FUSE compatible help and version printing.
+
+ - Error and exit callbacks are relaxed. In case of FUSE, the command
+ line is to be treated as a DSL [#]_. You don't wanna this module to
+ force an exit on you just because you hit a DSL syntax error.
+
+ - Built-in support for conventional FUSE options (``-d``, ``-f`, ``-s``).
+ The way of this can be tuned by keyword arguments, see below.
+
+ .. [#] http://en.wikipedia.org/wiki/Domain-specific_programming_language
+
+ Keyword arguments for initialization
+ ------------------------------------
+
+ standard_mods
+ Boolean [default is `True`].
+ Enables support for the usual interpretation of the ``-d``, ``-f``
+ options.
+
+ fetch_mp
+ Boolean [default is `True`].
+ If it's True, then the last (non-option) argument
+ (if there is such a thing) will be used as the FUSE mountpoint.
+
+ dash_s_do
+ String: ``whine``, ``undef``, or ``setsingle`` [default is ``whine``].
+ The ``-s`` option -- traditionally for asking for single-threadedness --
+ is an oddball: single/multi threadedness of a fuse-py fs doesn't depend
+ on the FUSE command line, we have direct control over it.
+
+ Therefore we have two conflicting principles:
+
+ - *Orthogonality*: option parsing shouldn't affect the backing `Fuse`
+ instance directly, only via its `fuse_args` attribute.
+
+ - *POLS*: behave like other FUSE based fs-es do. The stock FUSE help
+ makes mention of ``-s`` as a single-threadedness setter.
+
+ So, if we follow POLS and implement a conventional ``-s`` option, then
+ we have to go beyond the `fuse_args` attribute and set the respective
+ Fuse attribute directly, hence violating orthogonality.
+
+ We let the fs authors make their choice: ``dash_s_do=undef`` leaves this
+ option unhandled, and the fs author can add a handler as she desires.
+ ``dash_s_do=setsingle`` enables the traditional behaviour.
+
+ Using ``dash_s_do=setsingle`` is not problematic at all, but we want fs
+ authors be aware of the particularity of ``-s``, therefore the default is
+ the ``dash_s_do=whine`` setting which raises an exception for ``-s`` and
+ suggests the user to read this documentation.
+
+ dash_o_handler
+ Argument should be a SubbedOpt instance (created with
+ ``action="store_hive"`` if you want it to be useful).
+ This lets you customize the handler of the ``-o`` option. For example,
+ you can alter or suppress the generic ``-o`` entry in help output.
+ """
+
+ def __init__(self, *args, **kw):
+
+ self.mountopts = []
+
+ self.fuse_args = \
+ 'fuse_args' in kw and kw.pop('fuse_args') or FuseArgs()
+ dsd = 'dash_s_do' in kw and kw.pop('dash_s_do') or 'whine'
+ if 'fetch_mp' in kw:
+ self.fetch_mp = bool(kw.pop('fetch_mp'))
+ else:
+ self.fetch_mp = True
+ if 'standard_mods' in kw:
+ smods = bool(kw.pop('standard_mods'))
+ else:
+ smods = True
+ if 'fuse' in kw:
+ self.fuse = kw.pop('fuse')
+ if not 'formatter' in kw:
+ kw['formatter'] = FuseFormatter()
+ doh = 'dash_o_handler' in kw and kw.pop('dash_o_handler')
+
+ SubbedOptParse.__init__(self, *args, **kw)
+
+ if doh:
+ self.add_option(doh)
+ else:
+ self.add_option('-o', action='store_hive',
+ subopts_hive=self.fuse_args, help="mount options",
+ metavar="opt,[opt...]")
+
+ if smods:
+ self.add_option('-f', action='callback',
+ callback=lambda *a: self.fuse_args.setmod('foreground'),
+ help=SUPPRESS_HELP)
+ self.add_option('-d', action='callback',
+ callback=lambda *a: self.fuse_args.add('debug'),
+ help=SUPPRESS_HELP)
+
+ if dsd == 'whine':
+ def dsdcb(option, opt_str, value, parser):
+ raise RuntimeError, """
+
+! If you want the "-s" option to work, pass
+!
+! dash_s_do='setsingle'
+!
+! to the Fuse constructor. See docstring of the FuseOptParse class for an
+! explanation why is it not set by default.
+"""
+
+ elif dsd == 'setsingle':
+ def dsdcb(option, opt_str, value, parser):
+ self.fuse.multithreaded = False
+
+ elif dsd == 'undef':
+ dsdcb = None
+ else:
+ raise ArgumentError, "key `dash_s_do': uninterpreted value " + str(dsd)
+
+ if dsdcb:
+ self.add_option('-s', action='callback', callback=dsdcb,
+ help=SUPPRESS_HELP)
+
+
+ def exit(self, status=0, msg=None):
+ if msg:
+ sys.stderr.write(msg)
+
+ def error(self, msg):
+ SubbedOptParse.error(self, msg)
+ raise OptParseError, msg
+
+ def print_help(self, file=sys.stderr):
+ SubbedOptParse.print_help(self, file)
+ print >> file
+ self.fuse_args.setmod('showhelp')
+
+ def print_version(self, file=sys.stderr):
+ SubbedOptParse.print_version(self, file)
+ self.fuse_args.setmod('showversion')
+
+ def parse_args(self, args=None, values=None):
+ o, a = SubbedOptParse.parse_args(self, args, values)
+ if a and self.fetch_mp:
+ self.fuse_args.mountpoint = a.pop()
+ return o, a
+
+ def add_option(self, *opts, **attrs):
+ if 'mountopt' in attrs:
+ if opts or 'subopt' in attrs:
+ raise OptParseError(
+ "having options or specifying the `subopt' attribute conflicts with `mountopt' attribute")
+ opts = ('-o',)
+ attrs['subopt'] = attrs.pop('mountopt')
+ if not 'dest' in attrs:
+ attrs['dest'] = attrs['subopt']
+
+ SubbedOptParse.add_option(self, *opts, **attrs)
+
+
+
+##########
+###
+### The FUSE interface.
+###
+##########
+
+
+
+class ErrnoWrapper(object):
+
+ def __init__(self, func):
+ self.func = func
+
+ def __call__(self, *args, **kw):
+ try:
+ return apply(self.func, args, kw)
+ except (IOError, OSError), detail:
+ # Sometimes this is an int, sometimes an instance...
+ if hasattr(detail, "errno"): detail = detail.errno
+ return -detail
+
+
+########### Custom objects for transmitting system structures to FUSE
+
+class FuseStruct(object):
+
+ def __init__(self, **kw):
+ for k in kw:
+ setattr(self, k, kw[k])
+
+
+class Stat(FuseStruct):
+ """
+ Auxiliary class which can be filled up stat attributes.
+ The attributes are undefined by default.
+ """
+
+ def __init__(self, **kw):
+ self.st_mode = None
+ self.st_ino = 0
+ self.st_dev = 0
+ self.st_nlink = None
+ self.st_uid = 0
+ self.st_gid = 0
+ self.st_size = 0
+ self.st_atime = 0
+ self.st_mtime = 0
+ self.st_ctime = 0
+
+ FuseStruct.__init__(self, **kw)
+
+
+class StatVfs(FuseStruct):
+ """
+ Auxiliary class which can be filled up statvfs attributes.
+ The attributes are 0 by default.
+ """
+
+ def __init__(self, **kw):
+
+ self.f_bsize = 0
+ self.f_frsize = 0
+ self.f_blocks = 0
+ self.f_bfree = 0
+ self.f_bavail = 0
+ self.f_files = 0
+ self.f_ffree = 0
+ self.f_favail = 0
+ self.f_flag = 0
+ self.f_namemax = 0
+
+ FuseStruct.__init__(self, **kw)
+
+
+class Direntry(FuseStruct):
+ """
+ Auxiliary class for carrying directory entry data.
+ Initialized with `name`. Further attributes (each
+ set to 0 as default):
+
+ offset
+ An integer (or long) parameter, used as a bookmark
+ during directory traversal.
+ This needs to be set it you want stateful directory
+ reading.
+
+ type
+ Directory entry type, should be one of the stat type
+ specifiers (stat.S_IFLNK, stat.S_IFBLK, stat.S_IFDIR,
+ stat.S_IFCHR, stat.S_IFREG, stat.S_IFIFO, stat.S_IFSOCK).
+
+ ino
+ Directory entry inode number.
+
+ Note that Python's standard directory reading interface is
+ stateless and provides only names, so the above optional
+ attributes doesn't make sense in that context.
+ """
+
+ def __init__(self, name, **kw):
+
+ self.name = name
+ self.offset = 0
+ self.type = 0
+ self.ino = 0
+
+ FuseStruct.__init__(self, **kw)
+
+
+class Flock(FuseStruct):
+ """
+ Class for representing flock structures (cf. fcntl(3)).
+
+ It makes sense to give values to the `l_type`, `l_start`,
+ `l_len`, `l_pid` attributes (`l_whence` is not used by
+ FUSE, see ``fuse.h``).
+ """
+
+ def __init__(self, name, **kw):
+
+ self.l_type = None
+ self.l_start = None
+ self.l_len = None
+ self.l_pid = None
+
+ FuseStruct.__init__(self, **kw)
+
+
+class Timespec(FuseStruct):
+ """
+ Cf. struct timespec in time.h:
+ http://www.opengroup.org/onlinepubs/009695399/basedefs/time.h.html
+ """
+
+ def __init__(self, name, **kw):
+
+ self.tv_sec = None
+ self.tv_nsec = None
+
+ FuseStruct.__init__(self, **kw)
+
+
+class FuseFileInfo(FuseStruct):
+
+ def __init__(self, **kw):
+
+ self.keep = False
+ self.direct_io = False
+
+ FuseStruct.__init__(self, **kw)
+
+
+
+########## Interface for requiring certain features from your underlying FUSE library.
+
+def feature_needs(*feas):
+ """
+ Get info about the FUSE API version needed for the support of some features.
+
+ This function takes a variable number of feature patterns.
+
+ A feature pattern is either:
+
+ - an integer (directly referring to a FUSE API version number)
+ - a built-in feature specifier string (meaning defined by dictionary)
+ - a string of the form ``has_foo``, where ``foo`` is a filesystem method
+ (refers to the API version where the method has been introduced)
+ - a list/tuple of other feature patterns (matches each of its members)
+ - a regexp (meant to be matched against the builtins plus ``has_foo``
+ patterns; can also be given by a string of the from "re:*")
+ - a negated regexp (can be given by a string of the form "!re:*")
+
+ If called with no arguments, then the list of builtins is returned, mapped
+ to their meaning.
+
+ Otherwise the function returns the smallest FUSE API version number which
+ has all the matching features.
+
+ Builtin specifiers worth to explicit mention:
+ - ``stateful_files``: you want to use custom filehandles (eg. a file class).
+ - ``*``: you want all features.
+ - while ``has_foo`` makes sense for all filesystem method ``foo``, some
+ of these can be found among the builtins, too (the ones which can be
+ handled by the general rule).
+
+ specifiers like ``has_foo`` refer to requirement that the library knows of
+ the fs method ``foo``.
+ """
+
+ fmap = {'stateful_files': 22,
+ 'stateful_dirs': 23,
+ 'stateful_io': ('stateful_files', 'stateful_dirs'),
+ 'stateful_files_keep_cache': 23,
+ 'stateful_files_direct_io': 23,
+ 'keep_cache': ('stateful_files_keep_cache',),
+ 'direct_io': ('stateful_files_direct_io',),
+ 'has_opendir': ('stateful_dirs',),
+ 'has_releasedir': ('stateful_dirs',),
+ 'has_fsyncdir': ('stateful_dirs',),
+ 'has_create': 25,
+ 'has_access': 25,
+ 'has_fgetattr': 25,
+ 'has_ftruncate': 25,
+ 'has_fsinit': ('has_init'),
+ 'has_fsdestroy': ('has_destroy'),
+ 'has_lock': 26,
+ 'has_utimens': 26,
+ 'has_bmap': 26,
+ 'has_init': 23,
+ 'has_destroy': 23,
+ '*': '!re:^\*$'}
+
+ if not feas:
+ return fmap
+
+ def resolve(args, maxva):
+
+ for fp in args:
+ if isinstance(fp, int):
+ maxva[0] = max(maxva[0], fp)
+ continue
+ if isinstance(fp, list) or isinstance(fp, tuple):
+ for f in fp:
+ yield f
+ continue
+ ma = isinstance(fp, str) and re.compile("(!\s*|)re:(.*)").match(fp)
+ if isinstance(fp, type(re.compile(''))) or ma:
+ neg = False
+ if ma:
+ mag = ma.groups()
+ fp = re.compile(mag[1])
+ neg = bool(mag[0])
+ for f in fmap.keys() + [ 'has_' + a for a in Fuse._attrs ]:
+ if neg != bool(re.search(fp, f)):
+ yield f
+ continue
+ ma = re.compile("has_(.*)").match(fp)
+ if ma and ma.groups()[0] in Fuse._attrs and not fp in fmap:
+ yield 21
+ continue
+ yield fmap[fp]
+
+ maxva = [0]
+ while feas:
+ feas = set(resolve(feas, maxva))
+
+ return maxva[0]
+
+
+def APIVersion():
+ """Get the API version of your underlying FUSE lib"""
+
+ return FuseAPIVersion()
+
+
+def feature_assert(*feas):
+ """
+ Takes some feature patterns (like in `feature_needs`).
+ Raises a fuse.FuseError if your underlying FUSE lib fails
+ to have some of the matching features.
+
+ (Note: use a ``has_foo`` type feature assertion only if lib support
+ for method ``foo`` is *necessary* for your fs. Don't use this assertion
+ just because your fs implements ``foo``. The usefulness of ``has_foo``
+ is limited by the fact that we can't guarantee that your FUSE kernel
+ module also supports ``foo``.)
+ """
+
+ fav = APIVersion()
+
+ for fea in feas:
+ fn = feature_needs(fea)
+ if fav < fn:
+ raise FuseError(
+ "FUSE API version %d is required for feature `%s' but only %d is available" % \
+ (fn, str(fea), fav))
+
+
+############# Subclass this.
+
+class Fuse(object):
+ """
+ Python interface to FUSE.
+
+ Basic usage:
+
+ - instantiate
+
+ - add options to `parser` attribute (an instance of `FuseOptParse`)
+
+ - call `parse`
+
+ - call `main`
+ """
+
+ _attrs = ['getattr', 'readlink', 'readdir', 'mknod', 'mkdir',
+ 'unlink', 'rmdir', 'symlink', 'rename', 'link', 'chmod',
+ 'chown', 'truncate', 'utime', 'open', 'read', 'write', 'release',
+ 'statfs', 'fsync', 'create', 'opendir', 'releasedir', 'fsyncdir',
+ 'flush', 'fgetattr', 'ftruncate', 'getxattr', 'listxattr',
+ 'setxattr', 'removexattr', 'access', 'lock', 'utimens', 'bmap',
+ 'fsinit', 'fsdestroy']
+
+ fusage = "%prog [mountpoint] [options]"
+
+ def __init__(self, *args, **kw):
+ """
+ Not much happens here apart from initializing the `parser` attribute.
+ Arguments are forwarded to the constructor of the parser class almost
+ unchanged.
+
+ The parser class is `FuseOptParse` unless you specify one using the
+ ``parser_class`` keyword. (See `FuseOptParse` documentation for
+ available options.)
+ """
+
+ if not fuse_python_api:
+ raise RuntimeError, __name__ + """.fuse_python_api not defined.
+
+! Please define """ + __name__ + """.fuse_python_api internally (eg.
+!
+! (1) """ + __name__ + """.fuse_python_api = """ + `FUSE_PYTHON_API_VERSION` + """
+!
+! ) or in the enviroment (eg.
+!
+! (2) FUSE_PYTHON_API=0.1
+!
+! ).
+!
+! If you are actually developing a filesystem, probably (1) is the way to go.
+! If you are using a filesystem written before 2007 Q2, probably (2) is what
+! you want."
+"""
+
+ def malformed():
+ raise RuntimeError, \
+ "malformatted fuse_python_api value " + `fuse_python_api`
+ if not isinstance(fuse_python_api, tuple):
+ malformed()
+ for i in fuse_python_api:
+ if not isinstance(i, int) or i < 0:
+ malformed()
+
+ if fuse_python_api > FUSE_PYTHON_API_VERSION:
+ raise RuntimeError, """
+! You require FUSE-Python API version """ + `fuse_python_api` + """.
+! However, the latest available is """ + `FUSE_PYTHON_API_VERSION` + """.
+"""
+
+ self.fuse_args = \
+ 'fuse_args' in kw and kw.pop('fuse_args') or FuseArgs()
+
+ if get_compat_0_1():
+ return self.__init_0_1__(*args, **kw)
+
+ self.multithreaded = True
+
+ if not 'usage' in kw:
+ kw['usage'] = self.fusage
+ if not 'fuse_args' in kw:
+ kw['fuse_args'] = self.fuse_args
+ kw['fuse'] = self
+ parserclass = \
+ 'parser_class' in kw and kw.pop('parser_class') or FuseOptParse
+
+ self.parser = parserclass(*args, **kw)
+ self.methproxy = self.Methproxy()
+
+ def parse(self, *args, **kw):
+ """Parse command line, fill `fuse_args` attribute."""
+
+ ev = 'errex' in kw and kw.pop('errex')
+ if ev and not isinstance(ev, int):
+ raise TypeError, "error exit value should be an integer"
+
+ try:
+ self.cmdline = self.parser.parse_args(*args, **kw)
+ except OptParseError:
+ if ev:
+ sys.exit(ev)
+ raise
+
+ return self.fuse_args
+
+ def main(self, args=None):
+ """Enter filesystem service loop."""
+
+ if get_compat_0_1():
+ args = self.main_0_1_preamble()
+
+ d = {'multithreaded': self.multithreaded and 1 or 0}
+ d['fuse_args'] = args or self.fuse_args.assemble()
+
+ for t in 'file_class', 'dir_class':
+ if hasattr(self, t):
+ getattr(self.methproxy, 'set_' + t)(getattr(self,t))
+
+ for a in self._attrs:
+ b = a
+ if get_compat_0_1() and a in self.compatmap:
+ b = self.compatmap[a]
+ if hasattr(self, b):
+ c = ''
+ if get_compat_0_1() and hasattr(self, a + '_compat_0_1'):
+ c = '_compat_0_1'
+ d[a] = ErrnoWrapper(self.lowwrap(a + c))
+
+ try:
+ main(**d)
+ except FuseError:
+ if args or self.fuse_args.mount_expected():
+ raise
+
+ def lowwrap(self, fname):
+ """
+ Wraps the fname method when the C code expects a different kind of
+ callback than we have in the fusepy API. (The wrapper is usually for
+ performing some checks or transfromations which could be done in C but
+ is simpler if done in Python.)
+
+ Currently `open` and `create` are wrapped: a boolean flag is added
+ which indicates if the result is to be kept during the opened file's
+ lifetime or can be thrown away. Namely, it's considered disposable
+ if it's an instance of FuseFileInfo.
+ """
+ fun = getattr(self, fname)
+
+ if fname in ('open', 'create'):
+ def wrap(*a, **kw):
+ res = fun(*a, **kw)
+ if not res or type(res) == type(0):
+ return res
+ else:
+ return (res, type(res) != FuseFileInfo)
+ elif fname == 'utimens':
+ def wrap(path, acc_sec, acc_nsec, mod_sec, mod_nsec):
+ ts_acc = Timespec(tv_sec = acc_sec, tv_nsec = acc_nsec)
+ ts_mod = Timespec(tv_sec = mod_sec, tv_nsec = mod_nsec)
+ return fun(path, ts_acc, ts_mod)
+ else:
+ wrap = fun
+
+ return wrap
+
+ def GetContext(self):
+ return FuseGetContext(self)
+
+ def Invalidate(self, path):
+ return FuseInvalidate(self, path)
+
+ def fuseoptref(cls):
+ """
+ Find out which options are recognized by the library.
+ Result is a `FuseArgs` instance with the list of supported
+ options, suitable for passing on to the `filter` method of
+ another `FuseArgs` instance.
+ """
+
+ import os, re
+
+ pr, pw = os.pipe()
+ pid = os.fork()
+ if pid == 0:
+ os.dup2(pw, 2)
+ os.close(pr)
+
+ fh = cls()
+ fh.fuse_args = FuseArgs()
+ fh.fuse_args.setmod('showhelp')
+ fh.main()
+ sys.exit()
+
+ os.close(pw)
+
+ fa = FuseArgs()
+ ore = re.compile("-o\s+([\w\[\]]+(?:=\w+)?)")
+ fpr = os.fdopen(pr)
+ for l in fpr:
+ m = ore.search(l)
+ if m:
+ o = m.groups()[0]
+ oa = [o]
+ # try to catch two-in-one options (like "[no]foo")
+ opa = o.split("[")
+ if len(opa) == 2:
+ o1, ox = opa
+ oxpa = ox.split("]")
+ if len(oxpa) == 2:
+ oo, o2 = oxpa
+ oa = [o1 + o2, o1 + oo + o2]
+ for o in oa:
+ fa.add(o)
+
+ fpr.close()
+ return fa
+
+ fuseoptref = classmethod(fuseoptref)
+
+
+ class Methproxy(object):
+
+ def __init__(self):
+
+ class mpx(object):
+ def __init__(self, name):
+ self.name = name
+ def __call__(self, *a, **kw):
+ return getattr(a[-1], self.name)(*(a[1:-1]), **kw)
+
+ self.proxyclass = mpx
+ self.mdic = {}
+ self.file_class = None
+ self.dir_class = None
+
+ def __call__(self, meth):
+ return meth in self.mdic and self.mdic[meth] or None
+
+ def _add_class_type(cls, type, inits, proxied):
+
+ def setter(self, xcls):
+
+ setattr(self, type + '_class', xcls)
+
+ for m in inits:
+ self.mdic[m] = xcls
+
+ for m in proxied:
+ if hasattr(xcls, m):
+ self.mdic[m] = self.proxyclass(m)
+
+ setattr(cls, 'set_' + type + '_class', setter)
+
+ _add_class_type = classmethod(_add_class_type)
+
+ Methproxy._add_class_type('file', ('open', 'create'),
+ ('read', 'write', 'fsync', 'release', 'flush',
+ 'fgetattr', 'ftruncate', 'lock'))
+ Methproxy._add_class_type('dir', ('opendir',),
+ ('readdir', 'fsyncdir', 'releasedir'))
+
+
+ def __getattr__(self, meth):
+
+ m = self.methproxy(meth)
+ if m:
+ return m
+
+ raise AttributeError, "Fuse instance has no attribute '%s'" % meth
+
+
+
+##########
+###
+### Compat stuff.
+###
+##########
+
+
+
+ def __init_0_1__(self, *args, **kw):
+
+ self.flags = 0
+ multithreaded = 0
+
+ # default attributes
+ if args == ():
+ # there is a self.optlist.append() later on, make sure it won't
+ # bomb out.
+ self.optlist = []
+ else:
+ self.optlist = args
+ self.optdict = kw
+
+ if len(self.optlist) == 1:
+ self.mountpoint = self.optlist[0]
+ else:
+ self.mountpoint = None
+
+ # grab command-line arguments, if any.
+ # Those will override whatever parameters
+ # were passed to __init__ directly.
+ argv = sys.argv
+ argc = len(argv)
+ if argc > 1:
+ # we've been given the mountpoint
+ self.mountpoint = argv[1]
+ if argc > 2:
+ # we've received mount args
+ optstr = argv[2]
+ opts = optstr.split(",")
+ for o in opts:
+ try:
+ k, v = o.split("=", 1)
+ self.optdict[k] = v
+ except:
+ self.optlist.append(o)
+
+
+ def main_0_1_preamble(self):
+
+ cfargs = FuseArgs()
+
+ cfargs.mountpoint = self.mountpoint
+
+ if hasattr(self, 'debug'):
+ cfargs.add('debug')
+
+ if hasattr(self, 'allow_other'):
+ cfargs.add('allow_other')
+
+ if hasattr(self, 'kernel_cache'):
+ cfargs.add('kernel_cache')
+
+ return cfargs.assemble()
+
+
+ def getattr_compat_0_1(self, *a):
+ from os import stat_result
+
+ return stat_result(self.getattr(*a))
+
+
+ def statfs_compat_0_1(self, *a):
+
+ oout = self.statfs(*a)
+ lo = len(oout)
+
+ svf = StatVfs()
+ svf.f_bsize = oout[0] # 0
+ svf.f_frsize = oout[lo >= 8 and 7 or 0] # 1
+ svf.f_blocks = oout[1] # 2
+ svf.f_bfree = oout[2] # 3
+ svf.f_bavail = oout[3] # 4
+ svf.f_files = oout[4] # 5
+ svf.f_ffree = oout[5] # 6
+ svf.f_favail = lo >= 9 and oout[8] or 0 # 7
+ svf.f_flag = lo >= 10 and oout[9] or 0 # 8
+ svf.f_namemax = oout[6] # 9
+
+ return svf
+
+
+ def readdir_compat_0_1(self, path, offset, *fh):
+
+ for name, type in self.getdir(path):
+ de = Direntry(name)
+ de.type = type
+
+ yield de
+
+
+ compatmap = {'readdir': 'getdir'}
--- /dev/null
+__version__ = "0.2"
--- /dev/null
+try:
+ set()
+ set = set
+except:
+ from sets import Set as set
--- /dev/null
+#
+# Copyright (C) 2006 Csaba Henk <csaba.henk@creo.hu>
+#
+# This program can be distributed under the terms of the GNU LGPL.
+# See the file COPYING.
+#
+
+from optparse import Option, OptionParser, OptParseError, OptionConflictError
+from optparse import HelpFormatter, IndentedHelpFormatter, SUPPRESS_HELP
+from fuseparts.setcompatwrap import set
+
+##########
+###
+### Generic suboption parsing stuff.
+###
+##########
+
+
+
+class SubOptsHive(object):
+ """
+ Class for collecting unhandled suboptions.
+ """
+
+ def __init__(self):
+
+ self.optlist = set()
+ self.optdict = {}
+
+ def _str_core(self):
+
+ sa = []
+ for k, v in self.optdict.iteritems():
+ sa.append(str(k) + '=' + str(v))
+
+ ra = (list(self.optlist) + sa) or ["(none)"]
+ ra.sort()
+ return ra
+
+ def __str__(self):
+ return "< opts: " + ", ".join(self._str_core()) + " >"
+
+ def canonify(self):
+ """
+ Transform self to an equivalent canonical form:
+ delete optdict keys with False value, move optdict keys
+ with True value to optlist, stringify other values.
+ """
+
+ for k, v in self.optdict.iteritems():
+ if v == False:
+ self.optdict.pop(k)
+ elif v == True:
+ self.optdict.pop(k)
+ self.optlist.add(v)
+ else:
+ self.optdict[k] = str(v)
+
+ def filter(self, other):
+ """
+ Throw away those options which are not in the other one.
+ Returns a new instance with the rejected options.
+ """
+
+ self.canonify()
+ other.canonify()
+
+ rej = self.__class__()
+ rej.optlist = self.optlist.difference(other.optlist)
+ self.optlist.difference_update(rej.optlist)
+ for x in self.optdict.copy():
+ if x not in other.optdict:
+ self.optdict.pop(x)
+ rej.optdict[x] = None
+
+ return rej
+
+ def add(self, opt, val=None):
+ """Add a suboption."""
+
+ ov = opt.split('=', 1)
+ o = ov[0]
+ v = len(ov) > 1 and ov[1] or None
+
+ if (v):
+ if val != None:
+ raise AttributeError, "ambiguous option value"
+ val = v
+
+ if val == False:
+ return
+
+ if val in (None, True):
+ self.optlist.add(o)
+ else:
+ self.optdict[o] = val
+
+
+
+class SubbedOpt(Option):
+ """
+ `Option` derivative enhanced with the attribute of being a suboption of
+ some other option (like ``foo`` and ``bar`` for ``-o`` in ``-o foo,bar``).
+ """
+
+ ATTRS = Option.ATTRS + ["subopt", "subsep", "subopts_hive"]
+ ACTIONS = Option.ACTIONS + ("store_hive",)
+ STORE_ACTIONS = Option.STORE_ACTIONS + ("store_hive",)
+ TYPED_ACTIONS = Option.TYPED_ACTIONS + ("store_hive",)
+
+ def __init__(self, *opts, **attrs):
+
+ self.subopt_map = {}
+
+ if "subopt" in attrs:
+ self._short_opts = []
+ self._long_opts = []
+ self._set_opt_strings(opts)
+ self.baseopt = self._short_opts[0] or self._long_opts[0]
+ opts = ()
+
+ Option.__init__(self, *opts, **attrs)
+
+ def __str__(self):
+ pf = ""
+ if hasattr(self, "subopt") and self.subopt:
+ pf = " %s...,%s,..." % (self.baseopt, self.subopt)
+ return Option.__str__(self) + pf
+
+ def _check_opt_strings(self, opts):
+ return opts
+
+ def _check_dest(self):
+ try:
+ Option._check_dest(self)
+ except IndexError:
+ if self.subopt:
+ self.dest = "__%s__%s" % (self.baseopt, self.subopt)
+ self.dest = self.dest.replace("-", "")
+ else:
+ raise
+
+ def get_opt_string(self):
+ if hasattr(self, 'subopt'):
+ return self.subopt
+ else:
+ return Option.get_opt_string(self)
+
+ def take_action(self, action, dest, opt, value, values, parser):
+ if action == "store_hive":
+ if not hasattr(values, dest) or getattr(values, dest) == None:
+ if hasattr(self, "subopts_hive") and self.subopts_hive:
+ hive = self.subopts_hive
+ else:
+ hive = parser.hive_class()
+ setattr(values, dest, hive)
+ for o in value.split(self.subsep or ","):
+ oo = o.split('=')
+ ok = oo[0]
+ ov = None
+ if (len(oo) > 1):
+ ov = oo[1]
+ if ok in self.subopt_map:
+ self.subopt_map[ok].process(ok, ov, values, parser)
+ else:
+ getattr(values, dest).add(*oo)
+ return
+ Option.take_action(self, action, dest, opt, value, values, parser)
+
+ def register_sub(self, o):
+ """Register argument a suboption for `self`."""
+
+ if o.subopt in self.subopt_map:
+ raise OptionConflictError(
+ "conflicting suboption handlers for `%s'" % o.subopt,
+ o)
+ self.subopt_map[o.subopt] = o
+
+ CHECK_METHODS = []
+ for m in Option.CHECK_METHODS:
+ #if not m == Option._check_dest:
+ if not m.__name__ == '_check_dest':
+ CHECK_METHODS.append(m)
+ CHECK_METHODS.append(_check_dest)
+
+
+
+class SubbedOptFormatter(HelpFormatter):
+
+ def format_option_strings(self, option):
+ if hasattr(option, "subopt") and option.subopt:
+ res = '-o ' + option.subopt
+ if option.takes_value():
+ res += "="
+ res += option.metavar or 'FOO'
+ return res
+
+ return HelpFormatter.format_option_strings(self, option)
+
+
+
+class SubbedOptIndentedFormatter(IndentedHelpFormatter, SubbedOptFormatter):
+
+ def format_option_strings(self, option):
+ return SubbedOptFormatter.format_option_strings(self, option)
+
+
+
+class SubbedOptParse(OptionParser):
+ """
+ This class alters / enhances `OptionParser` with *suboption handlers*.
+
+ That is, calling `sop.add_option('-x', subopt=foo)` installs a handler
+ which will be triggered if there is ``-x foo`` in the command line being
+ parsed (or, eg., ``-x foo,bar``).
+
+ Moreover, ``-x`` implicitly gets a handler which collects the unhandled
+ suboptions of ``-x`` into a `SubOptsHive` instance (accessible post festam
+ via the `x` attribute of the returned Values object). (The only exception
+ is when ``-x`` has *explicitly* been added with action ``store_hive``.
+ This opens up the possibility of customizing the ``-x`` handler at some
+ rate.)
+
+ Suboption handlers have all the nice features of normal option handlers,
+ eg. they are displayed in the automatically generated help message
+ (and can have their own help info).
+ """
+
+ def __init__(self, *args, **kw):
+
+ if not 'formatter' in kw:
+ kw['formatter'] = SubbedOptIndentedFormatter()
+ if not 'option_class' in kw:
+ kw['option_class'] = SubbedOpt
+ if 'hive_class' in kw:
+ self.hive_class = kw.pop('hive_class')
+ else:
+ self.hive_class = SubOptsHive
+
+ OptionParser.__init__(self, *args, **kw)
+
+ def add_option(self, *args, **kwargs):
+ if 'action' in kwargs and kwargs['action'] == 'store_hive':
+ if 'subopt' in kwargs:
+ raise OptParseError(
+ """option can't have a `subopt' attr and `action="store_hive"' at the same time""")
+ if not 'type' in kwargs:
+ kwargs['type'] = 'string'
+ elif 'subopt' in kwargs:
+ o = self.option_class(*args, **kwargs)
+
+ oo = self.get_option(o.baseopt)
+ if oo:
+ if oo.action != "store_hive":
+ raise OptionConflictError(
+ "can't add subopt as option has already a handler that doesn't do `store_hive'",
+ oo)
+ else:
+ self.add_option(o.baseopt, action='store_hive',
+ metavar="sub1,[sub2,...]")
+ oo = self.get_option(o.baseopt)
+
+ oo.register_sub(o)
+
+ args = (o,)
+ kwargs = {}
+
+ return OptionParser.add_option(self, *args, **kwargs)
+++ /dev/null
-#
-# Copyright (C) 2001 Jeff Epler <jepler@unpythonic.dhs.org>
-# Copyright (C) 2006 Csaba Henk <csaba.henk@creo.hu>
-#
-# This program can be distributed under the terms of the GNU LGPL.
-# See the file COPYING.
-#
-
-
-# suppress version mismatch warnings
-try:
- import warnings
- warnings.filterwarnings('ignore',
- 'Python C API version mismatch',
- RuntimeWarning,
- )
-except:
- pass
-
-from string import join
-import sys
-from errno import *
-from os import environ
-import re
-from fuseparts import __version__
-from fuseparts._fuse import main, FuseGetContext, FuseInvalidate
-from fuseparts._fuse import FuseError, FuseAPIVersion
-from fuseparts.subbedopts import SubOptsHive, SubbedOptFormatter
-from fuseparts.subbedopts import SubbedOptIndentedFormatter, SubbedOptParse
-from fuseparts.subbedopts import SUPPRESS_HELP, OptParseError
-from fuseparts.setcompatwrap import set
-
-
-##########
-###
-### API specification API.
-###
-##########
-
-# The actual API version of this module
-FUSE_PYTHON_API_VERSION = (0, 2)
-
-def __getenv__(var, pattern = '.', trans = lambda x: x):
- """
- Fetch enviroment variable and optionally transform it. Return `None` if
- variable is unset. Bail out if value of variable doesn't match (optional)
- regex pattern.
- """
-
- if var not in environ:
- return None
- val = environ[var]
- rpat = pattern
- if not isinstance(rpat, type(re.compile(''))):
- rpat = re.compile(rpat)
- if not rpat.search(val):
- raise RuntimeError("env var %s doesn't match required pattern %s" % \
- (var, `pattern`))
- return trans(val)
-
-def get_fuse_python_api():
- if fuse_python_api:
- return fuse_python_api
- elif compat_0_1:
- # deprecated way of API specification
- return (0,1)
-
-def get_compat_0_1():
- return get_fuse_python_api() == (0, 1)
-
-# API version to be used
-fuse_python_api = __getenv__('FUSE_PYTHON_API', '^[\d.]+$',
- lambda x: tuple([int(i) for i in x.split('.')]))
-
-# deprecated way of API specification
-compat_0_1 = __getenv__('FUSE_PYTHON_COMPAT', '^(0.1|ALL)$', lambda x: True)
-
-fuse_python_api = get_fuse_python_api()
-
-##########
-###
-### Parsing for FUSE.
-###
-##########
-
-
-
-class FuseArgs(SubOptsHive):
- """
- Class representing a FUSE command line.
- """
-
- fuse_modifiers = {'showhelp': '-ho',
- 'showversion': '-V',
- 'foreground': '-f'}
-
- def __init__(self):
-
- SubOptsHive.__init__(self)
-
- self.modifiers = {}
- self.mountpoint = None
-
- for m in self.fuse_modifiers:
- self.modifiers[m] = False
-
- def __str__(self):
- return '\n'.join(['< on ' + str(self.mountpoint) + ':',
- ' ' + str(self.modifiers), ' -o ']) + \
- ',\n '.join(self._str_core()) + \
- ' >'
-
- def getmod(self, mod):
- return self.modifiers[mod]
-
- def setmod(self, mod):
- self.modifiers[mod] = True
-
- def unsetmod(self, mod):
- self.modifiers[mod] = False
-
- def mount_expected(self):
- if self.getmod('showhelp'):
- return False
- if self.getmod('showversion'):
- return False
- return True
-
- def assemble(self):
- """Mangle self into an argument array"""
-
- self.canonify()
- args = [sys.argv and sys.argv[0] or "python"]
- if self.mountpoint:
- args.append(self.mountpoint)
- for m, v in self.modifiers.iteritems():
- if v:
- args.append(self.fuse_modifiers[m])
-
- opta = []
- for o, v in self.optdict.iteritems():
- opta.append(o + '=' + v)
- opta.extend(self.optlist)
-
- if opta:
- args.append("-o" + ",".join(opta))
-
- return args
-
- def filter(self, other=None):
- """
- Same as for SubOptsHive, with the following difference:
- if other is not specified, `Fuse.fuseoptref()` is run and its result
- will be used.
- """
-
- if not other:
- other = Fuse.fuseoptref()
-
- return SubOptsHive.filter(self, other)
-
-
-
-class FuseFormatter(SubbedOptIndentedFormatter):
-
- def __init__(self, **kw):
- if not 'indent_increment' in kw:
- kw['indent_increment'] = 4
- SubbedOptIndentedFormatter.__init__(self, **kw)
-
- def store_option_strings(self, parser):
- SubbedOptIndentedFormatter.store_option_strings(self, parser)
- # 27 is how the lib stock help appears
- self.help_position = max(self.help_position, 27)
- self.help_width = self.width - self.help_position
-
-
-class FuseOptParse(SubbedOptParse):
- """
- This class alters / enhances `SubbedOptParse` so that it's
- suitable for usage with FUSE.
-
- - When adding options, you can use the `mountopt` pseudo-attribute which
- is equivalent with adding a subopt for option ``-o``
- (it doesn't require an option argument).
-
- - FUSE compatible help and version printing.
-
- - Error and exit callbacks are relaxed. In case of FUSE, the command
- line is to be treated as a DSL [#]_. You don't wanna this module to
- force an exit on you just because you hit a DSL syntax error.
-
- - Built-in support for conventional FUSE options (``-d``, ``-f`, ``-s``).
- The way of this can be tuned by keyword arguments, see below.
-
- .. [#] http://en.wikipedia.org/wiki/Domain-specific_programming_language
-
- Keyword arguments for initialization
- ------------------------------------
-
- standard_mods
- Boolean [default is `True`].
- Enables support for the usual interpretation of the ``-d``, ``-f``
- options.
-
- fetch_mp
- Boolean [default is `True`].
- If it's True, then the last (non-option) argument
- (if there is such a thing) will be used as the FUSE mountpoint.
-
- dash_s_do
- String: ``whine``, ``undef``, or ``setsingle`` [default is ``whine``].
- The ``-s`` option -- traditionally for asking for single-threadedness --
- is an oddball: single/multi threadedness of a fuse-py fs doesn't depend
- on the FUSE command line, we have direct control over it.
-
- Therefore we have two conflicting principles:
-
- - *Orthogonality*: option parsing shouldn't affect the backing `Fuse`
- instance directly, only via its `fuse_args` attribute.
-
- - *POLS*: behave like other FUSE based fs-es do. The stock FUSE help
- makes mention of ``-s`` as a single-threadedness setter.
-
- So, if we follow POLS and implement a conventional ``-s`` option, then
- we have to go beyond the `fuse_args` attribute and set the respective
- Fuse attribute directly, hence violating orthogonality.
-
- We let the fs authors make their choice: ``dash_s_do=undef`` leaves this
- option unhandled, and the fs author can add a handler as she desires.
- ``dash_s_do=setsingle`` enables the traditional behaviour.
-
- Using ``dash_s_do=setsingle`` is not problematic at all, but we want fs
- authors be aware of the particularity of ``-s``, therefore the default is
- the ``dash_s_do=whine`` setting which raises an exception for ``-s`` and
- suggests the user to read this documentation.
-
- dash_o_handler
- Argument should be a SubbedOpt instance (created with
- ``action="store_hive"`` if you want it to be useful).
- This lets you customize the handler of the ``-o`` option. For example,
- you can alter or suppress the generic ``-o`` entry in help output.
- """
-
- def __init__(self, *args, **kw):
-
- self.mountopts = []
-
- self.fuse_args = \
- 'fuse_args' in kw and kw.pop('fuse_args') or FuseArgs()
- dsd = 'dash_s_do' in kw and kw.pop('dash_s_do') or 'whine'
- if 'fetch_mp' in kw:
- self.fetch_mp = bool(kw.pop('fetch_mp'))
- else:
- self.fetch_mp = True
- if 'standard_mods' in kw:
- smods = bool(kw.pop('standard_mods'))
- else:
- smods = True
- if 'fuse' in kw:
- self.fuse = kw.pop('fuse')
- if not 'formatter' in kw:
- kw['formatter'] = FuseFormatter()
- doh = 'dash_o_handler' in kw and kw.pop('dash_o_handler')
-
- SubbedOptParse.__init__(self, *args, **kw)
-
- if doh:
- self.add_option(doh)
- else:
- self.add_option('-o', action='store_hive',
- subopts_hive=self.fuse_args, help="mount options",
- metavar="opt,[opt...]")
-
- if smods:
- self.add_option('-f', action='callback',
- callback=lambda *a: self.fuse_args.setmod('foreground'),
- help=SUPPRESS_HELP)
- self.add_option('-d', action='callback',
- callback=lambda *a: self.fuse_args.add('debug'),
- help=SUPPRESS_HELP)
-
- if dsd == 'whine':
- def dsdcb(option, opt_str, value, parser):
- raise RuntimeError, """
-
-! If you want the "-s" option to work, pass
-!
-! dash_s_do='setsingle'
-!
-! to the Fuse constructor. See docstring of the FuseOptParse class for an
-! explanation why is it not set by default.
-"""
-
- elif dsd == 'setsingle':
- def dsdcb(option, opt_str, value, parser):
- self.fuse.multithreaded = False
-
- elif dsd == 'undef':
- dsdcb = None
- else:
- raise ArgumentError, "key `dash_s_do': uninterpreted value " + str(dsd)
-
- if dsdcb:
- self.add_option('-s', action='callback', callback=dsdcb,
- help=SUPPRESS_HELP)
-
-
- def exit(self, status=0, msg=None):
- if msg:
- sys.stderr.write(msg)
-
- def error(self, msg):
- SubbedOptParse.error(self, msg)
- raise OptParseError, msg
-
- def print_help(self, file=sys.stderr):
- SubbedOptParse.print_help(self, file)
- print >> file
- self.fuse_args.setmod('showhelp')
-
- def print_version(self, file=sys.stderr):
- SubbedOptParse.print_version(self, file)
- self.fuse_args.setmod('showversion')
-
- def parse_args(self, args=None, values=None):
- o, a = SubbedOptParse.parse_args(self, args, values)
- if a and self.fetch_mp:
- self.fuse_args.mountpoint = a.pop()
- return o, a
-
- def add_option(self, *opts, **attrs):
- if 'mountopt' in attrs:
- if opts or 'subopt' in attrs:
- raise OptParseError(
- "having options or specifying the `subopt' attribute conflicts with `mountopt' attribute")
- opts = ('-o',)
- attrs['subopt'] = attrs.pop('mountopt')
- if not 'dest' in attrs:
- attrs['dest'] = attrs['subopt']
-
- SubbedOptParse.add_option(self, *opts, **attrs)
-
-
-
-##########
-###
-### The FUSE interface.
-###
-##########
-
-
-
-class ErrnoWrapper(object):
-
- def __init__(self, func):
- self.func = func
-
- def __call__(self, *args, **kw):
- try:
- return apply(self.func, args, kw)
- except (IOError, OSError), detail:
- # Sometimes this is an int, sometimes an instance...
- if hasattr(detail, "errno"): detail = detail.errno
- return -detail
-
-
-########### Custom objects for transmitting system structures to FUSE
-
-class FuseStruct(object):
-
- def __init__(self, **kw):
- for k in kw:
- setattr(self, k, kw[k])
-
-
-class Stat(FuseStruct):
- """
- Auxiliary class which can be filled up stat attributes.
- The attributes are undefined by default.
- """
-
- def __init__(self, **kw):
- self.st_mode = None
- self.st_ino = 0
- self.st_dev = 0
- self.st_nlink = None
- self.st_uid = 0
- self.st_gid = 0
- self.st_size = 0
- self.st_atime = 0
- self.st_mtime = 0
- self.st_ctime = 0
-
- FuseStruct.__init__(self, **kw)
-
-
-class StatVfs(FuseStruct):
- """
- Auxiliary class which can be filled up statvfs attributes.
- The attributes are 0 by default.
- """
-
- def __init__(self, **kw):
-
- self.f_bsize = 0
- self.f_frsize = 0
- self.f_blocks = 0
- self.f_bfree = 0
- self.f_bavail = 0
- self.f_files = 0
- self.f_ffree = 0
- self.f_favail = 0
- self.f_flag = 0
- self.f_namemax = 0
-
- FuseStruct.__init__(self, **kw)
-
-
-class Direntry(FuseStruct):
- """
- Auxiliary class for carrying directory entry data.
- Initialized with `name`. Further attributes (each
- set to 0 as default):
-
- offset
- An integer (or long) parameter, used as a bookmark
- during directory traversal.
- This needs to be set it you want stateful directory
- reading.
-
- type
- Directory entry type, should be one of the stat type
- specifiers (stat.S_IFLNK, stat.S_IFBLK, stat.S_IFDIR,
- stat.S_IFCHR, stat.S_IFREG, stat.S_IFIFO, stat.S_IFSOCK).
-
- ino
- Directory entry inode number.
-
- Note that Python's standard directory reading interface is
- stateless and provides only names, so the above optional
- attributes doesn't make sense in that context.
- """
-
- def __init__(self, name, **kw):
-
- self.name = name
- self.offset = 0
- self.type = 0
- self.ino = 0
-
- FuseStruct.__init__(self, **kw)
-
-
-class Flock(FuseStruct):
- """
- Class for representing flock structures (cf. fcntl(3)).
-
- It makes sense to give values to the `l_type`, `l_start`,
- `l_len`, `l_pid` attributes (`l_whence` is not used by
- FUSE, see ``fuse.h``).
- """
-
- def __init__(self, name, **kw):
-
- self.l_type = None
- self.l_start = None
- self.l_len = None
- self.l_pid = None
-
- FuseStruct.__init__(self, **kw)
-
-
-class Timespec(FuseStruct):
- """
- Cf. struct timespec in time.h:
- http://www.opengroup.org/onlinepubs/009695399/basedefs/time.h.html
- """
-
- def __init__(self, name, **kw):
-
- self.tv_sec = None
- self.tv_nsec = None
-
- FuseStruct.__init__(self, **kw)
-
-
-class FuseFileInfo(FuseStruct):
-
- def __init__(self, **kw):
-
- self.keep = False
- self.direct_io = False
-
- FuseStruct.__init__(self, **kw)
-
-
-
-########## Interface for requiring certain features from your underlying FUSE library.
-
-def feature_needs(*feas):
- """
- Get info about the FUSE API version needed for the support of some features.
-
- This function takes a variable number of feature patterns.
-
- A feature pattern is either:
-
- - an integer (directly referring to a FUSE API version number)
- - a built-in feature specifier string (meaning defined by dictionary)
- - a string of the form ``has_foo``, where ``foo`` is a filesystem method
- (refers to the API version where the method has been introduced)
- - a list/tuple of other feature patterns (matches each of its members)
- - a regexp (meant to be matched against the builtins plus ``has_foo``
- patterns; can also be given by a string of the from "re:*")
- - a negated regexp (can be given by a string of the form "!re:*")
-
- If called with no arguments, then the list of builtins is returned, mapped
- to their meaning.
-
- Otherwise the function returns the smallest FUSE API version number which
- has all the matching features.
-
- Builtin specifiers worth to explicit mention:
- - ``stateful_files``: you want to use custom filehandles (eg. a file class).
- - ``*``: you want all features.
- - while ``has_foo`` makes sense for all filesystem method ``foo``, some
- of these can be found among the builtins, too (the ones which can be
- handled by the general rule).
-
- specifiers like ``has_foo`` refer to requirement that the library knows of
- the fs method ``foo``.
- """
-
- fmap = {'stateful_files': 22,
- 'stateful_dirs': 23,
- 'stateful_io': ('stateful_files', 'stateful_dirs'),
- 'stateful_files_keep_cache': 23,
- 'stateful_files_direct_io': 23,
- 'keep_cache': ('stateful_files_keep_cache',),
- 'direct_io': ('stateful_files_direct_io',),
- 'has_opendir': ('stateful_dirs',),
- 'has_releasedir': ('stateful_dirs',),
- 'has_fsyncdir': ('stateful_dirs',),
- 'has_create': 25,
- 'has_access': 25,
- 'has_fgetattr': 25,
- 'has_ftruncate': 25,
- 'has_fsinit': ('has_init'),
- 'has_fsdestroy': ('has_destroy'),
- 'has_lock': 26,
- 'has_utimens': 26,
- 'has_bmap': 26,
- 'has_init': 23,
- 'has_destroy': 23,
- '*': '!re:^\*$'}
-
- if not feas:
- return fmap
-
- def resolve(args, maxva):
-
- for fp in args:
- if isinstance(fp, int):
- maxva[0] = max(maxva[0], fp)
- continue
- if isinstance(fp, list) or isinstance(fp, tuple):
- for f in fp:
- yield f
- continue
- ma = isinstance(fp, str) and re.compile("(!\s*|)re:(.*)").match(fp)
- if isinstance(fp, type(re.compile(''))) or ma:
- neg = False
- if ma:
- mag = ma.groups()
- fp = re.compile(mag[1])
- neg = bool(mag[0])
- for f in fmap.keys() + [ 'has_' + a for a in Fuse._attrs ]:
- if neg != bool(re.search(fp, f)):
- yield f
- continue
- ma = re.compile("has_(.*)").match(fp)
- if ma and ma.groups()[0] in Fuse._attrs and not fp in fmap:
- yield 21
- continue
- yield fmap[fp]
-
- maxva = [0]
- while feas:
- feas = set(resolve(feas, maxva))
-
- return maxva[0]
-
-
-def APIVersion():
- """Get the API version of your underlying FUSE lib"""
-
- return FuseAPIVersion()
-
-
-def feature_assert(*feas):
- """
- Takes some feature patterns (like in `feature_needs`).
- Raises a fuse.FuseError if your underlying FUSE lib fails
- to have some of the matching features.
-
- (Note: use a ``has_foo`` type feature assertion only if lib support
- for method ``foo`` is *necessary* for your fs. Don't use this assertion
- just because your fs implements ``foo``. The usefulness of ``has_foo``
- is limited by the fact that we can't guarantee that your FUSE kernel
- module also supports ``foo``.)
- """
-
- fav = APIVersion()
-
- for fea in feas:
- fn = feature_needs(fea)
- if fav < fn:
- raise FuseError(
- "FUSE API version %d is required for feature `%s' but only %d is available" % \
- (fn, str(fea), fav))
-
-
-############# Subclass this.
-
-class Fuse(object):
- """
- Python interface to FUSE.
-
- Basic usage:
-
- - instantiate
-
- - add options to `parser` attribute (an instance of `FuseOptParse`)
-
- - call `parse`
-
- - call `main`
- """
-
- _attrs = ['getattr', 'readlink', 'readdir', 'mknod', 'mkdir',
- 'unlink', 'rmdir', 'symlink', 'rename', 'link', 'chmod',
- 'chown', 'truncate', 'utime', 'open', 'read', 'write', 'release',
- 'statfs', 'fsync', 'create', 'opendir', 'releasedir', 'fsyncdir',
- 'flush', 'fgetattr', 'ftruncate', 'getxattr', 'listxattr',
- 'setxattr', 'removexattr', 'access', 'lock', 'utimens', 'bmap',
- 'fsinit', 'fsdestroy']
-
- fusage = "%prog [mountpoint] [options]"
-
- def __init__(self, *args, **kw):
- """
- Not much happens here apart from initializing the `parser` attribute.
- Arguments are forwarded to the constructor of the parser class almost
- unchanged.
-
- The parser class is `FuseOptParse` unless you specify one using the
- ``parser_class`` keyword. (See `FuseOptParse` documentation for
- available options.)
- """
-
- if not fuse_python_api:
- raise RuntimeError, __name__ + """.fuse_python_api not defined.
-
-! Please define """ + __name__ + """.fuse_python_api internally (eg.
-!
-! (1) """ + __name__ + """.fuse_python_api = """ + `FUSE_PYTHON_API_VERSION` + """
-!
-! ) or in the enviroment (eg.
-!
-! (2) FUSE_PYTHON_API=0.1
-!
-! ).
-!
-! If you are actually developing a filesystem, probably (1) is the way to go.
-! If you are using a filesystem written before 2007 Q2, probably (2) is what
-! you want."
-"""
-
- def malformed():
- raise RuntimeError, \
- "malformatted fuse_python_api value " + `fuse_python_api`
- if not isinstance(fuse_python_api, tuple):
- malformed()
- for i in fuse_python_api:
- if not isinstance(i, int) or i < 0:
- malformed()
-
- if fuse_python_api > FUSE_PYTHON_API_VERSION:
- raise RuntimeError, """
-! You require FUSE-Python API version """ + `fuse_python_api` + """.
-! However, the latest available is """ + `FUSE_PYTHON_API_VERSION` + """.
-"""
-
- self.fuse_args = \
- 'fuse_args' in kw and kw.pop('fuse_args') or FuseArgs()
-
- if get_compat_0_1():
- return self.__init_0_1__(*args, **kw)
-
- self.multithreaded = True
-
- if not 'usage' in kw:
- kw['usage'] = self.fusage
- if not 'fuse_args' in kw:
- kw['fuse_args'] = self.fuse_args
- kw['fuse'] = self
- parserclass = \
- 'parser_class' in kw and kw.pop('parser_class') or FuseOptParse
-
- self.parser = parserclass(*args, **kw)
- self.methproxy = self.Methproxy()
-
- def parse(self, *args, **kw):
- """Parse command line, fill `fuse_args` attribute."""
-
- ev = 'errex' in kw and kw.pop('errex')
- if ev and not isinstance(ev, int):
- raise TypeError, "error exit value should be an integer"
-
- try:
- self.cmdline = self.parser.parse_args(*args, **kw)
- except OptParseError:
- if ev:
- sys.exit(ev)
- raise
-
- return self.fuse_args
-
- def main(self, args=None):
- """Enter filesystem service loop."""
-
- if get_compat_0_1():
- args = self.main_0_1_preamble()
-
- d = {'multithreaded': self.multithreaded and 1 or 0}
- d['fuse_args'] = args or self.fuse_args.assemble()
-
- for t in 'file_class', 'dir_class':
- if hasattr(self, t):
- getattr(self.methproxy, 'set_' + t)(getattr(self,t))
-
- for a in self._attrs:
- b = a
- if get_compat_0_1() and a in self.compatmap:
- b = self.compatmap[a]
- if hasattr(self, b):
- c = ''
- if get_compat_0_1() and hasattr(self, a + '_compat_0_1'):
- c = '_compat_0_1'
- d[a] = ErrnoWrapper(self.lowwrap(a + c))
-
- try:
- main(**d)
- except FuseError:
- if args or self.fuse_args.mount_expected():
- raise
-
- def lowwrap(self, fname):
- """
- Wraps the fname method when the C code expects a different kind of
- callback than we have in the fusepy API. (The wrapper is usually for
- performing some checks or transfromations which could be done in C but
- is simpler if done in Python.)
-
- Currently `open` and `create` are wrapped: a boolean flag is added
- which indicates if the result is to be kept during the opened file's
- lifetime or can be thrown away. Namely, it's considered disposable
- if it's an instance of FuseFileInfo.
- """
- fun = getattr(self, fname)
-
- if fname in ('open', 'create'):
- def wrap(*a, **kw):
- res = fun(*a, **kw)
- if not res or type(res) == type(0):
- return res
- else:
- return (res, type(res) != FuseFileInfo)
- elif fname == 'utimens':
- def wrap(path, acc_sec, acc_nsec, mod_sec, mod_nsec):
- ts_acc = Timespec(tv_sec = acc_sec, tv_nsec = acc_nsec)
- ts_mod = Timespec(tv_sec = mod_sec, tv_nsec = mod_nsec)
- return fun(path, ts_acc, ts_mod)
- else:
- wrap = fun
-
- return wrap
-
- def GetContext(self):
- return FuseGetContext(self)
-
- def Invalidate(self, path):
- return FuseInvalidate(self, path)
-
- def fuseoptref(cls):
- """
- Find out which options are recognized by the library.
- Result is a `FuseArgs` instance with the list of supported
- options, suitable for passing on to the `filter` method of
- another `FuseArgs` instance.
- """
-
- import os, re
-
- pr, pw = os.pipe()
- pid = os.fork()
- if pid == 0:
- os.dup2(pw, 2)
- os.close(pr)
-
- fh = cls()
- fh.fuse_args = FuseArgs()
- fh.fuse_args.setmod('showhelp')
- fh.main()
- sys.exit()
-
- os.close(pw)
-
- fa = FuseArgs()
- ore = re.compile("-o\s+([\w\[\]]+(?:=\w+)?)")
- fpr = os.fdopen(pr)
- for l in fpr:
- m = ore.search(l)
- if m:
- o = m.groups()[0]
- oa = [o]
- # try to catch two-in-one options (like "[no]foo")
- opa = o.split("[")
- if len(opa) == 2:
- o1, ox = opa
- oxpa = ox.split("]")
- if len(oxpa) == 2:
- oo, o2 = oxpa
- oa = [o1 + o2, o1 + oo + o2]
- for o in oa:
- fa.add(o)
-
- fpr.close()
- return fa
-
- fuseoptref = classmethod(fuseoptref)
-
-
- class Methproxy(object):
-
- def __init__(self):
-
- class mpx(object):
- def __init__(self, name):
- self.name = name
- def __call__(self, *a, **kw):
- return getattr(a[-1], self.name)(*(a[1:-1]), **kw)
-
- self.proxyclass = mpx
- self.mdic = {}
- self.file_class = None
- self.dir_class = None
-
- def __call__(self, meth):
- return meth in self.mdic and self.mdic[meth] or None
-
- def _add_class_type(cls, type, inits, proxied):
-
- def setter(self, xcls):
-
- setattr(self, type + '_class', xcls)
-
- for m in inits:
- self.mdic[m] = xcls
-
- for m in proxied:
- if hasattr(xcls, m):
- self.mdic[m] = self.proxyclass(m)
-
- setattr(cls, 'set_' + type + '_class', setter)
-
- _add_class_type = classmethod(_add_class_type)
-
- Methproxy._add_class_type('file', ('open', 'create'),
- ('read', 'write', 'fsync', 'release', 'flush',
- 'fgetattr', 'ftruncate', 'lock'))
- Methproxy._add_class_type('dir', ('opendir',),
- ('readdir', 'fsyncdir', 'releasedir'))
-
-
- def __getattr__(self, meth):
-
- m = self.methproxy(meth)
- if m:
- return m
-
- raise AttributeError, "Fuse instance has no attribute '%s'" % meth
-
-
-
-##########
-###
-### Compat stuff.
-###
-##########
-
-
-
- def __init_0_1__(self, *args, **kw):
-
- self.flags = 0
- multithreaded = 0
-
- # default attributes
- if args == ():
- # there is a self.optlist.append() later on, make sure it won't
- # bomb out.
- self.optlist = []
- else:
- self.optlist = args
- self.optdict = kw
-
- if len(self.optlist) == 1:
- self.mountpoint = self.optlist[0]
- else:
- self.mountpoint = None
-
- # grab command-line arguments, if any.
- # Those will override whatever parameters
- # were passed to __init__ directly.
- argv = sys.argv
- argc = len(argv)
- if argc > 1:
- # we've been given the mountpoint
- self.mountpoint = argv[1]
- if argc > 2:
- # we've received mount args
- optstr = argv[2]
- opts = optstr.split(",")
- for o in opts:
- try:
- k, v = o.split("=", 1)
- self.optdict[k] = v
- except:
- self.optlist.append(o)
-
-
- def main_0_1_preamble(self):
-
- cfargs = FuseArgs()
-
- cfargs.mountpoint = self.mountpoint
-
- if hasattr(self, 'debug'):
- cfargs.add('debug')
-
- if hasattr(self, 'allow_other'):
- cfargs.add('allow_other')
-
- if hasattr(self, 'kernel_cache'):
- cfargs.add('kernel_cache')
-
- return cfargs.assemble()
-
-
- def getattr_compat_0_1(self, *a):
- from os import stat_result
-
- return stat_result(self.getattr(*a))
-
-
- def statfs_compat_0_1(self, *a):
-
- oout = self.statfs(*a)
- lo = len(oout)
-
- svf = StatVfs()
- svf.f_bsize = oout[0] # 0
- svf.f_frsize = oout[lo >= 8 and 7 or 0] # 1
- svf.f_blocks = oout[1] # 2
- svf.f_bfree = oout[2] # 3
- svf.f_bavail = oout[3] # 4
- svf.f_files = oout[4] # 5
- svf.f_ffree = oout[5] # 6
- svf.f_favail = lo >= 9 and oout[8] or 0 # 7
- svf.f_flag = lo >= 10 and oout[9] or 0 # 8
- svf.f_namemax = oout[6] # 9
-
- return svf
-
-
- def readdir_compat_0_1(self, path, offset, *fh):
-
- for name, type in self.getdir(path):
- de = Direntry(name)
- de.type = type
-
- yield de
-
-
- compatmap = {'readdir': 'getdir'}
+++ /dev/null
-__version__ = "0.2"
+++ /dev/null
-try:
- set()
- set = set
-except:
- from sets import Set as set
+++ /dev/null
-#
-# Copyright (C) 2006 Csaba Henk <csaba.henk@creo.hu>
-#
-# This program can be distributed under the terms of the GNU LGPL.
-# See the file COPYING.
-#
-
-from optparse import Option, OptionParser, OptParseError, OptionConflictError
-from optparse import HelpFormatter, IndentedHelpFormatter, SUPPRESS_HELP
-from fuseparts.setcompatwrap import set
-
-##########
-###
-### Generic suboption parsing stuff.
-###
-##########
-
-
-
-class SubOptsHive(object):
- """
- Class for collecting unhandled suboptions.
- """
-
- def __init__(self):
-
- self.optlist = set()
- self.optdict = {}
-
- def _str_core(self):
-
- sa = []
- for k, v in self.optdict.iteritems():
- sa.append(str(k) + '=' + str(v))
-
- ra = (list(self.optlist) + sa) or ["(none)"]
- ra.sort()
- return ra
-
- def __str__(self):
- return "< opts: " + ", ".join(self._str_core()) + " >"
-
- def canonify(self):
- """
- Transform self to an equivalent canonical form:
- delete optdict keys with False value, move optdict keys
- with True value to optlist, stringify other values.
- """
-
- for k, v in self.optdict.iteritems():
- if v == False:
- self.optdict.pop(k)
- elif v == True:
- self.optdict.pop(k)
- self.optlist.add(v)
- else:
- self.optdict[k] = str(v)
-
- def filter(self, other):
- """
- Throw away those options which are not in the other one.
- Returns a new instance with the rejected options.
- """
-
- self.canonify()
- other.canonify()
-
- rej = self.__class__()
- rej.optlist = self.optlist.difference(other.optlist)
- self.optlist.difference_update(rej.optlist)
- for x in self.optdict.copy():
- if x not in other.optdict:
- self.optdict.pop(x)
- rej.optdict[x] = None
-
- return rej
-
- def add(self, opt, val=None):
- """Add a suboption."""
-
- ov = opt.split('=', 1)
- o = ov[0]
- v = len(ov) > 1 and ov[1] or None
-
- if (v):
- if val != None:
- raise AttributeError, "ambiguous option value"
- val = v
-
- if val == False:
- return
-
- if val in (None, True):
- self.optlist.add(o)
- else:
- self.optdict[o] = val
-
-
-
-class SubbedOpt(Option):
- """
- `Option` derivative enhanced with the attribute of being a suboption of
- some other option (like ``foo`` and ``bar`` for ``-o`` in ``-o foo,bar``).
- """
-
- ATTRS = Option.ATTRS + ["subopt", "subsep", "subopts_hive"]
- ACTIONS = Option.ACTIONS + ("store_hive",)
- STORE_ACTIONS = Option.STORE_ACTIONS + ("store_hive",)
- TYPED_ACTIONS = Option.TYPED_ACTIONS + ("store_hive",)
-
- def __init__(self, *opts, **attrs):
-
- self.subopt_map = {}
-
- if "subopt" in attrs:
- self._short_opts = []
- self._long_opts = []
- self._set_opt_strings(opts)
- self.baseopt = self._short_opts[0] or self._long_opts[0]
- opts = ()
-
- Option.__init__(self, *opts, **attrs)
-
- def __str__(self):
- pf = ""
- if hasattr(self, "subopt") and self.subopt:
- pf = " %s...,%s,..." % (self.baseopt, self.subopt)
- return Option.__str__(self) + pf
-
- def _check_opt_strings(self, opts):
- return opts
-
- def _check_dest(self):
- try:
- Option._check_dest(self)
- except IndexError:
- if self.subopt:
- self.dest = "__%s__%s" % (self.baseopt, self.subopt)
- self.dest = self.dest.replace("-", "")
- else:
- raise
-
- def get_opt_string(self):
- if hasattr(self, 'subopt'):
- return self.subopt
- else:
- return Option.get_opt_string(self)
-
- def take_action(self, action, dest, opt, value, values, parser):
- if action == "store_hive":
- if not hasattr(values, dest) or getattr(values, dest) == None:
- if hasattr(self, "subopts_hive") and self.subopts_hive:
- hive = self.subopts_hive
- else:
- hive = parser.hive_class()
- setattr(values, dest, hive)
- for o in value.split(self.subsep or ","):
- oo = o.split('=')
- ok = oo[0]
- ov = None
- if (len(oo) > 1):
- ov = oo[1]
- if ok in self.subopt_map:
- self.subopt_map[ok].process(ok, ov, values, parser)
- else:
- getattr(values, dest).add(*oo)
- return
- Option.take_action(self, action, dest, opt, value, values, parser)
-
- def register_sub(self, o):
- """Register argument a suboption for `self`."""
-
- if o.subopt in self.subopt_map:
- raise OptionConflictError(
- "conflicting suboption handlers for `%s'" % o.subopt,
- o)
- self.subopt_map[o.subopt] = o
-
- CHECK_METHODS = []
- for m in Option.CHECK_METHODS:
- #if not m == Option._check_dest:
- if not m.__name__ == '_check_dest':
- CHECK_METHODS.append(m)
- CHECK_METHODS.append(_check_dest)
-
-
-
-class SubbedOptFormatter(HelpFormatter):
-
- def format_option_strings(self, option):
- if hasattr(option, "subopt") and option.subopt:
- res = '-o ' + option.subopt
- if option.takes_value():
- res += "="
- res += option.metavar or 'FOO'
- return res
-
- return HelpFormatter.format_option_strings(self, option)
-
-
-
-class SubbedOptIndentedFormatter(IndentedHelpFormatter, SubbedOptFormatter):
-
- def format_option_strings(self, option):
- return SubbedOptFormatter.format_option_strings(self, option)
-
-
-
-class SubbedOptParse(OptionParser):
- """
- This class alters / enhances `OptionParser` with *suboption handlers*.
-
- That is, calling `sop.add_option('-x', subopt=foo)` installs a handler
- which will be triggered if there is ``-x foo`` in the command line being
- parsed (or, eg., ``-x foo,bar``).
-
- Moreover, ``-x`` implicitly gets a handler which collects the unhandled
- suboptions of ``-x`` into a `SubOptsHive` instance (accessible post festam
- via the `x` attribute of the returned Values object). (The only exception
- is when ``-x`` has *explicitly* been added with action ``store_hive``.
- This opens up the possibility of customizing the ``-x`` handler at some
- rate.)
-
- Suboption handlers have all the nice features of normal option handlers,
- eg. they are displayed in the automatically generated help message
- (and can have their own help info).
- """
-
- def __init__(self, *args, **kw):
-
- if not 'formatter' in kw:
- kw['formatter'] = SubbedOptIndentedFormatter()
- if not 'option_class' in kw:
- kw['option_class'] = SubbedOpt
- if 'hive_class' in kw:
- self.hive_class = kw.pop('hive_class')
- else:
- self.hive_class = SubOptsHive
-
- OptionParser.__init__(self, *args, **kw)
-
- def add_option(self, *args, **kwargs):
- if 'action' in kwargs and kwargs['action'] == 'store_hive':
- if 'subopt' in kwargs:
- raise OptParseError(
- """option can't have a `subopt' attr and `action="store_hive"' at the same time""")
- if not 'type' in kwargs:
- kwargs['type'] = 'string'
- elif 'subopt' in kwargs:
- o = self.option_class(*args, **kwargs)
-
- oo = self.get_option(o.baseopt)
- if oo:
- if oo.action != "store_hive":
- raise OptionConflictError(
- "can't add subopt as option has already a handler that doesn't do `store_hive'",
- oo)
- else:
- self.add_option(o.baseopt, action='store_hive',
- metavar="sub1,[sub2,...]")
- oo = self.get_option(o.baseopt)
-
- oo.register_sub(o)
-
- args = (o,)
- kwargs = {}
-
- return OptionParser.add_option(self, *args, **kwargs)
+++ /dev/null
-#!/usr/bin/env python
-
-#-----------------------------------------------------------------------------------------------
-from allmydata.uri import CHKFileURI, NewDirectoryURI, LiteralFileURI
-from allmydata.scripts.common_http import do_http as do_http_req
-
-import base64
-import sha
-import sys
-import os
-import errno
-import stat
-# pull in some spaghetti to make this stuff work without fuse-py being installed
-try:
- import _find_fuse_parts
- junk = _find_fuse_parts
- del junk
-except ImportError:
- pass
-import fuse
-
-import time
-import traceback
-import simplejson
-import urllib
-
-USAGE = 'usage: tahoe fuse [dir_cap_name] [fuse_options] mountpoint'
-
-if not hasattr(fuse, '__version__'):
- raise RuntimeError, \
- "your fuse-py doesn't know of fuse.__version__, probably it's too old."
-
-fuse.fuse_python_api = (0, 2)
-fuse.feature_assert('stateful_files', 'has_init')
-
-
-logfile = file('tfuse.log', 'wb')
-
-def log(msg):
- logfile.write("%s: %s\n" % (time.asctime(), msg))
- #time.sleep(0.1)
- logfile.flush()
-
-fuse.flog = log
-
-def do_http(method, url, body=''):
- resp = do_http_req(method, url, body)
- log('do_http(%s, %s) -> %s, %s' % (method, url, resp.status, resp.reason))
- if resp.status not in (200, 201):
- raise RuntimeError('http response (%s, %s)' % (resp.status, resp.reason))
- else:
- return resp.read()
-
-def flag2mode(flags):
- log('flag2mode(%r)' % (flags,))
- #md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
- md = {os.O_RDONLY: 'rb', os.O_WRONLY: 'wb', os.O_RDWR: 'w+b'}
- m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
-
- if flags | os.O_APPEND:
- m = m.replace('w', 'a', 1)
-
- return m
-
-def logargsretexc(meth):
- def inner(self, *args, **kwargs):
- log("%s(%r, %r)" % (meth, args, kwargs))
- try:
- ret = meth(self, *args, **kwargs)
- except:
- log('exception:\n%s' % (traceback.format_exc(),))
- raise
- log("ret: %r" % (ret, ))
- return ret
- inner.__name__ = '<logwrap(%s)>' % (meth,)
- return inner
-
-def logexc(meth):
- def inner(self, *args, **kwargs):
- try:
- ret = meth(self, *args, **kwargs)
- except:
- log('exception:\n%s' % (traceback.format_exc(),))
- raise
- return ret
- inner.__name__ = '<logwrap(%s)>' % (meth,)
- return inner
-
-def log_exc():
- log('exception:\n%s' % (traceback.format_exc(),))
-
-class TahoeFuseFile(object):
-
- def __init__(self, path, flags, *mode):
- log("TFF: __init__(%r, %r, %r)" % (path, flags, mode))
-
- self._path = path # for tahoe put
- try:
- self.parent, self.name, self.fnode = self.tfs.get_parent_name_and_child(path)
- m = flag2mode(flags)
- log('TFF: flags2(mode) -> %s' % (m,))
- if m[0] in 'wa':
- # write
- self.fname = self.tfs.cache.tmp_file(os.urandom(20))
- if self.fnode is None:
- log('TFF: [%s] open(%s) for write: no such file, creating new File' % (self.name, self.fname, ))
- self.fnode = File(0, None, None)
- self.fnode.tmp_fname = self.fname # XXX kill this
- self.parent.add_child(self.name, self.fnode)
- elif hasattr(self.fnode, 'tmp_fname'):
- self.fname = self.fnode.tmp_fname
- self.file = os.fdopen(os.open(self.fname, flags, *mode), m)
- self.fd = self.file.fileno()
- log('TFF: opened(%s) for write' % self.fname)
- self.open_for_write = True
- else:
- # read
- assert self.fnode is not None
- uri = self.fnode.get_uri()
-
- # XXX make this go away
- if hasattr(self.fnode, 'tmp_fname'):
- self.fname = self.fnode.tmp_fname
- log('TFF: reopening(%s) for reading' % self.fname)
- else:
- log('TFF: fetching file from cache for reading')
- self.fname = self.tfs.cache.get_file(uri)
-
- self.file = os.fdopen(os.open(self.fname, flags, *mode), m)
- self.fd = self.file.fileno()
- self.open_for_write = False
- log('TFF: opened(%s) for read' % self.fname)
- except:
- log_exc()
- raise
-
- def log(self, msg):
- log("<TFF(%s:%s)> %s" % (os.path.basename(self.fname), self.name, msg))
-
- @logexc
- def read(self, size, offset):
- self.log('read(%r, %r)' % (size, offset, ))
- self.file.seek(offset)
- return self.file.read(size)
-
- @logexc
- def write(self, buf, offset):
- self.log("write(-%s-, %r)" % (len(buf), offset))
- if not self.open_for_write:
- return -errno.EACCES
- self.file.seek(offset)
- self.file.write(buf)
- return len(buf)
-
- @logexc
- def release(self, flags):
- self.log("release(%r)" % (flags,))
- self.file.close()
- if self.open_for_write:
- size = os.path.getsize(self.fname)
- self.fnode.size = size
- file_cap = self.tfs.upload(self.fname)
- self.fnode.ro_uri = file_cap
- self.tfs.add_child(self.parent.get_uri(), self.name, file_cap)
- self.log("uploaded: %s" % (file_cap,))
-
- # dbg
- print_tree()
-
- def _fflush(self):
- if 'w' in self.file.mode or 'a' in self.file.mode:
- self.file.flush()
-
- @logexc
- def fsync(self, isfsyncfile):
- self.log("fsync(%r)" % (isfsyncfile,))
- self._fflush()
- if isfsyncfile and hasattr(os, 'fdatasync'):
- os.fdatasync(self.fd)
- else:
- os.fsync(self.fd)
-
- @logexc
- def flush(self):
- self.log("flush()")
- self._fflush()
- # cf. xmp_flush() in fusexmp_fh.c
- os.close(os.dup(self.fd))
-
- @logexc
- def fgetattr(self):
- self.log("fgetattr()")
- s = os.fstat(self.fd)
- self.log("fgetattr() -> %r" % (s,))
- return s
-
- @logexc
- def ftruncate(self, len):
- self.log("ftruncate(%r)" % (len,))
- self.file.truncate(len)
-
-class ObjFetcher(object):
- def get_tahoe_file(self, path, flags, *mode):
- log('objfetcher.get_tahoe_file(%r, %r, %r, %r)' % (self, path, flags, mode))
- return TahoeFuseFile(path, flags, *mode)
-fetcher = ObjFetcher()
-
-class TahoeFuse(fuse.Fuse):
-
- def __init__(self, tfs, *args, **kw):
- log("TF: __init__(%r, %r)" % (args, kw))
-
- self.tfs = tfs
- _tfs_ = tfs
- class MyFuseFile(TahoeFuseFile):
- tfs = _tfs_
- self.file_class = MyFuseFile
- log("TF: file_class: %r" % (self.file_class,))
-
- fuse.Fuse.__init__(self, *args, **kw)
-
- #import thread
- #thread.start_new_thread(self.launch_reactor, ())
-
- def log(self, msg):
- log("<TF> %s" % (msg, ))
-
- @logexc
- def readlink(self, path):
- self.log("readlink(%r)" % (path,))
- return -errno.EOPNOTSUPP
-
- @logexc
- def unlink(self, path):
- self.log("unlink(%r)" % (path,))
- return -errno.EOPNOTSUPP
-
- @logexc
- def rmdir(self, path):
- self.log("rmdir(%r)" % (path,))
- return -errno.EOPNOTSUPP
-
- @logexc
- def symlink(self, path, path1):
- self.log("symlink(%r, %r)" % (path, path1))
- return -errno.EOPNOTSUPP
-
- @logexc
- def rename(self, path, path1):
- self.log("rename(%r, %r)" % (path, path1))
- self.tfs.rename(path, path1)
-
- @logexc
- def link(self, path, path1):
- self.log("link(%r, %r)" % (path, path1))
- return -errno.EOPNOTSUPP
-
- @logexc
- def chmod(self, path, mode):
- self.log("chmod(%r, %r)" % (path, mode))
- return -errno.EOPNOTSUPP
-
- @logexc
- def chown(self, path, user, group):
- self.log("chown(%r, %r, %r)" % (path, user, group))
- return -errno.EOPNOTSUPP
-
- @logexc
- def truncate(self, path, len):
- self.log("truncate(%r, %r)" % (path, len))
- return -errno.EOPNOTSUPP
-
- @logexc
- def utime(self, path, times):
- self.log("utime(%r, %r)" % (path, times))
- return -errno.EOPNOTSUPP
-
- @logexc
- def statfs(self):
- self.log("statfs()")
- """
- Should return an object with statvfs attributes (f_bsize, f_frsize...).
- Eg., the return value of os.statvfs() is such a thing (since py 2.2).
- If you are not reusing an existing statvfs object, start with
- fuse.StatVFS(), and define the attributes.
-
- To provide usable information (ie., you want sensible df(1)
- output, you are suggested to specify the following attributes:
-
- - f_bsize - preferred size of file blocks, in bytes
- - f_frsize - fundamental size of file blcoks, in bytes
- [if you have no idea, use the same as blocksize]
- - f_blocks - total number of blocks in the filesystem
- - f_bfree - number of free blocks
- - f_files - total number of file inodes
- - f_ffree - nunber of free file inodes
- """
-
- return os.statvfs(".")
-
- def fsinit(self):
- self.log("fsinit()")
-
- def main(self, *a, **kw):
- self.log("main(%r, %r)" % (a, kw))
-
- return fuse.Fuse.main(self, *a, **kw)
-
- ##################################################################
-
- @logexc
- def readdir(self, path, offset):
- log('readdir(%r, %r)' % (path, offset))
- node = self.tfs.get_path(path)
- if node is None:
- return -errno.ENOENT
- dirlist = ['.', '..'] + node.children.keys()
- log('dirlist = %r' % (dirlist,))
- return [fuse.Direntry(d) for d in dirlist]
-
- @logexc
- def getattr(self, path):
- log('getattr(%r)' % (path,))
- node = self.tfs.get_path(path)
- if node is None:
- return -errno.ENOENT
- return node.get_stat()
-
- @logexc
- def access(self, path, mode):
- self.log("access(%r, %r)" % (path, mode))
- node = self.tfs.get_path(path)
- if not node:
- return -errno.ENOENT
- accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
- if (mode & 0222):
- if not node.writable():
- log('write access denied for %s (req:%o)' % (path, mode, ))
- return -errno.EACCES
- #else:
- #log('access granted for %s' % (path, ))
-
- @logexc
- def mkdir(self, path, mode):
- self.log("mkdir(%r, %r)" % (path, mode))
- self.tfs.mkdir(path)
-
-def launch_tahoe_fuse(tfs, argv):
- sys.argv = ['tahoe fuse'] + list(argv)
- server = TahoeFuse(tfs, version="%prog " + fuse.__version__,
- usage=USAGE,
- dash_s_do='setsingle')
- server.parse(errex=1)
- server.main()
-
-
-def getbasedir(cap_name='root_dir'):
- fname = os.path.expanduser("~/.tahoe/private/%s.cap" % (cap_name,))
- if os.path.exists(fname):
- f = file(fname, 'rb')
- bd = f.read().strip()
- f.close()
- return bd
- else:
- return None
-
-def getnodeurl():
- f = file(os.path.expanduser("~/.tahoe/node.url"), 'rb')
- nu = f.read().strip()
- f.close()
- if nu[-1] != "/":
- nu += "/"
- return nu
-
-def fingerprint(uri):
- if uri is None:
- return None
- return base64.b32encode(sha.new(uri).digest()).lower()[:6]
-
-class TStat(fuse.Stat):
- def __init__(self, **kwargs):
- fuse.Stat.__init__(self, **kwargs)
-
- def __repr__(self):
- return "<Stat%r" % {
- 'st_mode': self.st_mode,
- 'st_ino': self.st_ino,
- 'st_dev': self.st_dev,
- 'st_nlink': self.st_nlink,
- 'st_uid': self.st_uid,
- 'st_gid': self.st_gid,
- 'st_size': self.st_size,
- 'st_atime': self.st_atime,
- 'st_mtime': self.st_mtime,
- 'st_ctime': self.st_ctime,
- }
-
-class Directory(object):
- def __init__(self, ro_uri, rw_uri):
- self.ro_uri = ro_uri
- self.rw_uri = rw_uri
- assert (rw_uri or ro_uri)
- self.children = {}
-
- def __repr__(self):
- return "<Directory %s>" % (fingerprint(self.get_uri()),)
-
- def get_children(self):
- return self.children.keys()
-
- def get_child(self, name):
- return self.children[name]
-
- def add_child(self, name, file_node):
- self.children[name] = file_node
-
- def remove_child(self, name):
- del self.children[name]
-
- def get_uri(self):
- return self.rw_uri or self.ro_uri
-
- def writable(self):
- return self.rw_uri and self.rw_uri != self.ro_uri
-
- def pprint(self, prefix='', printed=None):
- ret = []
- if printed is None:
- printed = set()
- writable = self.writable() and '+' or ' '
- if self in printed:
- ret.append(" %s/%s ... <%s>" % (prefix, writable, fingerprint(self.get_uri()), ))
- else:
- ret.append("[%s] %s/%s" % (fingerprint(self.get_uri()), prefix, writable, ))
- printed.add(self)
- for name,f in sorted(self.children.items()):
- ret.append(f.pprint(' ' * (len(prefix)+1)+name, printed))
- return '\n'.join(ret)
-
- def get_stat(self):
- s = TStat(st_mode = stat.S_IFDIR | 0755, st_nlink = 2)
- log("%s.get_stat()->%s" % (self, s))
- return s
-
-class File(object):
- def __init__(self, size, ro_uri, metadata):
- self.size = size
- if ro_uri:
- ro_uri = str(ro_uri)
- self.ro_uri = ro_uri
- self.metadata = metadata or {}
-
- def __repr__(self):
- return "<File %s>" % (fingerprint(self.ro_uri) or [self.tmp_fname],)
-
- def pprint(self, prefix='', printed=None):
- times, remainder = self.get_times()
- return " %s (%s) %s %s" % (prefix, self.size, times, remainder)
-
- def get_times(self):
- rem = self.metadata.copy()
- now = time.time()
- times = {}
- for T in ['c', 'm']:
- t = rem.pop('%stime'%T, None)
- if not t:
- t = 'none'
- elif (now-t) < 86400:
- t = time.strftime('%a:%H:%M:%S', time.localtime(t))
- else:
- t = time.strftime('%Y:%b:%d:%H:%M', time.localtime(t))
- times[T] = t
- return times, rem
-
- def get_stat(self):
- if hasattr(self, 'tmp_fname'):
- s = os.stat(self.tmp_fname)
- log("%s.get_stat()->%s" % (self, s))
- else:
- s = TStat(st_size=self.size, st_mode = stat.S_IFREG | 0444, st_nlink = 1)
- log("%s.get_stat()->%s" % (self, s))
- return s
-
- def get_uri(self):
- return self.ro_uri
-
- def writable(self):
- #return not self.ro_uri
- return True
-
-class TFS(object):
- def __init__(self, nodeurl, root_uri):
- self.nodeurl = nodeurl
- self.root_uri = root_uri
- self.dirs = {}
-
- self.cache = FileCache(nodeurl, os.path.expanduser('~/.tahoe/_cache'))
- ro_uri = NewDirectoryURI.init_from_string(self.root_uri).get_readonly()
- self.root = Directory(ro_uri, self.root_uri)
- self.load_dir('<root>', self.root)
-
- def log(self, msg):
- log("<TFS> %s" % (msg, ))
-
- def pprint(self):
- return self.root.pprint()
-
- def get_parent_name_and_child(self, path):
- dirname, name = os.path.split(path)
- parent = self.get_path(dirname)
- try:
- child = parent.get_child(name)
- return parent, name, child
- except KeyError:
- return parent, name, None
-
- def get_path(self, path):
- comps = path.strip('/').split('/')
- if comps == ['']:
- comps = []
- cursor = self.root
- for comp in comps:
- if not isinstance(cursor, Directory):
- self.log('path "%s" is not a dir' % (path,))
- return None
- try:
- cursor = cursor.children[comp]
- except KeyError:
- self.log('path "%s" not found' % (path,))
- return None
- return cursor
-
- def load_dir(self, name, dirobj):
- print 'loading', name, dirobj
- url = self.nodeurl + "uri/%s?t=json" % urllib.quote(dirobj.get_uri())
- data = urllib.urlopen(url).read()
- parsed = simplejson.loads(data)
- nodetype, d = parsed
- assert nodetype == 'dirnode'
- for name,details in d['children'].items():
- name = str(name)
- ctype, cattrs = details
- if ctype == 'dirnode':
- cobj = self.dir_for(name, cattrs.get('ro_uri'), cattrs.get('rw_uri'))
- else:
- assert ctype == "filenode"
- cobj = File(cattrs.get('size'), cattrs.get('ro_uri'), cattrs.get('metadata'))
- dirobj.children[name] = cobj
-
- def dir_for(self, name, ro_uri, rw_uri):
- if ro_uri:
- ro_uri = str(ro_uri)
- if rw_uri:
- rw_uri = str(rw_uri)
- uri = rw_uri or ro_uri
- assert uri
- dirobj = self.dirs.get(uri)
- if not dirobj:
- dirobj = Directory(ro_uri, rw_uri)
- self.dirs[uri] = dirobj
- self.load_dir(name, dirobj)
- return dirobj
-
- def upload(self, fname):
- self.log('upload(%r)' % (fname,))
- fh = file(fname, 'rb')
- url = self.nodeurl + "uri"
- file_cap = do_http('PUT', url, fh)
- self.log('uploaded to: %r' % (file_cap,))
- return file_cap
-
- def add_child(self, parent_dir_uri, child_name, child_uri):
- self.log('add_child(%r, %r, %r)' % (parent_dir_uri, child_name, child_uri,))
- url = self.nodeurl + "uri/%s/%s?t=uri" % (urllib.quote(parent_dir_uri), urllib.quote(child_name), )
- child_cap = do_http('PUT', url, child_uri)
- assert child_cap == child_uri
- self.log('added child %r with %r to %r' % (child_name, child_uri, parent_dir_uri))
- return child_uri
-
- def remove_child(self, parent_uri, child_name):
- self.log('remove_child(%r, %r)' % (parent_uri, child_name, ))
- url = self.nodeurl + "uri/%s/%s" % (urllib.quote(parent_uri), urllib.quote(child_name))
- resp = do_http('DELETE', url)
- self.log('child removal yielded %r' % (resp,))
-
- def mkdir(self, path):
- self.log('mkdir(%r)' % (path,))
- url = self.nodeurl + "uri?t=mkdir"
- new_dir_cap = do_http('PUT', url)
- parent_path, name = os.path.split(path)
- self.log('parent_path, name = %s, %s' % (parent_path, name,))
- parent = self.get_path(parent_path)
- self.log('parent = %s' % (parent, ))
- self.log('new_dir_cap = %s' % (new_dir_cap, ))
- child_uri = self.add_child(parent.get_uri(), name, new_dir_cap)
- ro_uri = NewDirectoryURI.init_from_string(child_uri).get_readonly()
- child = Directory(ro_uri, child_uri)
- parent.add_child(name, child)
-
- def rename(self, path, path1):
- self.log('rename(%s, %s)' % (path, path1))
- parent, name, child = self.get_parent_name_and_child(path)
- child_uri = child.get_uri()
- new_parent_path, new_child_name = os.path.split(path1)
- new_parent = self.get_path(new_parent_path)
- self.add_child(new_parent.get_uri(), new_child_name, child_uri)
- self.remove_child(parent.get_uri(), name)
- parent.remove_child(name)
- new_parent.add_child(new_child_name, child)
-
-class FileCache(object):
- def __init__(self, nodeurl, cachedir):
- self.nodeurl = nodeurl
- self.cachedir = cachedir
- if not os.path.exists(self.cachedir):
- os.makedirs(self.cachedir)
- self.tmpdir = os.path.join(self.cachedir, 'tmp')
- if not os.path.exists(self.tmpdir):
- os.makedirs(self.tmpdir)
-
- def log(self, msg):
- log("<FC> %s" % (msg, ))
-
- def get_file(self, uri):
- self.log('get_file(%s)' % (uri,))
- if uri.startswith("URI:LIT"):
- return self.get_literal(uri)
- else:
- return self.get_chk(uri)
-
- def get_literal(self, uri):
- h = sha.new(uri).digest()
- u = LiteralFileURI.init_from_string(uri)
- fname = os.path.join(self.cachedir, '__'+base64.b32encode(h).lower())
- size = len(u.data)
- self.log('writing literal file %s (%s)' % (fname, size, ))
- fh = open(fname, 'wb')
- fh.write(u.data)
- fh.close()
- return fname
-
- def get_chk(self, uri):
- u = CHKFileURI.init_from_string(str(uri))
- storage_index = u.storage_index
- size = u.size
- fname = os.path.join(self.cachedir, base64.b32encode(storage_index).lower())
- if os.path.exists(fname):
- fsize = os.path.getsize(fname)
- if fsize == size:
- return fname
- else:
- self.log('warning file "%s" is too short %s < %s' % (fname, fsize, size))
- self.log('downloading file %s (%s)' % (fname, size, ))
- fh = open(fname, 'wb')
- url = "%suri/%s" % (self.nodeurl, uri)
- download = urllib.urlopen(''.join([ self.nodeurl, "uri/", uri ]))
- while True:
- chunk = download.read(4096)
- if not chunk:
- break
- fh.write(chunk)
- fh.close()
- return fname
-
- def tmp_file(self, id):
- fname = os.path.join(self.tmpdir, base64.b32encode(id).lower())
- return fname
-
-def print_tree():
- log('tree:\n' + _tfs.pprint())
-
-def main(argv):
- log("\n\nmain(%s)" % (argv,))
-
- if not argv:
- argv = ['--help']
- if len(argv) == 1 and argv[0] in ['-h', '--help', '--version']:
- #print USAGE
- launch_tahoe_fuse(None, argv)
- return -2
-
- if not argv[0].startswith('-'):
- cap_name = argv[0]
- basedir = getbasedir(cap_name)
- if basedir is None:
- print 'root dir named "%s" not found.' % (cap_name,)
- return -2
- argv = argv[1:]
- else:
- basedir = getbasedir() # default 'root_dir'
-
- if argv[-1].startswith('-'):
- print 'mountpoint not given'
- return -2
- mountpoint = os.path.abspath(argv[-1])
- if not os.path.exists(mountpoint):
- #raise OSError(2, 'No such file or directory: "%s"' % (mountpoint,))
- print 'No such file or directory: "%s"' % (mountpoint,)
- return -2
-
- nodeurl = getnodeurl()
-
- tfs = TFS(nodeurl, basedir)
- print tfs.pprint()
-
- # make tfs instance accesible to print_tree() for dbg
- global _tfs
- _tfs = tfs
-
- launch_tahoe_fuse(tfs, argv)
-
-if __name__ == '__main__':
- main(sys.argv)
--- /dev/null
+#!/usr/bin/env python
+
+#-----------------------------------------------------------------------------------------------
+from allmydata.uri import CHKFileURI, NewDirectoryURI, LiteralFileURI
+from allmydata.scripts.common_http import do_http as do_http_req
+
+import base64
+import sha
+import sys
+import os
+import errno
+import stat
+# pull in some spaghetti to make this stuff work without fuse-py being installed
+try:
+ import _find_fuse_parts
+ junk = _find_fuse_parts
+ del junk
+except ImportError:
+ pass
+import fuse
+
+import time
+import traceback
+import simplejson
+import urllib
+
+USAGE = 'usage: tahoe fuse [dir_cap_name] [fuse_options] mountpoint'
+
+if not hasattr(fuse, '__version__'):
+ raise RuntimeError, \
+ "your fuse-py doesn't know of fuse.__version__, probably it's too old."
+
+fuse.fuse_python_api = (0, 2)
+fuse.feature_assert('stateful_files', 'has_init')
+
+
+logfile = file('tfuse.log', 'wb')
+
+def log(msg):
+ logfile.write("%s: %s\n" % (time.asctime(), msg))
+ #time.sleep(0.1)
+ logfile.flush()
+
+fuse.flog = log
+
+def do_http(method, url, body=''):
+ resp = do_http_req(method, url, body)
+ log('do_http(%s, %s) -> %s, %s' % (method, url, resp.status, resp.reason))
+ if resp.status not in (200, 201):
+ raise RuntimeError('http response (%s, %s)' % (resp.status, resp.reason))
+ else:
+ return resp.read()
+
+def flag2mode(flags):
+ log('flag2mode(%r)' % (flags,))
+ #md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
+ md = {os.O_RDONLY: 'rb', os.O_WRONLY: 'wb', os.O_RDWR: 'w+b'}
+ m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
+
+ if flags | os.O_APPEND:
+ m = m.replace('w', 'a', 1)
+
+ return m
+
+def logargsretexc(meth):
+ def inner(self, *args, **kwargs):
+ log("%s(%r, %r)" % (meth, args, kwargs))
+ try:
+ ret = meth(self, *args, **kwargs)
+ except:
+ log('exception:\n%s' % (traceback.format_exc(),))
+ raise
+ log("ret: %r" % (ret, ))
+ return ret
+ inner.__name__ = '<logwrap(%s)>' % (meth,)
+ return inner
+
+def logexc(meth):
+ def inner(self, *args, **kwargs):
+ try:
+ ret = meth(self, *args, **kwargs)
+ except:
+ log('exception:\n%s' % (traceback.format_exc(),))
+ raise
+ return ret
+ inner.__name__ = '<logwrap(%s)>' % (meth,)
+ return inner
+
+def log_exc():
+ log('exception:\n%s' % (traceback.format_exc(),))
+
+class TahoeFuseFile(object):
+
+ def __init__(self, path, flags, *mode):
+ log("TFF: __init__(%r, %r, %r)" % (path, flags, mode))
+
+ self._path = path # for tahoe put
+ try:
+ self.parent, self.name, self.fnode = self.tfs.get_parent_name_and_child(path)
+ m = flag2mode(flags)
+ log('TFF: flags2(mode) -> %s' % (m,))
+ if m[0] in 'wa':
+ # write
+ self.fname = self.tfs.cache.tmp_file(os.urandom(20))
+ if self.fnode is None:
+ log('TFF: [%s] open(%s) for write: no such file, creating new File' % (self.name, self.fname, ))
+ self.fnode = File(0, None, None)
+ self.fnode.tmp_fname = self.fname # XXX kill this
+ self.parent.add_child(self.name, self.fnode)
+ elif hasattr(self.fnode, 'tmp_fname'):
+ self.fname = self.fnode.tmp_fname
+ self.file = os.fdopen(os.open(self.fname, flags, *mode), m)
+ self.fd = self.file.fileno()
+ log('TFF: opened(%s) for write' % self.fname)
+ self.open_for_write = True
+ else:
+ # read
+ assert self.fnode is not None
+ uri = self.fnode.get_uri()
+
+ # XXX make this go away
+ if hasattr(self.fnode, 'tmp_fname'):
+ self.fname = self.fnode.tmp_fname
+ log('TFF: reopening(%s) for reading' % self.fname)
+ else:
+ log('TFF: fetching file from cache for reading')
+ self.fname = self.tfs.cache.get_file(uri)
+
+ self.file = os.fdopen(os.open(self.fname, flags, *mode), m)
+ self.fd = self.file.fileno()
+ self.open_for_write = False
+ log('TFF: opened(%s) for read' % self.fname)
+ except:
+ log_exc()
+ raise
+
+ def log(self, msg):
+ log("<TFF(%s:%s)> %s" % (os.path.basename(self.fname), self.name, msg))
+
+ @logexc
+ def read(self, size, offset):
+ self.log('read(%r, %r)' % (size, offset, ))
+ self.file.seek(offset)
+ return self.file.read(size)
+
+ @logexc
+ def write(self, buf, offset):
+ self.log("write(-%s-, %r)" % (len(buf), offset))
+ if not self.open_for_write:
+ return -errno.EACCES
+ self.file.seek(offset)
+ self.file.write(buf)
+ return len(buf)
+
+ @logexc
+ def release(self, flags):
+ self.log("release(%r)" % (flags,))
+ self.file.close()
+ if self.open_for_write:
+ size = os.path.getsize(self.fname)
+ self.fnode.size = size
+ file_cap = self.tfs.upload(self.fname)
+ self.fnode.ro_uri = file_cap
+ self.tfs.add_child(self.parent.get_uri(), self.name, file_cap)
+ self.log("uploaded: %s" % (file_cap,))
+
+ # dbg
+ print_tree()
+
+ def _fflush(self):
+ if 'w' in self.file.mode or 'a' in self.file.mode:
+ self.file.flush()
+
+ @logexc
+ def fsync(self, isfsyncfile):
+ self.log("fsync(%r)" % (isfsyncfile,))
+ self._fflush()
+ if isfsyncfile and hasattr(os, 'fdatasync'):
+ os.fdatasync(self.fd)
+ else:
+ os.fsync(self.fd)
+
+ @logexc
+ def flush(self):
+ self.log("flush()")
+ self._fflush()
+ # cf. xmp_flush() in fusexmp_fh.c
+ os.close(os.dup(self.fd))
+
+ @logexc
+ def fgetattr(self):
+ self.log("fgetattr()")
+ s = os.fstat(self.fd)
+ self.log("fgetattr() -> %r" % (s,))
+ return s
+
+ @logexc
+ def ftruncate(self, len):
+ self.log("ftruncate(%r)" % (len,))
+ self.file.truncate(len)
+
+class ObjFetcher(object):
+ def get_tahoe_file(self, path, flags, *mode):
+ log('objfetcher.get_tahoe_file(%r, %r, %r, %r)' % (self, path, flags, mode))
+ return TahoeFuseFile(path, flags, *mode)
+fetcher = ObjFetcher()
+
+class TahoeFuse(fuse.Fuse):
+
+ def __init__(self, tfs, *args, **kw):
+ log("TF: __init__(%r, %r)" % (args, kw))
+
+ self.tfs = tfs
+ _tfs_ = tfs
+ class MyFuseFile(TahoeFuseFile):
+ tfs = _tfs_
+ self.file_class = MyFuseFile
+ log("TF: file_class: %r" % (self.file_class,))
+
+ fuse.Fuse.__init__(self, *args, **kw)
+
+ #import thread
+ #thread.start_new_thread(self.launch_reactor, ())
+
+ def log(self, msg):
+ log("<TF> %s" % (msg, ))
+
+ @logexc
+ def readlink(self, path):
+ self.log("readlink(%r)" % (path,))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def unlink(self, path):
+ self.log("unlink(%r)" % (path,))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def rmdir(self, path):
+ self.log("rmdir(%r)" % (path,))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def symlink(self, path, path1):
+ self.log("symlink(%r, %r)" % (path, path1))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def rename(self, path, path1):
+ self.log("rename(%r, %r)" % (path, path1))
+ self.tfs.rename(path, path1)
+
+ @logexc
+ def link(self, path, path1):
+ self.log("link(%r, %r)" % (path, path1))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def chmod(self, path, mode):
+ self.log("chmod(%r, %r)" % (path, mode))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def chown(self, path, user, group):
+ self.log("chown(%r, %r, %r)" % (path, user, group))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def truncate(self, path, len):
+ self.log("truncate(%r, %r)" % (path, len))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def utime(self, path, times):
+ self.log("utime(%r, %r)" % (path, times))
+ return -errno.EOPNOTSUPP
+
+ @logexc
+ def statfs(self):
+ self.log("statfs()")
+ """
+ Should return an object with statvfs attributes (f_bsize, f_frsize...).
+ Eg., the return value of os.statvfs() is such a thing (since py 2.2).
+ If you are not reusing an existing statvfs object, start with
+ fuse.StatVFS(), and define the attributes.
+
+ To provide usable information (ie., you want sensible df(1)
+ output, you are suggested to specify the following attributes:
+
+ - f_bsize - preferred size of file blocks, in bytes
+ - f_frsize - fundamental size of file blcoks, in bytes
+ [if you have no idea, use the same as blocksize]
+ - f_blocks - total number of blocks in the filesystem
+ - f_bfree - number of free blocks
+ - f_files - total number of file inodes
+ - f_ffree - nunber of free file inodes
+ """
+
+ return os.statvfs(".")
+
+ def fsinit(self):
+ self.log("fsinit()")
+
+ def main(self, *a, **kw):
+ self.log("main(%r, %r)" % (a, kw))
+
+ return fuse.Fuse.main(self, *a, **kw)
+
+ ##################################################################
+
+ @logexc
+ def readdir(self, path, offset):
+ log('readdir(%r, %r)' % (path, offset))
+ node = self.tfs.get_path(path)
+ if node is None:
+ return -errno.ENOENT
+ dirlist = ['.', '..'] + node.children.keys()
+ log('dirlist = %r' % (dirlist,))
+ return [fuse.Direntry(d) for d in dirlist]
+
+ @logexc
+ def getattr(self, path):
+ log('getattr(%r)' % (path,))
+ node = self.tfs.get_path(path)
+ if node is None:
+ return -errno.ENOENT
+ return node.get_stat()
+
+ @logexc
+ def access(self, path, mode):
+ self.log("access(%r, %r)" % (path, mode))
+ node = self.tfs.get_path(path)
+ if not node:
+ return -errno.ENOENT
+ accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
+ if (mode & 0222):
+ if not node.writable():
+ log('write access denied for %s (req:%o)' % (path, mode, ))
+ return -errno.EACCES
+ #else:
+ #log('access granted for %s' % (path, ))
+
+ @logexc
+ def mkdir(self, path, mode):
+ self.log("mkdir(%r, %r)" % (path, mode))
+ self.tfs.mkdir(path)
+
+def launch_tahoe_fuse(tfs, argv):
+ sys.argv = ['tahoe fuse'] + list(argv)
+ server = TahoeFuse(tfs, version="%prog " + fuse.__version__,
+ usage=USAGE,
+ dash_s_do='setsingle')
+ server.parse(errex=1)
+ server.main()
+
+
+def getbasedir(cap_name='root_dir'):
+ fname = os.path.expanduser("~/.tahoe/private/%s.cap" % (cap_name,))
+ if os.path.exists(fname):
+ f = file(fname, 'rb')
+ bd = f.read().strip()
+ f.close()
+ return bd
+ else:
+ return None
+
+def getnodeurl():
+ f = file(os.path.expanduser("~/.tahoe/node.url"), 'rb')
+ nu = f.read().strip()
+ f.close()
+ if nu[-1] != "/":
+ nu += "/"
+ return nu
+
+def fingerprint(uri):
+ if uri is None:
+ return None
+ return base64.b32encode(sha.new(uri).digest()).lower()[:6]
+
+class TStat(fuse.Stat):
+ def __init__(self, **kwargs):
+ fuse.Stat.__init__(self, **kwargs)
+
+ def __repr__(self):
+ return "<Stat%r" % {
+ 'st_mode': self.st_mode,
+ 'st_ino': self.st_ino,
+ 'st_dev': self.st_dev,
+ 'st_nlink': self.st_nlink,
+ 'st_uid': self.st_uid,
+ 'st_gid': self.st_gid,
+ 'st_size': self.st_size,
+ 'st_atime': self.st_atime,
+ 'st_mtime': self.st_mtime,
+ 'st_ctime': self.st_ctime,
+ }
+
+class Directory(object):
+ def __init__(self, ro_uri, rw_uri):
+ self.ro_uri = ro_uri
+ self.rw_uri = rw_uri
+ assert (rw_uri or ro_uri)
+ self.children = {}
+
+ def __repr__(self):
+ return "<Directory %s>" % (fingerprint(self.get_uri()),)
+
+ def get_children(self):
+ return self.children.keys()
+
+ def get_child(self, name):
+ return self.children[name]
+
+ def add_child(self, name, file_node):
+ self.children[name] = file_node
+
+ def remove_child(self, name):
+ del self.children[name]
+
+ def get_uri(self):
+ return self.rw_uri or self.ro_uri
+
+ def writable(self):
+ return self.rw_uri and self.rw_uri != self.ro_uri
+
+ def pprint(self, prefix='', printed=None):
+ ret = []
+ if printed is None:
+ printed = set()
+ writable = self.writable() and '+' or ' '
+ if self in printed:
+ ret.append(" %s/%s ... <%s>" % (prefix, writable, fingerprint(self.get_uri()), ))
+ else:
+ ret.append("[%s] %s/%s" % (fingerprint(self.get_uri()), prefix, writable, ))
+ printed.add(self)
+ for name,f in sorted(self.children.items()):
+ ret.append(f.pprint(' ' * (len(prefix)+1)+name, printed))
+ return '\n'.join(ret)
+
+ def get_stat(self):
+ s = TStat(st_mode = stat.S_IFDIR | 0755, st_nlink = 2)
+ log("%s.get_stat()->%s" % (self, s))
+ return s
+
+class File(object):
+ def __init__(self, size, ro_uri, metadata):
+ self.size = size
+ if ro_uri:
+ ro_uri = str(ro_uri)
+ self.ro_uri = ro_uri
+ self.metadata = metadata or {}
+
+ def __repr__(self):
+ return "<File %s>" % (fingerprint(self.ro_uri) or [self.tmp_fname],)
+
+ def pprint(self, prefix='', printed=None):
+ times, remainder = self.get_times()
+ return " %s (%s) %s %s" % (prefix, self.size, times, remainder)
+
+ def get_times(self):
+ rem = self.metadata.copy()
+ now = time.time()
+ times = {}
+ for T in ['c', 'm']:
+ t = rem.pop('%stime'%T, None)
+ if not t:
+ t = 'none'
+ elif (now-t) < 86400:
+ t = time.strftime('%a:%H:%M:%S', time.localtime(t))
+ else:
+ t = time.strftime('%Y:%b:%d:%H:%M', time.localtime(t))
+ times[T] = t
+ return times, rem
+
+ def get_stat(self):
+ if hasattr(self, 'tmp_fname'):
+ s = os.stat(self.tmp_fname)
+ log("%s.get_stat()->%s" % (self, s))
+ else:
+ s = TStat(st_size=self.size, st_mode = stat.S_IFREG | 0444, st_nlink = 1)
+ log("%s.get_stat()->%s" % (self, s))
+ return s
+
+ def get_uri(self):
+ return self.ro_uri
+
+ def writable(self):
+ #return not self.ro_uri
+ return True
+
+class TFS(object):
+ def __init__(self, nodeurl, root_uri):
+ self.nodeurl = nodeurl
+ self.root_uri = root_uri
+ self.dirs = {}
+
+ self.cache = FileCache(nodeurl, os.path.expanduser('~/.tahoe/_cache'))
+ ro_uri = NewDirectoryURI.init_from_string(self.root_uri).get_readonly()
+ self.root = Directory(ro_uri, self.root_uri)
+ self.load_dir('<root>', self.root)
+
+ def log(self, msg):
+ log("<TFS> %s" % (msg, ))
+
+ def pprint(self):
+ return self.root.pprint()
+
+ def get_parent_name_and_child(self, path):
+ dirname, name = os.path.split(path)
+ parent = self.get_path(dirname)
+ try:
+ child = parent.get_child(name)
+ return parent, name, child
+ except KeyError:
+ return parent, name, None
+
+ def get_path(self, path):
+ comps = path.strip('/').split('/')
+ if comps == ['']:
+ comps = []
+ cursor = self.root
+ for comp in comps:
+ if not isinstance(cursor, Directory):
+ self.log('path "%s" is not a dir' % (path,))
+ return None
+ try:
+ cursor = cursor.children[comp]
+ except KeyError:
+ self.log('path "%s" not found' % (path,))
+ return None
+ return cursor
+
+ def load_dir(self, name, dirobj):
+ print 'loading', name, dirobj
+ url = self.nodeurl + "uri/%s?t=json" % urllib.quote(dirobj.get_uri())
+ data = urllib.urlopen(url).read()
+ parsed = simplejson.loads(data)
+ nodetype, d = parsed
+ assert nodetype == 'dirnode'
+ for name,details in d['children'].items():
+ name = str(name)
+ ctype, cattrs = details
+ if ctype == 'dirnode':
+ cobj = self.dir_for(name, cattrs.get('ro_uri'), cattrs.get('rw_uri'))
+ else:
+ assert ctype == "filenode"
+ cobj = File(cattrs.get('size'), cattrs.get('ro_uri'), cattrs.get('metadata'))
+ dirobj.children[name] = cobj
+
+ def dir_for(self, name, ro_uri, rw_uri):
+ if ro_uri:
+ ro_uri = str(ro_uri)
+ if rw_uri:
+ rw_uri = str(rw_uri)
+ uri = rw_uri or ro_uri
+ assert uri
+ dirobj = self.dirs.get(uri)
+ if not dirobj:
+ dirobj = Directory(ro_uri, rw_uri)
+ self.dirs[uri] = dirobj
+ self.load_dir(name, dirobj)
+ return dirobj
+
+ def upload(self, fname):
+ self.log('upload(%r)' % (fname,))
+ fh = file(fname, 'rb')
+ url = self.nodeurl + "uri"
+ file_cap = do_http('PUT', url, fh)
+ self.log('uploaded to: %r' % (file_cap,))
+ return file_cap
+
+ def add_child(self, parent_dir_uri, child_name, child_uri):
+ self.log('add_child(%r, %r, %r)' % (parent_dir_uri, child_name, child_uri,))
+ url = self.nodeurl + "uri/%s/%s?t=uri" % (urllib.quote(parent_dir_uri), urllib.quote(child_name), )
+ child_cap = do_http('PUT', url, child_uri)
+ assert child_cap == child_uri
+ self.log('added child %r with %r to %r' % (child_name, child_uri, parent_dir_uri))
+ return child_uri
+
+ def remove_child(self, parent_uri, child_name):
+ self.log('remove_child(%r, %r)' % (parent_uri, child_name, ))
+ url = self.nodeurl + "uri/%s/%s" % (urllib.quote(parent_uri), urllib.quote(child_name))
+ resp = do_http('DELETE', url)
+ self.log('child removal yielded %r' % (resp,))
+
+ def mkdir(self, path):
+ self.log('mkdir(%r)' % (path,))
+ url = self.nodeurl + "uri?t=mkdir"
+ new_dir_cap = do_http('PUT', url)
+ parent_path, name = os.path.split(path)
+ self.log('parent_path, name = %s, %s' % (parent_path, name,))
+ parent = self.get_path(parent_path)
+ self.log('parent = %s' % (parent, ))
+ self.log('new_dir_cap = %s' % (new_dir_cap, ))
+ child_uri = self.add_child(parent.get_uri(), name, new_dir_cap)
+ ro_uri = NewDirectoryURI.init_from_string(child_uri).get_readonly()
+ child = Directory(ro_uri, child_uri)
+ parent.add_child(name, child)
+
+ def rename(self, path, path1):
+ self.log('rename(%s, %s)' % (path, path1))
+ parent, name, child = self.get_parent_name_and_child(path)
+ child_uri = child.get_uri()
+ new_parent_path, new_child_name = os.path.split(path1)
+ new_parent = self.get_path(new_parent_path)
+ self.add_child(new_parent.get_uri(), new_child_name, child_uri)
+ self.remove_child(parent.get_uri(), name)
+ parent.remove_child(name)
+ new_parent.add_child(new_child_name, child)
+
+class FileCache(object):
+ def __init__(self, nodeurl, cachedir):
+ self.nodeurl = nodeurl
+ self.cachedir = cachedir
+ if not os.path.exists(self.cachedir):
+ os.makedirs(self.cachedir)
+ self.tmpdir = os.path.join(self.cachedir, 'tmp')
+ if not os.path.exists(self.tmpdir):
+ os.makedirs(self.tmpdir)
+
+ def log(self, msg):
+ log("<FC> %s" % (msg, ))
+
+ def get_file(self, uri):
+ self.log('get_file(%s)' % (uri,))
+ if uri.startswith("URI:LIT"):
+ return self.get_literal(uri)
+ else:
+ return self.get_chk(uri)
+
+ def get_literal(self, uri):
+ h = sha.new(uri).digest()
+ u = LiteralFileURI.init_from_string(uri)
+ fname = os.path.join(self.cachedir, '__'+base64.b32encode(h).lower())
+ size = len(u.data)
+ self.log('writing literal file %s (%s)' % (fname, size, ))
+ fh = open(fname, 'wb')
+ fh.write(u.data)
+ fh.close()
+ return fname
+
+ def get_chk(self, uri):
+ u = CHKFileURI.init_from_string(str(uri))
+ storage_index = u.storage_index
+ size = u.size
+ fname = os.path.join(self.cachedir, base64.b32encode(storage_index).lower())
+ if os.path.exists(fname):
+ fsize = os.path.getsize(fname)
+ if fsize == size:
+ return fname
+ else:
+ self.log('warning file "%s" is too short %s < %s' % (fname, fsize, size))
+ self.log('downloading file %s (%s)' % (fname, size, ))
+ fh = open(fname, 'wb')
+ url = "%suri/%s" % (self.nodeurl, uri)
+ download = urllib.urlopen(''.join([ self.nodeurl, "uri/", uri ]))
+ while True:
+ chunk = download.read(4096)
+ if not chunk:
+ break
+ fh.write(chunk)
+ fh.close()
+ return fname
+
+ def tmp_file(self, id):
+ fname = os.path.join(self.tmpdir, base64.b32encode(id).lower())
+ return fname
+
+def print_tree():
+ log('tree:\n' + _tfs.pprint())
+
+def main(argv):
+ log("\n\nmain(%s)" % (argv,))
+
+ if not argv:
+ argv = ['--help']
+ if len(argv) == 1 and argv[0] in ['-h', '--help', '--version']:
+ #print USAGE
+ launch_tahoe_fuse(None, argv)
+ return -2
+
+ if not argv[0].startswith('-'):
+ cap_name = argv[0]
+ basedir = getbasedir(cap_name)
+ if basedir is None:
+ print 'root dir named "%s" not found.' % (cap_name,)
+ return -2
+ argv = argv[1:]
+ else:
+ basedir = getbasedir() # default 'root_dir'
+
+ if argv[-1].startswith('-'):
+ print 'mountpoint not given'
+ return -2
+ mountpoint = os.path.abspath(argv[-1])
+ if not os.path.exists(mountpoint):
+ #raise OSError(2, 'No such file or directory: "%s"' % (mountpoint,))
+ print 'No such file or directory: "%s"' % (mountpoint,)
+ return -2
+
+ nodeurl = getnodeurl()
+
+ tfs = TFS(nodeurl, basedir)
+ print tfs.pprint()
+
+ # make tfs instance accesible to print_tree() for dbg
+ global _tfs
+ _tfs = tfs
+
+ launch_tahoe_fuse(tfs, argv)
+
+if __name__ == '__main__':
+ main(sys.argv)