diff options
Diffstat (limited to 'fuse.py')
| -rw-r--r-- | fuse.py | 518 |
1 files changed, 329 insertions, 189 deletions
@@ -17,45 +17,60 @@ from __future__ import division from ctypes import * from ctypes.util import find_library from errno import * -from functools import partial from os import strerror from platform import machine, system from signal import signal, SIGINT, SIG_DFL from stat import S_IFDIR from traceback import print_exc +import logging -__version__ = '1.1' +try: + from functools import partial +except ImportError: + # http://docs.python.org/library/functools.html#functools.partial + def partial(func, *args, **keywords): + def newfunc(*fargs, **fkeywords): + newkeywords = keywords.copy() + newkeywords.update(fkeywords) + return func(*(args + fargs), **newkeywords) + + newfunc.func = func + newfunc.args = args + newfunc.keywords = keywords + return newfunc + +if not hasattr(__builtins__, 'basestring'): + basestring = str + +class c_timespec(Structure): + _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)] + +class c_utimbuf(Structure): + _fields_ = [('actime', c_timespec), ('modtime', c_timespec)] + +class c_stat(Structure): + pass # Platform dependent _system = system() _machine = machine() if _system == 'Darwin': - _libfuse_path = find_library('fuse4x') or find_library('osxfuse') or \ - find_library('fuse') + _libiconv = CDLL(find_library('iconv'), RTLD_GLOBAL) # libfuse dependency + _libfuse_path = (find_library('fuse4x') or find_library('osxfuse') or + find_library('fuse')) else: _libfuse_path = find_library('fuse') + if not _libfuse_path: raise EnvironmentError('Unable to find libfuse') - -if _system == 'Darwin': - _libiconv = CDLL(find_library('iconv'), RTLD_GLOBAL) # libfuse dependency -_libfuse = CDLL(_libfuse_path) +else: + _libfuse = CDLL(_libfuse_path) if _system == 'Darwin' and hasattr(_libfuse, 'macfuse_version'): _system = 'Darwin-MacFuse' -class c_timespec(Structure): - _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)] - -class c_utimbuf(Structure): - _fields_ = [('actime', c_timespec), ('modtime', c_timespec)] - -class c_stat(Structure): - pass # Platform dependent - - if _system in ('Darwin', 'Darwin-MacFuse', 'FreeBSD'): ENOTSUP = 45 c_dev_t = c_int32 @@ -115,9 +130,12 @@ elif _system == 'Linux': c_off_t = c_longlong c_pid_t = c_int c_uid_t = c_uint - setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t, c_int) - getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t) - + setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), + c_size_t, c_int) + + getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), + c_size_t) + if _machine == 'x86_64': c_stat._fields_ = [ ('st_dev', c_dev_t), @@ -187,8 +205,12 @@ class c_statvfs(Structure): if _system == 'FreeBSD': c_fsblkcnt_t = c_uint64 c_fsfilcnt_t = c_uint64 - setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t, c_int) - getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t) + setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), + c_size_t, c_int) + + getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), + c_size_t) + class c_statvfs(Structure): _fields_ = [ ('f_bavail', c_fsblkcnt_t), @@ -223,6 +245,7 @@ class fuse_context(Structure): _libfuse.fuse_get_context.restype = POINTER(fuse_context) + class fuse_operations(Structure): _fields_ = [ ('getattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat))), @@ -240,10 +263,13 @@ class fuse_operations(Structure): ('truncate', CFUNCTYPE(c_int, c_char_p, c_off_t)), ('utime', c_voidp), # Deprecated, use utimens ('open', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), - ('read', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, c_off_t, - POINTER(fuse_file_info))), - ('write', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, c_off_t, - POINTER(fuse_file_info))), + + ('read', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, + c_off_t, POINTER(fuse_file_info))), + + ('write', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, + c_off_t, POINTER(fuse_file_info))), + ('statfs', CFUNCTYPE(c_int, c_char_p, POINTER(c_statvfs))), ('flush', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), ('release', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), @@ -253,20 +279,36 @@ class fuse_operations(Structure): ('listxattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)), ('removexattr', CFUNCTYPE(c_int, c_char_p, c_char_p)), ('opendir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), - ('readdir', CFUNCTYPE(c_int, c_char_p, c_voidp, CFUNCTYPE(c_int, c_voidp, - c_char_p, POINTER(c_stat), c_off_t), c_off_t, POINTER(fuse_file_info))), + + ('readdir', CFUNCTYPE(c_int, c_char_p, c_voidp, + CFUNCTYPE(c_int, c_voidp, c_char_p, + POINTER(c_stat), c_off_t), + c_off_t, POINTER(fuse_file_info))), + ('releasedir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), - ('fsyncdir', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))), + + ('fsyncdir', CFUNCTYPE(c_int, c_char_p, c_int, + POINTER(fuse_file_info))), + ('init', CFUNCTYPE(c_voidp, c_voidp)), ('destroy', CFUNCTYPE(c_voidp, c_voidp)), ('access', CFUNCTYPE(c_int, c_char_p, c_int)), - ('create', CFUNCTYPE(c_int, c_char_p, c_mode_t, POINTER(fuse_file_info))), - ('ftruncate', CFUNCTYPE(c_int, c_char_p, c_off_t, POINTER(fuse_file_info))), + + ('create', CFUNCTYPE(c_int, c_char_p, c_mode_t, + POINTER(fuse_file_info))), + + ('ftruncate', CFUNCTYPE(c_int, c_char_p, c_off_t, + POINTER(fuse_file_info))), + ('fgetattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat), - POINTER(fuse_file_info))), - ('lock', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info), c_int, c_voidp)), + POINTER(fuse_file_info))), + + ('lock', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info), + c_int, c_voidp)), + ('utimens', CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf))), - ('bmap', CFUNCTYPE(c_int, c_char_p, c_size_t, POINTER(c_ulonglong)))] + ('bmap', CFUNCTYPE(c_int, c_char_p, c_size_t, POINTER(c_ulonglong))), + ] def time_of_timespec(ts): @@ -298,14 +340,18 @@ class FUSE(object): """This class is the lower level interface and should not be subclassed under normal use. Its methods are called by fuse. Assumes API version 2.6 or later.""" - - def __init__(self, operations, mountpoint, raw_fi=False, **kwargs): + + def __init__(self, operations, mountpoint, raw_fi=False, encoding='utf-8', + **kwargs): + """Setting raw_fi to True will cause FUSE to pass the fuse_file_info class as is to Operations, instead of just the fh field. This gives you access to direct_io, keep_cache, etc.""" - + self.operations = operations self.raw_fi = raw_fi + self.encoding = encoding + args = ['fuse'] if kwargs.pop('foreground', False): args.append('-f') @@ -315,29 +361,39 @@ class FUSE(object): args.append('-s') kwargs.setdefault('fsname', operations.__class__.__name__) args.append('-o') - args.append(','.join(key if val == True else '%s=%s' % (key, val) - for key, val in kwargs.items())) + args.append(','.join(self._normalize_fuse_options(**kwargs))) args.append(mountpoint) + + args = [arg.encode(encoding) for arg in args] argv = (c_char_p * len(args))(*args) - + fuse_ops = fuse_operations() for name, prototype in fuse_operations._fields_: if prototype != c_voidp and getattr(operations, name, None): - op = partial(self._wrapper_, getattr(self, name)) + op = partial(self._wrapper, getattr(self, name)) setattr(fuse_ops, name, prototype(op)) - + old_handler = signal(SIGINT, SIG_DFL) - + err = _libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops), sizeof(fuse_ops), None) - + signal(SIGINT, old_handler) - + del self.operations # Invoke the destructor if err: raise RuntimeError(err) - - def _wrapper_(self, func, *args, **kwargs): + + @staticmethod + def _normalize_fuse_options(**kargs): + for key, value in kargs.items(): + if isinstance(value, bool): + if value is True: yield key + else: + yield '%s=%s' % (key, value) + + @staticmethod + def _wrapper(func, *args, **kwargs): """Decorator for the methods that follow""" try: return func(*args, **kwargs) or 0 @@ -346,129 +402,176 @@ class FUSE(object): except: print_exc() return -EFAULT - + def getattr(self, path, buf): return self.fgetattr(path, buf, None) - + def readlink(self, path, buf, bufsize): - ret = self.operations('readlink', path) + ret = self.operations('readlink', path).encode(self.encoding) data = create_string_buffer(ret[:bufsize - 1]) memmove(buf, data, len(data)) return 0 - + def mknod(self, path, mode, dev): - return self.operations('mknod', path, mode, dev) - + return self.operations('mknod', path.decode(self.encoding), mode, dev) + def mkdir(self, path, mode): - return self.operations('mkdir', path, mode) - + return self.operations('mkdir', path.decode(self.encoding), mode) + def unlink(self, path): - return self.operations('unlink', path) - + return self.operations('unlink', path.decode(self.encoding)) + def rmdir(self, path): - return self.operations('rmdir', path) - + return self.operations('rmdir', path.decode(self.encoding)) + def symlink(self, source, target): - return self.operations('symlink', target, source) - + return self.operations('symlink', target.decode(self.encoding), + source.decode(self.encoding)) + def rename(self, old, new): - return self.operations('rename', old, new) - + return self.operations('rename', old.decode(self.encoding), + new.decode(self.encoding)) + def link(self, source, target): - return self.operations('link', target, source) - + return self.operations('link', target.decode(self.encoding), + source.decode(self.encoding)) + def chmod(self, path, mode): - return self.operations('chmod', path, mode) - + return self.operations('chmod', path.decode(self.encoding), mode) + def chown(self, path, uid, gid): # Check if any of the arguments is a -1 that has overflowed if c_uid_t(uid + 1).value == 0: uid = -1 if c_gid_t(gid + 1).value == 0: gid = -1 - return self.operations('chown', path, uid, gid) - + + return self.operations('chown', path.decode(self.encoding), uid, gid) + def truncate(self, path, length): - return self.operations('truncate', path, length) - + return self.operations('truncate', path.decode(self.encoding), length) + def open(self, path, fip): fi = fip.contents if self.raw_fi: - return self.operations('open', path, fi) + return self.operations('open', path.decode(self.encoding), fi) else: - fi.fh = self.operations('open', path, fi.flags) + fi.fh = self.operations('open', path.decode(self.encoding), + fi.flags) + return 0 - + def read(self, path, buf, size, offset, fip): - fh = fip.contents if self.raw_fi else fip.contents.fh - ret = self.operations('read', path, size, offset, fh) - if not ret: - return 0 + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + ret = self.operations('read', path.decode(self.encoding), size, + offset, fh) + + if not ret: return 0 + data = create_string_buffer(ret[:size], size) memmove(buf, data, size) return size - + def write(self, path, buf, size, offset, fip): data = string_at(buf, size) - fh = fip.contents if self.raw_fi else fip.contents.fh - return self.operations('write', path, data, offset, fh) - + + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('write', path.decode(self.encoding), data, + offset, fh) + def statfs(self, path, buf): stv = buf.contents - attrs = self.operations('statfs', path) + attrs = self.operations('statfs', path.decode(self.encoding)) for key, val in attrs.items(): if hasattr(stv, key): setattr(stv, key, val) + return 0 - + def flush(self, path, fip): - fh = fip.contents if self.raw_fi else fip.contents.fh - return self.operations('flush', path, fh) - + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('flush', path.decode(self.encoding), fh) + def release(self, path, fip): - fh = fip.contents if self.raw_fi else fip.contents.fh - return self.operations('release', path, fh) - + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('release', path.decode(self.encoding), fh) + def fsync(self, path, datasync, fip): - fh = fip.contents if self.raw_fi else fip.contents.fh - return self.operations('fsync', path, datasync, fh) - + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('fsync', path.decode(self.encoding), datasync, + fh) + def setxattr(self, path, name, value, size, options, *args): data = string_at(value, size) - return self.operations('setxattr', path, name, data, options, *args) - + return self.operations('setxattr', path.decode(self.encoding), + name.decode(self.encoding), + data.decode(self.encoding), options, *args) + def getxattr(self, path, name, value, size, *args): - ret = self.operations('getxattr', path, name, *args) + ret = self.operations('getxattr', path.decode(self.encoding), + name.decode(self.encoding), *args) \ + .encode(self.encoding) + retsize = len(ret) buf = create_string_buffer(ret, retsize) # Does not add trailing 0 - if bool(value): - if retsize > size: - return -ERANGE + + if value: + if retsize > size: return -ERANGE + memmove(value, buf, retsize) + return retsize - + def listxattr(self, path, namebuf, size): - ret = self.operations('listxattr', path) - buf = create_string_buffer('\x00'.join(ret)) if ret else '' + ret = '\x00'.join(self.operations('listxattr', path) or '') \ + .encode(self.encoding) + + buf = create_string_buffer(ret) bufsize = len(buf) - if bool(namebuf): - if bufsize > size: - return -ERANGE + if namebuf: + if bufsize > size: return -ERANGE + memmove(namebuf, buf, bufsize) + return bufsize - + def removexattr(self, path, name): - return self.operations('removexattr', path, name) - + return self.operations('removexattr', path.decode(self.encoding), + name.decode(self.encoding)) + def opendir(self, path, fip): # Ignore raw_fi - fip.contents.fh = self.operations('opendir', path) + fip.contents.fh = self.operations('opendir', + path.decode(self.encoding)) + return 0 - + def readdir(self, path, buf, filler, offset, fip): # Ignore raw_fi - for item in self.operations('readdir', path, fip.contents.fh): - if isinstance(item, str): + for item in self.operations('readdir', path.decode(self.encoding), + fip.contents.fh): + + if isinstance(item, basestring): name, st, offset = item, None, 0 else: name, attrs, offset = item @@ -477,51 +580,74 @@ class FUSE(object): set_st_attrs(st, attrs) else: st = None - if filler(buf, name, st, offset) != 0: + + if filler(buf, name.encode(self.encoding), st, offset) != 0: break + return 0 - + def releasedir(self, path, fip): # Ignore raw_fi - return self.operations('releasedir', path, fip.contents.fh) - + return self.operations('releasedir', path.decode(self.encoding), + fip.contents.fh) + def fsyncdir(self, path, datasync, fip): # Ignore raw_fi - return self.operations('fsyncdir', path, datasync, fip.contents.fh) - + return self.operations('fsyncdir', path.decode(self.encoding), + datasync, fip.contents.fh) + def init(self, conn): return self.operations('init', '/') - + def destroy(self, private_data): return self.operations('destroy', '/') - + def access(self, path, amode): - return self.operations('access', path, amode) - + return self.operations('access', path.decode(self.encoding), amode) + def create(self, path, mode, fip): fi = fip.contents + path = path.decode(self.encoding) + if self.raw_fi: return self.operations('create', path, mode, fi) else: fi.fh = self.operations('create', path, mode) return 0 - + def ftruncate(self, path, length, fip): - fh = fip.contents if self.raw_fi else fip.contents.fh - return self.operations('truncate', path, length, fh) - + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('truncate', path.decode(self.encoding), + length, fh) + def fgetattr(self, path, buf, fip): memset(buf, 0, sizeof(c_stat)) + st = buf.contents - fh = fip and (fip.contents if self.raw_fi else fip.contents.fh) - attrs = self.operations('getattr', path, fh) + if not fip: + fh = fip + elif self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + attrs = self.operations('getattr', path.decode(self.encoding), fh) set_st_attrs(st, attrs) return 0 - + def lock(self, path, fip, cmd, lock): - fh = fip.contents if self.raw_fi else fip.contents.fh - return self.operations('lock', path, fh, cmd, lock) - + if self.raw_fi: + fh = fip.contents + else: + fh = fip.contents.fh + + return self.operations('lock', path.decode(self.encoding), fh, cmd, + lock) + def utimens(self, path, buf): if buf: atime = time_of_timespec(buf.contents.actime) @@ -529,158 +655,172 @@ class FUSE(object): times = (atime, mtime) else: times = None - return self.operations('utimens', path, times) - + + return self.operations('utimens', path.decode(self.encoding), times) + def bmap(self, path, blocksize, idx): - return self.operations('bmap', path, blocksize, idx) + return self.operations('bmap', path.decode(self.encoding), blocksize, + idx) class Operations(object): """This class should be subclassed and passed as an argument to FUSE on initialization. All operations should raise a FuseOSError exception on error. - + When in doubt of what an operation should do, check the FUSE header file or the corresponding system call man page.""" - + def __call__(self, op, *args): if not hasattr(self, op): raise FuseOSError(EFAULT) return getattr(self, op)(*args) - + def access(self, path, amode): return 0 - + bmap = None - + def chmod(self, path, mode): raise FuseOSError(EROFS) - + def chown(self, path, uid, gid): raise FuseOSError(EROFS) - + def create(self, path, mode, fi=None): """When raw_fi is False (default case), fi is None and create should return a numerical file handle. - When raw_fi is True the file handle should be set directly by create - and return 0.""" + + When raw_fi is True the file handle should be set directly by + create and return 0.""" raise FuseOSError(EROFS) - + def destroy(self, path): """Called on filesystem destruction. Path is always /""" pass - + def flush(self, path, fh): return 0 - + def fsync(self, path, datasync, fh): return 0 - + def fsyncdir(self, path, datasync, fh): return 0 - + def getattr(self, path, fh=None): """Returns a dictionary with keys identical to the stat C structure of stat(2). + st_atime, st_mtime and st_ctime should be floats. - NOTE: There is an incombatibility between Linux and Mac OS X concerning - st_nlink of directories. Mac OS X counts all files inside the directory, - while Linux counts only the subdirectories.""" - + + NOTE: There is an incombatibility between Linux and Mac OS X + concerning st_nlink of directories. Mac OS X counts all files + inside the directory, while Linux counts only the + subdirectories.""" + if path != '/': raise FuseOSError(ENOENT) return dict(st_mode=(S_IFDIR | 0755), st_nlink=2) - + def getxattr(self, path, name, position=0): raise FuseOSError(ENOTSUP) - + def init(self, path): - """Called on filesystem initialization. Path is always / - Use it instead of __init__ if you start threads on initialization.""" + """Called on filesystem initialization. (Path is always /) + + Use it instead of __init__ if you start threads on + initialization.""" pass - + def link(self, target, source): raise FuseOSError(EROFS) - + def listxattr(self, path): return [] - + lock = None - + def mkdir(self, path, mode): raise FuseOSError(EROFS) - + def mknod(self, path, mode, dev): raise FuseOSError(EROFS) - + def open(self, path, flags): - """When raw_fi is False (default case), open should return a numerical - file handle. + """When raw_fi is False (default case), open should return a + numerical file handle. + When raw_fi is True the signature of open becomes: open(self, path, fi) + and the file handle should be set directly.""" return 0 - + def opendir(self, path): """Returns a numerical file handle.""" return 0 - + def read(self, path, size, offset, fh): """Returns a string containing the data requested.""" raise FuseOSError(EIO) - + def readdir(self, path, fh): - """Can return either a list of names, or a list of (name, attrs, offset) - tuples. attrs is a dict as in getattr.""" + """Can return either a list of names, or a list of + (name, attrs, offset) tuples. attrs is a dict as in getattr.""" return ['.', '..'] - + def readlink(self, path): raise FuseOSError(ENOENT) - + def release(self, path, fh): return 0 - + def releasedir(self, path, fh): return 0 - + def removexattr(self, path, name): raise FuseOSError(ENOTSUP) - + def rename(self, old, new): raise FuseOSError(EROFS) - + def rmdir(self, path): raise FuseOSError(EROFS) - + def setxattr(self, path, name, value, options, position=0): raise FuseOSError(ENOTSUP) - + def statfs(self, path): - """Returns a dictionary with keys identical to the statvfs C structure - of statvfs(3). - On Mac OS X f_bsize and f_frsize must be a power of 2 (minimum 512).""" + """Returns a dictionary with keys identical to the statvfs C + structure of statvfs(3). + + On Mac OS X f_bsize and f_frsize must be a power of 2 + (minimum 512).""" return {} - + def symlink(self, target, source): raise FuseOSError(EROFS) - + def truncate(self, path, length, fh=None): raise FuseOSError(EROFS) - + def unlink(self, path): raise FuseOSError(EROFS) - + def utimens(self, path, times=None): """Times is a (atime, mtime) tuple. If None use current time.""" return 0 - + def write(self, path, data, offset, fh): raise FuseOSError(EROFS) class LoggingMixIn: + log = logging.getLogger('fuse.log-mixin') + def __call__(self, op, path, *args): - print '->', op, path, repr(args) + self.log.debug('-> %s %s %s', op, path, repr(args)) ret = '[Unhandled Exception]' try: ret = getattr(self, op)(path, *args) @@ -689,4 +829,4 @@ class LoggingMixIn: ret = str(e) raise finally: - print '<-', op, repr(ret) + self.log.debug('<- %s %s', op, repr(ret)) |
