diff --git a/mac/macfuse/fuse.py b/mac/macfuse/fuse.py new file mode 100644 index 000000000..51f602bab --- /dev/null +++ b/mac/macfuse/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/macfuse/fuseparts/__init__.py b/mac/macfuse/fuseparts/__init__.py new file mode 100644 index 000000000..09888577e --- /dev/null +++ b/mac/macfuse/fuseparts/__init__.py @@ -0,0 +1 @@ +__version__ = "0.2" diff --git a/mac/macfuse/fuseparts/_fusemodule.so b/mac/macfuse/fuseparts/_fusemodule.so new file mode 100644 index 000000000..c802be463 Binary files /dev/null and b/mac/macfuse/fuseparts/_fusemodule.so differ diff --git a/mac/macfuse/fuseparts/setcompatwrap.py b/mac/macfuse/fuseparts/setcompatwrap.py new file mode 100644 index 000000000..7ead738a6 --- /dev/null +++ b/mac/macfuse/fuseparts/setcompatwrap.py @@ -0,0 +1,5 @@ +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 new file mode 100644 index 000000000..9cf40ae76 --- /dev/null +++ b/mac/macfuse/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/tahoefuse.py b/mac/macfuse/tahoefuse.py new file mode 100644 index 000000000..d61a3ef34 --- /dev/null +++ b/mac/macfuse/tahoefuse.py @@ -0,0 +1,659 @@ +#!/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 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 + +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) + 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 + 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 main(tfs): + + usage = "Userspace tahoe fs: cache a tahoe tree and present via fuse\n" + fuse.Fuse.fusage + + server = TahoeFuse(tfs, version="%prog " + fuse.__version__, + usage=usage, + dash_s_do='setsingle') + server.parse(errex=1) + server.main() + + +def getbasedir(): + f = file(os.path.expanduser("~/.tahoe/private/root_dir.cap"), 'rb') + bd = f.read().strip() + f.close() + return bd + +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): + self.size = size + if ro_uri: + ro_uri = str(ro_uri) + self.ro_uri = ro_uri + + def __repr__(self): + return "" % (fingerprint(self.ro_uri) or [self.tmp_fname],) + + def pprint(self, prefix='', printed=None): + return " %s (%s)" % (prefix, self.size, ) + + 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')) + 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()) + +if __name__ == '__main__': + log("\n\nmain()") + tfs = TFS(getnodeurl(), getbasedir()) + print tfs.pprint() + + # make tfs instance accesible to print_tree() for dbg + global _tfs + _tfs = tfs + + main(tfs) +