| # This is an embedded copy of fuse.py taken from the following upstream commit: |
| # |
| # https://github.com/terencehonles/fusepy/commit/0eafeb557e0e70926ed9450008ef17057d302391 |
| # |
| # Our local modifications are recorded in the Git history of this repo. |
| |
| # Copyright (c) 2012 Terence Honles <terence@honles.com> (maintainer) |
| # Copyright (c) 2008 Giorgos Verigakis <verigak@gmail.com> (author) |
| # |
| # Permission to use, copy, modify, and distribute this software for any |
| # purpose with or without fee is hereby granted, provided that the above |
| # copyright notice and this permission notice appear in all copies. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| |
| # pylint: skip-file |
| |
| from __future__ import print_function, absolute_import, division |
| |
| from ctypes import * |
| from ctypes.util import find_library |
| from errno import * |
| 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 |
| |
| 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 |
| |
| try: |
| basestring |
| except NameError: |
| 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': |
| _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') |
| else: |
| _libfuse = CDLL(_libfuse_path) |
| |
| if _system == 'Darwin' and hasattr(_libfuse, 'macfuse_version'): |
| _system = 'Darwin-MacFuse' |
| |
| |
| if _system in ('Darwin', 'Darwin-MacFuse', 'FreeBSD'): |
| ENOTSUP = 45 |
| c_dev_t = c_int32 |
| c_fsblkcnt_t = c_ulong |
| c_fsfilcnt_t = c_ulong |
| c_gid_t = c_uint32 |
| c_mode_t = c_uint16 |
| c_off_t = c_int64 |
| c_pid_t = c_int32 |
| c_uid_t = c_uint32 |
| setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), |
| c_size_t, c_int, c_uint32) |
| getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), |
| c_size_t, c_uint32) |
| if _system == 'Darwin': |
| c_stat._fields_ = [ |
| ('st_dev', c_dev_t), |
| ('st_mode', c_mode_t), |
| ('st_nlink', c_uint16), |
| ('st_ino', c_uint64), |
| ('st_uid', c_uid_t), |
| ('st_gid', c_gid_t), |
| ('st_rdev', c_dev_t), |
| ('st_atimespec', c_timespec), |
| ('st_mtimespec', c_timespec), |
| ('st_ctimespec', c_timespec), |
| ('st_birthtimespec', c_timespec), |
| ('st_size', c_off_t), |
| ('st_blocks', c_int64), |
| ('st_blksize', c_int32), |
| ('st_flags', c_int32), |
| ('st_gen', c_int32), |
| ('st_lspare', c_int32), |
| ('st_qspare', c_int64)] |
| else: |
| c_stat._fields_ = [ |
| ('st_dev', c_dev_t), |
| ('st_ino', c_uint32), |
| ('st_mode', c_mode_t), |
| ('st_nlink', c_uint16), |
| ('st_uid', c_uid_t), |
| ('st_gid', c_gid_t), |
| ('st_rdev', c_dev_t), |
| ('st_atimespec', c_timespec), |
| ('st_mtimespec', c_timespec), |
| ('st_ctimespec', c_timespec), |
| ('st_size', c_off_t), |
| ('st_blocks', c_int64), |
| ('st_blksize', c_int32)] |
| elif _system == 'Linux': |
| ENOTSUP = 95 |
| c_dev_t = c_ulonglong |
| c_fsblkcnt_t = c_ulonglong |
| c_fsfilcnt_t = c_ulonglong |
| c_gid_t = c_uint |
| c_mode_t = c_uint |
| 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) |
| |
| if _machine == 'x86_64': |
| c_stat._fields_ = [ |
| ('st_dev', c_dev_t), |
| ('st_ino', c_ulong), |
| ('st_nlink', c_ulong), |
| ('st_mode', c_mode_t), |
| ('st_uid', c_uid_t), |
| ('st_gid', c_gid_t), |
| ('__pad0', c_int), |
| ('st_rdev', c_dev_t), |
| ('st_size', c_off_t), |
| ('st_blksize', c_long), |
| ('st_blocks', c_long), |
| ('st_atimespec', c_timespec), |
| ('st_mtimespec', c_timespec), |
| ('st_ctimespec', c_timespec)] |
| elif _machine == 'mips': |
| c_stat._fields_ = [ |
| ('st_dev', c_dev_t), |
| ('__pad1_1', c_ulong), |
| ('__pad1_2', c_ulong), |
| ('__pad1_3', c_ulong), |
| ('st_ino', c_ulong), |
| ('st_mode', c_mode_t), |
| ('st_nlink', c_ulong), |
| ('st_uid', c_uid_t), |
| ('st_gid', c_gid_t), |
| ('st_rdev', c_dev_t), |
| ('__pad2_1', c_ulong), |
| ('__pad2_2', c_ulong), |
| ('st_size', c_off_t), |
| ('__pad3', c_ulong), |
| ('st_atimespec', c_timespec), |
| ('__pad4', c_ulong), |
| ('st_mtimespec', c_timespec), |
| ('__pad5', c_ulong), |
| ('st_ctimespec', c_timespec), |
| ('__pad6', c_ulong), |
| ('st_blksize', c_long), |
| ('st_blocks', c_long), |
| ('__pad7_1', c_ulong), |
| ('__pad7_2', c_ulong), |
| ('__pad7_3', c_ulong), |
| ('__pad7_4', c_ulong), |
| ('__pad7_5', c_ulong), |
| ('__pad7_6', c_ulong), |
| ('__pad7_7', c_ulong), |
| ('__pad7_8', c_ulong), |
| ('__pad7_9', c_ulong), |
| ('__pad7_10', c_ulong), |
| ('__pad7_11', c_ulong), |
| ('__pad7_12', c_ulong), |
| ('__pad7_13', c_ulong), |
| ('__pad7_14', c_ulong)] |
| elif _machine == 'ppc': |
| c_stat._fields_ = [ |
| ('st_dev', c_dev_t), |
| ('st_ino', c_ulonglong), |
| ('st_mode', c_mode_t), |
| ('st_nlink', c_uint), |
| ('st_uid', c_uid_t), |
| ('st_gid', c_gid_t), |
| ('st_rdev', c_dev_t), |
| ('__pad2', c_ushort), |
| ('st_size', c_off_t), |
| ('st_blksize', c_long), |
| ('st_blocks', c_longlong), |
| ('st_atimespec', c_timespec), |
| ('st_mtimespec', c_timespec), |
| ('st_ctimespec', c_timespec)] |
| elif _machine == 'ppc64' or _machine == 'ppc64le': |
| c_stat._fields_ = [ |
| ('st_dev', c_dev_t), |
| ('st_ino', c_ulong), |
| ('st_nlink', c_ulong), |
| ('st_mode', c_mode_t), |
| ('st_uid', c_uid_t), |
| ('st_gid', c_gid_t), |
| ('__pad', c_uint), |
| ('st_rdev', c_dev_t), |
| ('st_size', c_off_t), |
| ('st_blksize', c_long), |
| ('st_blocks', c_long), |
| ('st_atimespec', c_timespec), |
| ('st_mtimespec', c_timespec), |
| ('st_ctimespec', c_timespec)] |
| elif _machine == 'aarch64': |
| c_stat._fields_ = [ |
| ('st_dev', c_dev_t), |
| ('st_ino', c_ulong), |
| ('st_mode', c_mode_t), |
| ('st_nlink', c_uint), |
| ('st_uid', c_uid_t), |
| ('st_gid', c_gid_t), |
| ('st_rdev', c_dev_t), |
| ('__pad1', c_ulong), |
| ('st_size', c_off_t), |
| ('st_blksize', c_int), |
| ('__pad2', c_int), |
| ('st_blocks', c_long), |
| ('st_atimespec', c_timespec), |
| ('st_mtimespec', c_timespec), |
| ('st_ctimespec', c_timespec)] |
| else: |
| # i686, use as fallback for everything else |
| c_stat._fields_ = [ |
| ('st_dev', c_dev_t), |
| ('__pad1', c_ushort), |
| ('__st_ino', c_ulong), |
| ('st_mode', c_mode_t), |
| ('st_nlink', c_uint), |
| ('st_uid', c_uid_t), |
| ('st_gid', c_gid_t), |
| ('st_rdev', c_dev_t), |
| ('__pad2', c_ushort), |
| ('st_size', c_off_t), |
| ('st_blksize', c_long), |
| ('st_blocks', c_longlong), |
| ('st_atimespec', c_timespec), |
| ('st_mtimespec', c_timespec), |
| ('st_ctimespec', c_timespec), |
| ('st_ino', c_ulonglong)] |
| else: |
| raise NotImplementedError('{} is not supported.'.format(_system)) |
| |
| |
| class c_statvfs(Structure): |
| _fields_ = [ |
| ('f_bsize', c_ulong), |
| ('f_frsize', c_ulong), |
| ('f_blocks', c_fsblkcnt_t), |
| ('f_bfree', c_fsblkcnt_t), |
| ('f_bavail', c_fsblkcnt_t), |
| ('f_files', c_fsfilcnt_t), |
| ('f_ffree', c_fsfilcnt_t), |
| ('f_favail', c_fsfilcnt_t), |
| ('f_fsid', c_ulong), |
| #('unused', c_int), |
| ('f_flag', c_ulong), |
| ('f_namemax', c_ulong)] |
| |
| 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) |
| |
| class c_statvfs(Structure): |
| _fields_ = [ |
| ('f_bavail', c_fsblkcnt_t), |
| ('f_bfree', c_fsblkcnt_t), |
| ('f_blocks', c_fsblkcnt_t), |
| ('f_favail', c_fsfilcnt_t), |
| ('f_ffree', c_fsfilcnt_t), |
| ('f_files', c_fsfilcnt_t), |
| ('f_bsize', c_ulong), |
| ('f_flag', c_ulong), |
| ('f_frsize', c_ulong)] |
| |
| class fuse_file_info(Structure): |
| _fields_ = [ |
| ('flags', c_int), |
| ('fh_old', c_ulong), |
| ('writepage', c_int), |
| ('direct_io', c_uint, 1), |
| ('keep_cache', c_uint, 1), |
| ('flush', c_uint, 1), |
| ('padding', c_uint, 29), |
| ('fh', c_uint64), |
| ('lock_owner', c_uint64)] |
| |
| class fuse_context(Structure): |
| _fields_ = [ |
| ('fuse', c_voidp), |
| ('uid', c_uid_t), |
| ('gid', c_gid_t), |
| ('pid', c_pid_t), |
| ('private_data', c_voidp)] |
| |
| _libfuse.fuse_get_context.restype = POINTER(fuse_context) |
| |
| |
| class fuse_operations(Structure): |
| _fields_ = [ |
| ('getattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat))), |
| ('readlink', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)), |
| ('getdir', c_voidp), # Deprecated, use readdir |
| ('mknod', CFUNCTYPE(c_int, c_char_p, c_mode_t, c_dev_t)), |
| ('mkdir', CFUNCTYPE(c_int, c_char_p, c_mode_t)), |
| ('unlink', CFUNCTYPE(c_int, c_char_p)), |
| ('rmdir', CFUNCTYPE(c_int, c_char_p)), |
| ('symlink', CFUNCTYPE(c_int, c_char_p, c_char_p)), |
| ('rename', CFUNCTYPE(c_int, c_char_p, c_char_p)), |
| ('link', CFUNCTYPE(c_int, c_char_p, c_char_p)), |
| ('chmod', CFUNCTYPE(c_int, c_char_p, c_mode_t)), |
| ('chown', CFUNCTYPE(c_int, c_char_p, c_uid_t, c_gid_t)), |
| ('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))), |
| |
| ('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))), |
| ('fsync', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))), |
| ('setxattr', setxattr_t), |
| ('getxattr', getxattr_t), |
| ('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))), |
| |
| ('releasedir', CFUNCTYPE(c_int, c_char_p, 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))), |
| |
| ('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)), |
| |
| ('utimens', CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf))), |
| ('bmap', CFUNCTYPE(c_int, c_char_p, c_size_t, POINTER(c_ulonglong))), |
| ('flag_nullpath_ok', c_uint, 1), |
| ('flag_nopath', c_uint, 1), |
| ('flag_utime_omit_ok', c_uint, 1), |
| ('flag_reserved', c_uint, 29), |
| ] |
| |
| |
| def time_of_timespec(ts): |
| return ts.tv_sec + ts.tv_nsec / 10 ** 9 |
| |
| def set_st_attrs(st, attrs): |
| for key, val in attrs.items(): |
| if key in ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime'): |
| timespec = getattr(st, key + 'spec', None) |
| if timespec is None: |
| continue |
| timespec.tv_sec = int(val) |
| timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9) |
| elif hasattr(st, key): |
| setattr(st, key, val) |
| |
| |
| def fuse_get_context(): |
| 'Returns a (uid, gid, pid) tuple' |
| |
| ctxp = _libfuse.fuse_get_context() |
| ctx = ctxp.contents |
| return ctx.uid, ctx.gid, ctx.pid |
| |
| |
| class FuseOSError(OSError): |
| def __init__(self, errno): |
| super(FuseOSError, self).__init__(errno, strerror(errno)) |
| |
| |
| 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. |
| ''' |
| |
| OPTIONS = ( |
| ('foreground', '-f'), |
| ('debug', '-d'), |
| ('nothreads', '-s'), |
| ) |
| |
| 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'] |
| |
| args.extend(flag for arg, flag in self.OPTIONS |
| if kwargs.pop(arg, False)) |
| |
| kwargs.setdefault('fsname', operations.__class__.__name__) |
| args.append('-o') |
| 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 ent in fuse_operations._fields_: |
| name, prototype = ent[:2] |
| |
| val = getattr(operations, name, None) |
| if val is None: |
| continue |
| |
| # Function pointer members are tested for using the |
| # getattr(operations, name) above but are dynamically |
| # invoked using self.operations(name) |
| if hasattr(prototype, 'argtypes'): |
| val = prototype(partial(self._wrapper, getattr(self, name))) |
| |
| setattr(fuse_ops, name, val) |
| |
| try: |
| old_handler = signal(SIGINT, SIG_DFL) |
| except ValueError: |
| old_handler = SIG_DFL |
| |
| err = _libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops), |
| sizeof(fuse_ops), None) |
| |
| try: |
| signal(SIGINT, old_handler) |
| except ValueError: |
| pass |
| |
| del self.operations # Invoke the destructor |
| if err: |
| raise RuntimeError(err) |
| |
| @staticmethod |
| def _normalize_fuse_options(**kargs): |
| for key, value in kargs.items(): |
| if isinstance(value, bool): |
| if value is True: yield key |
| else: |
| yield '{}={}'.format(key, value) |
| |
| @staticmethod |
| def _wrapper(func, *args, **kwargs): |
| 'Decorator for the methods that follow' |
| |
| try: |
| return func(*args, **kwargs) or 0 |
| except OSError as e: |
| return -(e.errno or EFAULT) |
| except: |
| print_exc() |
| return -EFAULT |
| |
| def _decode_optional_path(self, path): |
| # NB: this method is intended for fuse operations that |
| # allow the path argument to be NULL, |
| # *not* as a generic path decoding method |
| if path is None: |
| return None |
| return path.decode(self.encoding) |
| |
| def getattr(self, path, buf): |
| return self.fgetattr(path, buf, None) |
| |
| def readlink(self, path, buf, bufsize): |
| ret = self.operations('readlink', path.decode(self.encoding)) \ |
| .encode(self.encoding) |
| |
| # copies a string into the given buffer |
| # (null terminated and truncated if necessary) |
| 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.decode(self.encoding), mode, dev) |
| |
| def mkdir(self, path, mode): |
| return self.operations('mkdir', path.decode(self.encoding), mode) |
| |
| def unlink(self, path): |
| return self.operations('unlink', path.decode(self.encoding)) |
| |
| def rmdir(self, path): |
| return self.operations('rmdir', path.decode(self.encoding)) |
| |
| def symlink(self, source, target): |
| 'creates a symlink `target -> source` (e.g. ln -s source target)' |
| |
| return self.operations('symlink', target.decode(self.encoding), |
| source.decode(self.encoding)) |
| |
| def rename(self, old, new): |
| return self.operations('rename', old.decode(self.encoding), |
| new.decode(self.encoding)) |
| |
| def link(self, source, target): |
| 'creates a hard link `target -> source` (e.g. ln source target)' |
| |
| return self.operations('link', target.decode(self.encoding), |
| source.decode(self.encoding)) |
| |
| def chmod(self, 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.decode(self.encoding), uid, gid) |
| |
| def truncate(self, 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.decode(self.encoding), fi) |
| else: |
| fi.fh = self.operations('open', path.decode(self.encoding), |
| fi.flags) |
| |
| return 0 |
| |
| def read(self, path, buf, size, offset, fip): |
| if self.raw_fi: |
| fh = fip.contents |
| else: |
| fh = fip.contents.fh |
| |
| ret = self.operations('read', self._decode_optional_path(path), size, |
| offset, fh) |
| |
| if not ret: return 0 |
| |
| retsize = len(ret) |
| assert retsize <= size, \ |
| 'actual amount read {:d} greater than expected {:d}'.format(retsize, size) |
| |
| data = create_string_buffer(ret, retsize) |
| memmove(buf, data, retsize) |
| return retsize |
| |
| def write(self, path, buf, size, offset, fip): |
| data = string_at(buf, size) |
| |
| if self.raw_fi: |
| fh = fip.contents |
| else: |
| fh = fip.contents.fh |
| |
| return self.operations('write', self._decode_optional_path(path), data, |
| offset, fh) |
| |
| def statfs(self, path, buf): |
| stv = buf.contents |
| 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): |
| if self.raw_fi: |
| fh = fip.contents |
| else: |
| fh = fip.contents.fh |
| |
| return self.operations('flush', self._decode_optional_path(path), fh) |
| |
| def release(self, path, fip): |
| if self.raw_fi: |
| fh = fip.contents |
| else: |
| fh = fip.contents.fh |
| |
| return self.operations('release', self._decode_optional_path(path), fh) |
| |
| def fsync(self, path, datasync, fip): |
| if self.raw_fi: |
| fh = fip.contents |
| else: |
| fh = fip.contents.fh |
| |
| return self.operations('fsync', self._decode_optional_path(path), datasync, |
| fh) |
| |
| def setxattr(self, path, name, value, size, options, *args): |
| return self.operations('setxattr', path.decode(self.encoding), |
| name.decode(self.encoding), |
| string_at(value, size), options, *args) |
| |
| def getxattr(self, path, name, value, size, *args): |
| ret = self.operations('getxattr', path.decode(self.encoding), |
| name.decode(self.encoding), *args) |
| |
| retsize = len(ret) |
| # allow size queries |
| if not value: return retsize |
| |
| # do not truncate |
| if retsize > size: return -ERANGE |
| |
| buf = create_string_buffer(ret, retsize) # Does not add trailing 0 |
| memmove(value, buf, retsize) |
| |
| return retsize |
| |
| def listxattr(self, path, namebuf, size): |
| attrs = self.operations('listxattr', path.decode(self.encoding)) or '' |
| ret = '\x00'.join(attrs).encode(self.encoding) |
| if len(ret) > 0: |
| ret += '\x00'.encode(self.encoding) |
| |
| retsize = len(ret) |
| # allow size queries |
| if not namebuf: return retsize |
| |
| # do not truncate |
| if retsize > size: return -ERANGE |
| |
| buf = create_string_buffer(ret, retsize) |
| memmove(namebuf, buf, retsize) |
| |
| return retsize |
| |
| def removexattr(self, 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.decode(self.encoding)) |
| |
| return 0 |
| |
| def readdir(self, path, buf, filler, offset, fip): |
| # Ignore raw_fi |
| for item in self.operations('readdir', self._decode_optional_path(path), |
| fip.contents.fh): |
| |
| if isinstance(item, basestring): |
| name, st, offset = item, None, 0 |
| else: |
| name, attrs, offset = item |
| if attrs: |
| st = c_stat() |
| set_st_attrs(st, attrs) |
| else: |
| st = None |
| |
| 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', self._decode_optional_path(path), |
| fip.contents.fh) |
| |
| def fsyncdir(self, path, datasync, fip): |
| # Ignore raw_fi |
| return self.operations('fsyncdir', self._decode_optional_path(path), |
| 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.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: |
| # This line is different from upstream to fix issues |
| # reading file opened with O_CREAT|O_RDWR. |
| # See issue #143. |
| fi.fh = self.operations('create', path, mode, fi.flags) |
| # END OF MODIFICATION |
| return 0 |
| |
| def ftruncate(self, path, length, fip): |
| if self.raw_fi: |
| fh = fip.contents |
| else: |
| fh = fip.contents.fh |
| |
| return self.operations('truncate', self._decode_optional_path(path), |
| length, fh) |
| |
| def fgetattr(self, path, buf, fip): |
| memset(buf, 0, sizeof(c_stat)) |
| |
| st = buf.contents |
| if not fip: |
| fh = fip |
| elif self.raw_fi: |
| fh = fip.contents |
| else: |
| fh = fip.contents.fh |
| |
| attrs = self.operations('getattr', self._decode_optional_path(path), fh) |
| set_st_attrs(st, attrs) |
| return 0 |
| |
| def lock(self, path, fip, cmd, lock): |
| if self.raw_fi: |
| fh = fip.contents |
| else: |
| fh = fip.contents.fh |
| |
| return self.operations('lock', self._decode_optional_path(path), fh, cmd, |
| lock) |
| |
| def utimens(self, path, buf): |
| if buf: |
| atime = time_of_timespec(buf.contents.actime) |
| mtime = time_of_timespec(buf.contents.modtime) |
| times = (atime, mtime) |
| else: |
| times = None |
| |
| return self.operations('utimens', path.decode(self.encoding), times) |
| |
| def bmap(self, 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. |
| ''' |
| |
| 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. |
| ''' |
| |
| if path != '/': |
| raise FuseOSError(ENOENT) |
| return dict(st_mode=(S_IFDIR | 0o755), 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. |
| ''' |
| |
| pass |
| |
| def link(self, target, source): |
| 'creates a hard link `target -> source` (e.g. ln source target)' |
| |
| 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 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. |
| ''' |
| |
| 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). |
| ''' |
| |
| return {} |
| |
| def symlink(self, target, source): |
| 'creates a symlink `target -> source` (e.g. ln -s source target)' |
| |
| 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): |
| self.log.debug('-> %s %s %s', op, path, repr(args)) |
| ret = '[Unhandled Exception]' |
| try: |
| ret = getattr(self, op)(path, *args) |
| return ret |
| except OSError as e: |
| ret = str(e) |
| raise |
| finally: |
| self.log.debug('<- %s %s', op, repr(ret)) |