From: robk-tahoe Date: Wed, 20 Feb 2008 00:18:17 +0000 (-0700) Subject: macfuse: move macfuse files around to simplify pythonpath X-Git-Url: https://git.rkrishnan.org/(%5B%5E?a=commitdiff_plain;h=6f5ccb170753fb003c41cb899d0d43468c3926f9;p=tahoe-lafs%2Ftahoe-lafs.git macfuse: move macfuse files around to simplify pythonpath the mac/macfuse subdirectory needed to be added to the pythonpath in order to build a binary incorporating the mac fuse system. this change should make those modules accessible relative to the mac/ directory which is implicitly included in the .app build process. --- diff --git a/mac/allmydata_tahoe.py b/mac/allmydata_tahoe.py index cd4c780e..a21fc796 100644 --- a/mac/allmydata_tahoe.py +++ b/mac/allmydata_tahoe.py @@ -27,8 +27,8 @@ class FuseOptions(usage.Options): 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 = [ diff --git a/mac/fuse.py b/mac/fuse.py new file mode 100644 index 00000000..51f602ba --- /dev/null +++ b/mac/fuse.py @@ -0,0 +1,997 @@ +# +# Copyright (C) 2001 Jeff Epler +# Copyright (C) 2006 Csaba Henk +# +# 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'} diff --git a/mac/fuseparts/__init__.py b/mac/fuseparts/__init__.py new file mode 100644 index 00000000..09888577 --- /dev/null +++ b/mac/fuseparts/__init__.py @@ -0,0 +1 @@ +__version__ = "0.2" diff --git a/mac/fuseparts/_fusemodule.so b/mac/fuseparts/_fusemodule.so new file mode 100644 index 00000000..c802be46 Binary files /dev/null and b/mac/fuseparts/_fusemodule.so differ diff --git a/mac/fuseparts/setcompatwrap.py b/mac/fuseparts/setcompatwrap.py new file mode 100644 index 00000000..7ead738a --- /dev/null +++ b/mac/fuseparts/setcompatwrap.py @@ -0,0 +1,5 @@ +try: + set() + set = set +except: + from sets import Set as set diff --git a/mac/fuseparts/subbedopts.py b/mac/fuseparts/subbedopts.py new file mode 100644 index 00000000..9cf40ae7 --- /dev/null +++ b/mac/fuseparts/subbedopts.py @@ -0,0 +1,268 @@ +# +# Copyright (C) 2006 Csaba Henk +# +# 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) diff --git a/mac/macfuse/fuse.py b/mac/macfuse/fuse.py deleted file mode 100644 index 51f602ba..00000000 --- a/mac/macfuse/fuse.py +++ /dev/null @@ -1,997 +0,0 @@ -# -# Copyright (C) 2001 Jeff Epler -# Copyright (C) 2006 Csaba Henk -# -# 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'} diff --git a/mac/macfuse/fuseparts/__init__.py b/mac/macfuse/fuseparts/__init__.py deleted file mode 100644 index 09888577..00000000 --- a/mac/macfuse/fuseparts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.2" diff --git a/mac/macfuse/fuseparts/_fusemodule.so b/mac/macfuse/fuseparts/_fusemodule.so deleted file mode 100644 index c802be46..00000000 Binary files a/mac/macfuse/fuseparts/_fusemodule.so and /dev/null differ diff --git a/mac/macfuse/fuseparts/setcompatwrap.py b/mac/macfuse/fuseparts/setcompatwrap.py deleted file mode 100644 index 7ead738a..00000000 --- a/mac/macfuse/fuseparts/setcompatwrap.py +++ /dev/null @@ -1,5 +0,0 @@ -try: - set() - set = set -except: - from sets import Set as set diff --git a/mac/macfuse/fuseparts/subbedopts.py b/mac/macfuse/fuseparts/subbedopts.py deleted file mode 100644 index 9cf40ae7..00000000 --- a/mac/macfuse/fuseparts/subbedopts.py +++ /dev/null @@ -1,268 +0,0 @@ -# -# Copyright (C) 2006 Csaba Henk -# -# 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) diff --git a/mac/macfuse/tahoefuse.py b/mac/macfuse/tahoefuse.py deleted file mode 100644 index 470898d9..00000000 --- a/mac/macfuse/tahoefuse.py +++ /dev/null @@ -1,713 +0,0 @@ -#!/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__ = '' % (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__ = '' % (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(" %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(" %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 "" % (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 "" % (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('', self.root) - - def log(self, msg): - log(" %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(" %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) diff --git a/mac/tahoefuse.py b/mac/tahoefuse.py new file mode 100644 index 00000000..470898d9 --- /dev/null +++ b/mac/tahoefuse.py @@ -0,0 +1,713 @@ +#!/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__ = '' % (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__ = '' % (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(" %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(" %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 "" % (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 "" % (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('', self.root) + + def log(self, msg): + log(" %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(" %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)