diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..a8a82a4 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README* diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..49ce660 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,61 @@ +Metadata-Version: 1.1 +Name: fusepy +Version: 2.0.4 +Summary: Simple ctypes bindings for FUSE +Home-page: http://github.com/terencehonles/fusepy +Author: Terence Honles +Author-email: terence@honles.com +License: ISC +Description: fusepy + ====== + + ``fusepy`` is a Python module that provides a simple interface to FUSE_ and + MacFUSE_. It's just one file and is implemented using ctypes. + + The original version of ``fusepy`` was hosted on `Google Code`_, but is now + `officially hosted on GitHub`_. + + ``fusepy`` is written in 2x syntax, but trying to pay attention to bytes and + other changes 3x would care about. + + examples + -------- + See some examples of how you can use fusepy: + + :memory_: A simple memory filesystem + :loopback_: A loopback filesystem + :context_: Sample usage of fuse_get_context() + :sftp_: A simple SFTP filesystem (requires paramiko) + + To get started download_ fusepy or just browse the source_. + + fusepy requires FUSE 2.6 (or later) and runs on: + + - Linux (i386, x86_64, PPC, arm64, MIPS) + - Mac OS X (Intel, PowerPC) + - FreeBSD (i386, amd64) + + + .. _FUSE: http://fuse.sourceforge.net/ + .. _MacFUSE: http://code.google.com/p/macfuse/ + .. _`Google Code`: http://code.google.com/p/fusepy/ + + .. _officially hosted on GitHub: source_ + .. _download: https://github.com/terencehonles/fusepy/zipball/master + .. _source: http://github.com/terencehonles/fusepy + + .. examples + .. _memory: http://github.com/terencehonles/fusepy/blob/master/examples/memory.py + .. _loopback: http://github.com/terencehonles/fusepy/blob/master/examples/loopback.py + .. _context: http://github.com/terencehonles/fusepy/blob/master/examples/context.py + .. _sftp: http://github.com/terencehonles/fusepy/blob/master/examples/sftp.py + +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: ISC License (ISCL) +Classifier: Operating System :: MacOS +Classifier: Operating System :: POSIX +Classifier: Operating System :: Unix +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: System :: Filesystems diff --git a/README b/README new file mode 120000 index 0000000..92cacd2 --- /dev/null +++ b/README @@ -0,0 +1 @@ +README.rst \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..828da7e --- /dev/null +++ b/README.rst @@ -0,0 +1,43 @@ +fusepy +====== + +``fusepy`` is a Python module that provides a simple interface to FUSE_ and +MacFUSE_. It's just one file and is implemented using ctypes. + +The original version of ``fusepy`` was hosted on `Google Code`_, but is now +`officially hosted on GitHub`_. + +``fusepy`` is written in 2x syntax, but trying to pay attention to bytes and +other changes 3x would care about. + +examples +-------- +See some examples of how you can use fusepy: + +:memory_: A simple memory filesystem +:loopback_: A loopback filesystem +:context_: Sample usage of fuse_get_context() +:sftp_: A simple SFTP filesystem (requires paramiko) + +To get started download_ fusepy or just browse the source_. + +fusepy requires FUSE 2.6 (or later) and runs on: + +- Linux (i386, x86_64, PPC, arm64, MIPS) +- Mac OS X (Intel, PowerPC) +- FreeBSD (i386, amd64) + + +.. _FUSE: http://fuse.sourceforge.net/ +.. _MacFUSE: http://code.google.com/p/macfuse/ +.. _`Google Code`: http://code.google.com/p/fusepy/ + +.. _officially hosted on GitHub: source_ +.. _download: https://github.com/terencehonles/fusepy/zipball/master +.. _source: http://github.com/terencehonles/fusepy + +.. examples +.. _memory: http://github.com/terencehonles/fusepy/blob/master/examples/memory.py +.. _loopback: http://github.com/terencehonles/fusepy/blob/master/examples/loopback.py +.. _context: http://github.com/terencehonles/fusepy/blob/master/examples/context.py +.. _sftp: http://github.com/terencehonles/fusepy/blob/master/examples/sftp.py diff --git a/fuse.py b/fuse.py new file mode 100644 index 0000000..0500af8 --- /dev/null +++ b/fuse.py @@ -0,0 +1,978 @@ +# Copyright (c) 2012 Terence Honles (maintainer) +# Copyright (c) 2008 Giorgos Verigakis (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. + +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 == '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('%s is not supported.' % _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 '%s=%s' % (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' % (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: + fi.fh = self.operations('create', path, mode) + 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)) diff --git a/fusepy.egg-info/PKG-INFO b/fusepy.egg-info/PKG-INFO new file mode 100644 index 0000000..49ce660 --- /dev/null +++ b/fusepy.egg-info/PKG-INFO @@ -0,0 +1,61 @@ +Metadata-Version: 1.1 +Name: fusepy +Version: 2.0.4 +Summary: Simple ctypes bindings for FUSE +Home-page: http://github.com/terencehonles/fusepy +Author: Terence Honles +Author-email: terence@honles.com +License: ISC +Description: fusepy + ====== + + ``fusepy`` is a Python module that provides a simple interface to FUSE_ and + MacFUSE_. It's just one file and is implemented using ctypes. + + The original version of ``fusepy`` was hosted on `Google Code`_, but is now + `officially hosted on GitHub`_. + + ``fusepy`` is written in 2x syntax, but trying to pay attention to bytes and + other changes 3x would care about. + + examples + -------- + See some examples of how you can use fusepy: + + :memory_: A simple memory filesystem + :loopback_: A loopback filesystem + :context_: Sample usage of fuse_get_context() + :sftp_: A simple SFTP filesystem (requires paramiko) + + To get started download_ fusepy or just browse the source_. + + fusepy requires FUSE 2.6 (or later) and runs on: + + - Linux (i386, x86_64, PPC, arm64, MIPS) + - Mac OS X (Intel, PowerPC) + - FreeBSD (i386, amd64) + + + .. _FUSE: http://fuse.sourceforge.net/ + .. _MacFUSE: http://code.google.com/p/macfuse/ + .. _`Google Code`: http://code.google.com/p/fusepy/ + + .. _officially hosted on GitHub: source_ + .. _download: https://github.com/terencehonles/fusepy/zipball/master + .. _source: http://github.com/terencehonles/fusepy + + .. examples + .. _memory: http://github.com/terencehonles/fusepy/blob/master/examples/memory.py + .. _loopback: http://github.com/terencehonles/fusepy/blob/master/examples/loopback.py + .. _context: http://github.com/terencehonles/fusepy/blob/master/examples/context.py + .. _sftp: http://github.com/terencehonles/fusepy/blob/master/examples/sftp.py + +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: ISC License (ISCL) +Classifier: Operating System :: MacOS +Classifier: Operating System :: POSIX +Classifier: Operating System :: Unix +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: System :: Filesystems diff --git a/fusepy.egg-info/SOURCES.txt b/fusepy.egg-info/SOURCES.txt new file mode 100644 index 0000000..dbb951d --- /dev/null +++ b/fusepy.egg-info/SOURCES.txt @@ -0,0 +1,9 @@ +MANIFEST.in +README +README.rst +fuse.py +setup.py +fusepy.egg-info/PKG-INFO +fusepy.egg-info/SOURCES.txt +fusepy.egg-info/dependency_links.txt +fusepy.egg-info/top_level.txt \ No newline at end of file diff --git a/fusepy.egg-info/dependency_links.txt b/fusepy.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/fusepy.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/fusepy.egg-info/top_level.txt b/fusepy.egg-info/top_level.txt new file mode 100644 index 0000000..a517c48 --- /dev/null +++ b/fusepy.egg-info/top_level.txt @@ -0,0 +1 @@ +fuse diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..861a9f5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..23d1664 --- /dev/null +++ b/setup.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +from __future__ import with_statement + +from setuptools import setup + +with open('README') as readme: + documentation = readme.read() + +setup( + name = 'fusepy', + version = '2.0.4', + + description = 'Simple ctypes bindings for FUSE', + long_description = documentation, + author = 'Giorgos Verigakis', + author_email = 'verigak@gmail.com', + maintainer = 'Terence Honles', + maintainer_email = 'terence@honles.com', + license = 'ISC', + py_modules=['fuse'], + url = 'http://github.com/terencehonles/fusepy', + + classifiers = [ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: ISC License (ISCL)', + 'Operating System :: MacOS', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 3', + 'Topic :: System :: Filesystems', + ] +)