adds fusepy library

This commit is contained in:
Brett Slatkin 2011-03-22 17:27:27 -07:00
parent 43d058ae77
commit 87be928fa9
21 changed files with 4811 additions and 0 deletions

View File

@ -0,0 +1,8 @@
import sys
pyver = sys.version_info[0:2]
if pyver <= (2, 4):
from fuse24 import *
elif pyver >= (3, 0):
from fuse3 import *
else:
from fuse import *

61
lib/python/fusepy/context.py Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
from errno import ENOENT
from stat import S_IFDIR, S_IFREG
from sys import argv, exit
from time import time
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn, fuse_get_context
class Context(LoggingMixIn, Operations):
"""Example filesystem to demonstrate fuse_get_context()"""
def getattr(self, path, fh=None):
uid, gid, pid = fuse_get_context()
if path == '/':
st = dict(st_mode=(S_IFDIR | 0755), st_nlink=2)
elif path == '/uid':
size = len('%s\n' % uid)
st = dict(st_mode=(S_IFREG | 0444), st_size=size)
elif path == '/gid':
size = len('%s\n' % gid)
st = dict(st_mode=(S_IFREG | 0444), st_size=size)
elif path == '/pid':
size = len('%s\n' % pid)
st = dict(st_mode=(S_IFREG | 0444), st_size=size)
else:
raise FuseOSError(ENOENT)
st['st_ctime'] = st['st_mtime'] = st['st_atime'] = time()
return st
def read(self, path, size, offset, fh):
uid, gid, pid = fuse_get_context()
if path == '/uid':
return '%s\n' % uid
elif path == '/gid':
return '%s\n' % gid
elif path == '/pid':
return '%s\n' % pid
return ''
def readdir(self, path, fh):
return ['.', '..', 'uid', 'gid', 'pid']
# Disable unused operations:
access = None
flush = None
getxattr = None
listxattr = None
open = None
opendir = None
release = None
releasedir = None
statfs = None
if __name__ == "__main__":
if len(argv) != 2:
print 'usage: %s <mountpoint>' % argv[0]
exit(1)
fuse = FUSE(Context(), argv[1], foreground=True)

650
lib/python/fusepy/fuse.py Normal file
View File

@ -0,0 +1,650 @@
# Copyright (c) 2008 Giorgos Verigakis <verigak@gmail.com>
#
# 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 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 stat import S_IFDIR
from traceback import print_exc
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()
if _system in ('Darwin', 'FreeBSD'):
_libiconv = CDLL(find_library("iconv"), RTLD_GLOBAL) # libfuse dependency
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)
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)
_machine = machine()
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 == '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)]
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)]
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)]
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)))]
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'):
timespec = getattr(st, key + 'spec')
timespec.tv_sec = int(val)
timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9)
elif hasattr(st, key):
setattr(st, key, val)
_libfuse_path = find_library('fuse')
if not _libfuse_path:
raise EnvironmentError('Unable to find libfuse')
_libfuse = CDLL(_libfuse_path)
_libfuse.fuse_get_context.restype = POINTER(fuse_context)
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."""
def __init__(self, operations, mountpoint, raw_fi=False, **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
args = ['fuse']
if kwargs.pop('foreground', False):
args.append('-f')
if kwargs.pop('debug', False):
args.append('-d')
if kwargs.pop('nothreads', False):
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(mountpoint)
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))
setattr(fuse_ops, name, prototype(op))
err = _libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops),
sizeof(fuse_ops), None)
del self.operations # Invoke the destructor
if err:
raise RuntimeError(err)
def _wrapper_(self, func, *args, **kwargs):
"""Decorator for the methods that follow"""
try:
return func(*args, **kwargs) or 0
except OSError, e:
return -(e.errno or EFAULT)
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)
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)
def mkdir(self, path, mode):
return self.operations('mkdir', path, mode)
def unlink(self, path):
return self.operations('unlink', path)
def rmdir(self, path):
return self.operations('rmdir', path)
def symlink(self, source, target):
return self.operations('symlink', target, source)
def rename(self, old, new):
return self.operations('rename', old, new)
def link(self, source, target):
return self.operations('link', target, source)
def chmod(self, path, mode):
return self.operations('chmod', path, 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)
def truncate(self, path, length):
return self.operations('truncate', path, length)
def open(self, path, fip):
fi = fip.contents
if self.raw_fi:
return self.operations('open', path, fi)
else:
fi.fh = self.operations('open', path, 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
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)
def statfs(self, path, buf):
stv = buf.contents
attrs = self.operations('statfs', path)
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)
def release(self, path, fip):
fh = fip.contents if self.raw_fi else fip.contents.fh
return self.operations('release', path, 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)
def setxattr(self, path, name, value, size, options, *args):
data = string_at(value, size)
return self.operations('setxattr', path, name, data, options, *args)
def getxattr(self, path, name, value, size, *args):
ret = self.operations('getxattr', path, name, *args)
retsize = len(ret)
buf = create_string_buffer(ret, retsize) # Does not add trailing 0
if bool(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 ''
bufsize = len(buf)
if bool(namebuf):
if bufsize > size:
return -ERANGE
memmove(namebuf, buf, bufsize)
return bufsize
def removexattr(self, path, name):
return self.operations('removexattr', path, name)
def opendir(self, path, fip):
# Ignore raw_fi
fip.contents.fh = self.operations('opendir', path)
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):
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, st, offset) != 0:
break
return 0
def releasedir(self, path, fip):
# Ignore raw_fi
return self.operations('releasedir', path, fip.contents.fh)
def fsyncdir(self, path, datasync, fip):
# Ignore raw_fi
return self.operations('fsyncdir', 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, amode)
def create(self, path, mode, fip):
fi = fip.contents
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)
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)
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)
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, times)
def bmap(self, path, blocksize, idx):
return self.operations('bmap', path, 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 | 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."""
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 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):
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:
def __call__(self, op, path, *args):
print '->', op, path, repr(args)
ret = '[Unhandled Exception]'
try:
ret = getattr(self, op)(path, *args)
return ret
except OSError, e:
ret = str(e)
raise
finally:
print '<-', op, repr(ret)

669
lib/python/fusepy/fuse24.py Normal file
View File

@ -0,0 +1,669 @@
# Copyright (c) 2008 Giorgos Verigakis <verigak@gmail.com>
#
# 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 division
from ctypes import *
from ctypes.util import find_library
from errno import *
from platform import machine, system
from stat import S_IFDIR
from traceback import print_exc
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()
if _system in ('Darwin', 'FreeBSD'):
_libiconv = CDLL(find_library("iconv"), RTLD_GLOBAL) # libfuse dependency
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)
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)
_machine = machine()
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 == '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)]
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)]
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)]
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)))]
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'):
timespec = getattr(st, key + 'spec')
timespec.tv_sec = int(val)
timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9)
elif hasattr(st, key):
setattr(st, key, val)
_libfuse_path = find_library('fuse')
if not _libfuse_path:
raise EnvironmentError('Unable to find libfuse')
_libfuse = CDLL(_libfuse_path)
_libfuse.fuse_get_context.restype = POINTER(fuse_context)
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 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):
"""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
args = ['fuse']
if kwargs.pop('foreground', False):
args.append('-f')
if kwargs.pop('debug', False):
args.append('-d')
if kwargs.pop('nothreads', False):
args.append('-s')
kwargs.setdefault('fsname', operations.__class__.__name__)
args.append('-o')
args.append(','.join(val is True and key or '%s=%s' % (key, val)
for key, val in kwargs.items()))
args.append(mountpoint)
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 = self._create_wrapper_(getattr(self, name))
setattr(fuse_ops, name, prototype(op))
_libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops),
sizeof(fuse_ops), None)
del self.operations # Invoke the destructor
@staticmethod
def _create_wrapper_(func):
def _wrapper_(*args, **kwargs):
"""Decorator for the methods that follow"""
try:
return func(*args, **kwargs) or 0
except OSError, e:
return -(e.errno or EFAULT)
except:
print_exc()
return -EFAULT
return _wrapper_
def getattr(self, path, buf):
return self.fgetattr(path, buf, None)
def readlink(self, path, buf, bufsize):
ret = self.operations('readlink', path)
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)
def mkdir(self, path, mode):
return self.operations('mkdir', path, mode)
def unlink(self, path):
return self.operations('unlink', path)
def rmdir(self, path):
return self.operations('rmdir', path)
def symlink(self, source, target):
return self.operations('symlink', target, source)
def rename(self, old, new):
return self.operations('rename', old, new)
def link(self, source, target):
return self.operations('link', target, source)
def chmod(self, path, mode):
return self.operations('chmod', path, mode)
def chown(self, path, uid, gid):
return self.operations('chown', path, uid, gid)
def truncate(self, path, length):
return self.operations('truncate', path, length)
def open(self, path, fip):
fi = fip.contents
if self.raw_fi:
return self.operations('open', path, fi)
else:
fi.fh = self.operations('open', path, 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', path, 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)
if self.raw_fi:
fh = fip.contents
else:
fh = fip.contents.fh
return self.operations('write', path, data, offset, fh)
def statfs(self, path, buf):
stv = buf.contents
attrs = self.operations('statfs', path)
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', path, fh)
def release(self, path, fip):
if self.raw_fi:
fh = fip.contents
else:
fh = fip.contents.fh
return self.operations('release', path, fh)
def fsync(self, path, datasync, fip):
if self.raw_fi:
fh = fip.contents
else:
fh = fip.contents.fh
return self.operations('fsync', path, 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)
def getxattr(self, path, name, value, size, *args):
ret = self.operations('getxattr', path, name, *args)
retsize = len(ret)
buf = create_string_buffer(ret, retsize) # Does not add trailing 0
if bool(value):
if retsize > size:
return -ERANGE
memmove(value, buf, retsize)
return retsize
def listxattr(self, path, namebuf, size):
ret = self.operations('listxattr', path)
if ret:
buf = create_string_buffer('\x00'.join(ret))
else:
buf = ''
bufsize = len(buf)
if bool(namebuf):
if bufsize > size:
return -ERANGE
memmove(namebuf, buf, bufsize)
return bufsize
def removexattr(self, path, name):
return self.operations('removexattr', path, name)
def opendir(self, path, fip):
# Ignore raw_fi
fip.contents.fh = self.operations('opendir', path)
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):
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, st, offset) != 0:
break
return 0
def releasedir(self, path, fip):
# Ignore raw_fi
return self.operations('releasedir', path, fip.contents.fh)
def fsyncdir(self, path, datasync, fip):
# Ignore raw_fi
return self.operations('fsyncdir', 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, amode)
def create(self, path, mode, fip):
fi = fip.contents
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', 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', 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', 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, times)
def bmap(self, path, blocksize, idx):
return self.operations('bmap', path, blocksize, idx)
class Operations(object):
"""This class should be subclassed and passed as an argument to FUSE on
initialization. All operations should raise an OSError 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 OSError(EFAULT, '')
return getattr(self, op)(*args)
def access(self, path, amode):
return 0
bmap = None
def chmod(self, path, mode):
raise OSError(EROFS, '')
def chown(self, path, uid, gid):
raise OSError(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 OSError(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 OSError(ENOENT, '')
return dict(st_mode=(S_IFDIR | 0755), st_nlink=2)
def getxattr(self, path, name, position=0):
raise OSError(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):
raise OSError(EROFS, '')
def listxattr(self, path):
return []
lock = None
def mkdir(self, path, mode):
raise OSError(EROFS, '')
def mknod(self, path, mode, dev):
raise OSError(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 OSError(ENOENT, '')
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 OSError(ENOENT, '')
def release(self, path, fh):
return 0
def releasedir(self, path, fh):
return 0
def removexattr(self, path, name):
raise OSError(ENOTSUP, '')
def rename(self, old, new):
raise OSError(EROFS, '')
def rmdir(self, path):
raise OSError(EROFS, '')
def setxattr(self, path, name, value, options, position=0):
raise OSError(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):
raise OSError(EROFS, '')
def truncate(self, path, length, fh=None):
raise OSError(EROFS, '')
def unlink(self, path):
raise OSError(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 OSError(EROFS, '')
class LoggingMixIn:
def __call__(self, op, path, *args):
print '->', op, path, repr(args)
ret = '[Unknown Error]'
try:
try:
ret = getattr(self, op)(path, *args)
return ret
except OSError, e:
ret = str(e)
raise
finally:
print '<-', op, repr(ret)

637
lib/python/fusepy/fuse3.py Normal file
View File

@ -0,0 +1,637 @@
# Copyright (c) 2008 Giorgos Verigakis <verigak@gmail.com>
#
# 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 ctypes import *
from ctypes.util import find_library
from errno import *
from functools import partial
from platform import machine, system
from stat import S_IFDIR
from traceback import print_exc
import logging
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()
if _system in ('Darwin', 'FreeBSD'):
_libiconv = CDLL(find_library("iconv"), RTLD_GLOBAL) # libfuse dependency
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)
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)
_machine = machine()
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 == '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)]
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)]
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)]
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)))]
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'):
timespec = getattr(st, key + 'spec')
timespec.tv_sec = int(val)
timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9)
elif hasattr(st, key):
setattr(st, key, val)
_libfuse_path = find_library('fuse')
if not _libfuse_path:
raise EnvironmentError('Unable to find libfuse')
_libfuse = CDLL(_libfuse_path)
_libfuse.fuse_get_context.restype = POINTER(fuse_context)
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 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):
"""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
args = ['fuse']
if kwargs.pop('foreground', False):
args.append('-f')
if kwargs.pop('debug', False):
args.append('-d')
if kwargs.pop('nothreads', False):
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(mountpoint)
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))
setattr(fuse_ops, name, prototype(op))
_libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops),
sizeof(fuse_ops), None)
del self.operations # Invoke the destructor
def _wrapper_(self, 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 getattr(self, path, buf):
return self.fgetattr(path, buf, None)
def readlink(self, path, buf, bufsize):
ret = self.operations('readlink', path).encode('utf-8')
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)
def mkdir(self, path, mode):
return self.operations('mkdir', path, mode)
def unlink(self, path):
return self.operations('unlink', path)
def rmdir(self, path):
return self.operations('rmdir', path)
def symlink(self, source, target):
return self.operations('symlink', target, source)
def rename(self, old, new):
return self.operations('rename', old, new)
def link(self, source, target):
return self.operations('link', target, source)
def chmod(self, path, mode):
return self.operations('chmod', path, mode)
def chown(self, path, uid, gid):
return self.operations('chown', path, uid, gid)
def truncate(self, path, length):
return self.operations('truncate', path, length)
def open(self, path, fip):
fi = fip.contents
if self.raw_fi:
return self.operations('open', path, fi)
else:
fi.fh = self.operations('open', path, 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
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)
def statfs(self, path, buf):
stv = buf.contents
attrs = self.operations('statfs', path)
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)
def release(self, path, fip):
fh = fip.contents if self.raw_fi else fip.contents.fh
return self.operations('release', path, 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)
def setxattr(self, path, name, value, size, options, *args):
data = string_at(value, size)
return self.operations('setxattr', path, name, data, options, *args)
def getxattr(self, path, name, value, size, *args):
ret = self.operations('getxattr', path, name, *args)
retsize = len(ret)
buf = create_string_buffer(ret, retsize) # Does not add trailing 0
if bool(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 ''
bufsize = len(buf)
if bool(namebuf):
if bufsize > size:
return -ERANGE
memmove(namebuf, buf, bufsize)
return bufsize
def removexattr(self, path, name):
return self.operations('removexattr', path, name)
def opendir(self, path, fip):
# Ignore raw_fi
fip.contents.fh = self.operations('opendir', path)
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):
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('utf-8'), st, offset) != 0:
break
return 0
def releasedir(self, path, fip):
# Ignore raw_fi
return self.operations('releasedir', path, fip.contents.fh)
def fsyncdir(self, path, datasync, fip):
# Ignore raw_fi
return self.operations('fsyncdir', 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, amode)
def create(self, path, mode, fip):
fi = fip.contents
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)
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)
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)
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, times)
def bmap(self, path, blocksize, idx):
return self.operations('bmap', path, blocksize, idx)
class Operations(object):
"""This class should be subclassed and passed as an argument to FUSE on
initialization. All operations should raise an OSError 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 OSError(EFAULT, '')
return getattr(self, op)(*args)
def access(self, path, amode):
return 0
bmap = None
def chmod(self, path, mode):
raise OSError(EROFS, '')
def chown(self, path, uid, gid):
raise OSError(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 OSError(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 OSError(ENOENT, '')
return dict(st_mode=(S_IFDIR | 0o755), st_nlink=2)
def getxattr(self, path, name, position=0):
raise OSError(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):
raise OSError(EROFS, '')
def listxattr(self, path):
return []
lock = None
def mkdir(self, path, mode):
raise OSError(EROFS, '')
def mknod(self, path, mode, dev):
raise OSError(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 OSError(ENOENT, '')
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 OSError(ENOENT, '')
def release(self, path, fh):
return 0
def releasedir(self, path, fh):
return 0
def removexattr(self, path, name):
raise OSError(ENOTSUP, '')
def rename(self, old, new):
raise OSError(EROFS, '')
def rmdir(self, path):
raise OSError(EROFS, '')
def setxattr(self, path, name, value, options, position=0):
raise OSError(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):
raise OSError(EROFS, '')
def truncate(self, path, length, fh=None):
raise OSError(EROFS, '')
def unlink(self, path):
raise OSError(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 OSError(EROFS, '')
class LoggingMixIn:
def __call__(self, op, path, *args):
logging.debug('-> %s %s %s', op, path, repr(args))
ret = '[Unknown Error]'
try:
ret = getattr(self, op)(path, *args)
return ret
except OSError as e:
ret = str(e)
raise
finally:
logging.debug('<- %s %s', op, repr(ret))

619
lib/python/fusepy/fusell.py Normal file
View File

@ -0,0 +1,619 @@
# Copyright (c) 2010 Giorgos Verigakis <verigak@gmail.com>
#
# 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 division
from ctypes import *
from ctypes.util import find_library
from errno import *
from functools import partial, wraps
from inspect import getmembers, ismethod
from platform import machine, system
from stat import S_IFDIR, S_IFREG
_system = system()
_machine = machine()
class LibFUSE(CDLL):
def __init__(self):
if _system == 'Darwin':
self.libiconv = CDLL(find_library('iconv'), RTLD_GLOBAL)
super(LibFUSE, self).__init__(find_library('fuse'))
self.fuse_mount.argtypes = (c_char_p, POINTER(fuse_args))
self.fuse_mount.restype = c_void_p
self.fuse_lowlevel_new.argtypes = (POINTER(fuse_args), POINTER(fuse_lowlevel_ops),
c_size_t, c_void_p)
self.fuse_lowlevel_new.restype = c_void_p
self.fuse_set_signal_handlers.argtypes = (c_void_p,)
self.fuse_session_add_chan.argtypes = (c_void_p, c_void_p)
self.fuse_session_loop.argtypes = (c_void_p,)
self.fuse_remove_signal_handlers.argtypes = (c_void_p,)
self.fuse_session_remove_chan.argtypes = (c_void_p,)
self.fuse_session_destroy.argtypes = (c_void_p,)
self.fuse_unmount.argtypes = (c_char_p, c_void_p)
self.fuse_req_ctx.restype = POINTER(fuse_ctx)
self.fuse_req_ctx.argtypes = (fuse_req_t,)
self.fuse_reply_err.argtypes = (fuse_req_t, c_int)
self.fuse_reply_attr.argtypes = (fuse_req_t, c_void_p, c_double)
self.fuse_reply_entry.argtypes = (fuse_req_t, c_void_p)
self.fuse_reply_open.argtypes = (fuse_req_t, c_void_p)
self.fuse_reply_buf.argtypes = (fuse_req_t, c_char_p, c_size_t)
self.fuse_reply_write.argtypes = (fuse_req_t, c_size_t)
self.fuse_add_direntry.argtypes = (c_void_p, c_char_p, c_size_t, c_char_p,
c_stat_p, c_off_t)
class fuse_args(Structure):
_fields_ = [('argc', c_int), ('argv', POINTER(c_char_p)), ('allocated', c_int)]
class c_timespec(Structure):
_fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)]
class c_stat(Structure):
pass # Platform dependent
if _system == 'Darwin':
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
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
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 == '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)]
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)]
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_ctx(Structure):
_fields_ = [('uid', c_uid_t), ('gid', c_gid_t), ('pid', c_pid_t)]
fuse_ino_t = c_ulong
fuse_req_t = c_void_p
c_stat_p = POINTER(c_stat)
fuse_file_info_p = POINTER(fuse_file_info)
FUSE_SET_ATTR = ('st_mode', 'st_uid', 'st_gid', 'st_size', 'st_atime', 'st_mtime')
class fuse_entry_param(Structure):
_fields_ = [
('ino', fuse_ino_t),
('generation', c_ulong),
('attr', c_stat),
('attr_timeout', c_double),
('entry_timeout', c_double)]
class fuse_lowlevel_ops(Structure):
_fields_ = [
('init', CFUNCTYPE(None, c_void_p, c_void_p)),
('destroy', CFUNCTYPE(None, c_void_p)),
('lookup', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)),
('forget', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_ulong)),
('getattr', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
('setattr', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_stat_p, c_int, fuse_file_info_p)),
('readlink', CFUNCTYPE(None, fuse_req_t, fuse_ino_t)),
('mknod', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_mode_t, c_dev_t)),
('mkdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_mode_t)),
('unlink', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)),
('rmdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)),
('symlink', CFUNCTYPE(None, fuse_req_t, c_char_p, fuse_ino_t, c_char_p)),
('rename', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, fuse_ino_t, c_char_p)),
('link', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_ino_t, c_char_p)),
('open', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
('read', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_size_t, c_off_t, fuse_file_info_p)),
('write', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_size_t, c_off_t,
fuse_file_info_p)),
('flush', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
('release', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
('fsync', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_int, fuse_file_info_p)),
('opendir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
('readdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_size_t, c_off_t, fuse_file_info_p)),
('releasedir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
('fsyncdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_int, fuse_file_info_p))]
def struct_to_dict(p):
try:
x = p.contents
return dict((key, getattr(x, key)) for key, type in x._fields_)
except ValueError:
return {}
def stat_to_dict(p):
try:
d = {}
x = p.contents
for key, type in x._fields_:
if key in ('st_atimespec', 'st_mtimespec', 'st_ctimespec'):
ts = getattr(x, key)
key = key[:-4] # Lose the "spec"
d[key] = ts.tv_sec + ts.tv_nsec / 10 ** 9
else:
d[key] = getattr(x, key)
return d
except ValueError:
return {}
def dict_to_stat(d):
for key in ('st_atime', 'st_mtime', 'st_ctime'):
if key in d:
val = d[key]
sec = int(val)
nsec = int((val - sec) * 10 ** 9)
d[key + 'spec'] = c_timespec(sec, nsec)
return c_stat(**d)
def setattr_mask_to_list(mask):
return [FUSE_SET_ATTR[i] for i in range(len(FUSE_SET_ATTR)) if mask & (1 << i)]
class FUSELL(object):
def __init__(self, mountpoint):
self.libfuse = LibFUSE()
fuse_ops = fuse_lowlevel_ops()
for name, prototype in fuse_lowlevel_ops._fields_:
method = getattr(self, 'fuse_' + name, None) or getattr(self, name, None)
if method:
setattr(fuse_ops, name, prototype(method))
args = ['fuse']
argv = fuse_args(len(args), (c_char_p * len(args))(*args), 0)
# TODO: handle initialization errors
chan = self.libfuse.fuse_mount(mountpoint, argv)
assert chan
session = self.libfuse.fuse_lowlevel_new(argv, byref(fuse_ops), sizeof(fuse_ops), None)
assert session
err = self.libfuse.fuse_set_signal_handlers(session)
assert err == 0
self.libfuse.fuse_session_add_chan(session, chan)
err = self.libfuse.fuse_session_loop(session)
assert err == 0
err = self.libfuse.fuse_remove_signal_handlers(session)
assert err == 0
self.libfuse.fuse_session_remove_chan(chan)
self.libfuse.fuse_session_destroy(session)
self.libfuse.fuse_unmount(mountpoint, chan)
def reply_err(self, req, err):
return self.libfuse.fuse_reply_err(req, err)
def reply_none(self, req):
self.libfuse.fuse_reply_none(req)
def reply_entry(self, req, entry):
entry['attr'] = c_stat(**entry['attr'])
e = fuse_entry_param(**entry)
self.libfuse.fuse_reply_entry(req, byref(e))
def reply_create(self, req, *args):
pass # XXX
def reply_attr(self, req, attr, attr_timeout):
st = dict_to_stat(attr)
return self.libfuse.fuse_reply_attr(req, byref(st), c_double(attr_timeout))
def reply_readlink(self, req, *args):
pass # XXX
def reply_open(self, req, d):
fi = fuse_file_info(**d)
return self.libfuse.fuse_reply_open(req, byref(fi))
def reply_write(self, req, count):
return self.libfuse.fuse_reply_write(req, count)
def reply_buf(self, req, buf):
return self.libfuse.fuse_reply_buf(req, buf, len(buf))
def reply_readdir(self, req, size, off, entries):
bufsize = 0
sized_entries = []
for name, attr in entries:
entsize = self.libfuse.fuse_add_direntry(req, None, 0, name, None, 0)
sized_entries.append((name, attr, entsize))
bufsize += entsize
next = 0
buf = create_string_buffer(bufsize)
for name, attr, entsize in sized_entries:
entbuf = cast(addressof(buf) + next, c_char_p)
st = c_stat(**attr)
next += entsize
self.libfuse.fuse_add_direntry(req, entbuf, entsize, name, byref(st), next)
if off < bufsize:
buf = cast(addressof(buf) + off, c_char_p) if off else buf
return self.libfuse.fuse_reply_buf(req, buf, min(bufsize - off, size))
else:
return self.libfuse.fuse_reply_buf(req, None, 0)
# If you override the following methods you should reply directly
# with the self.libfuse.fuse_reply_* methods.
def fuse_getattr(self, req, ino, fi):
self.getattr(req, ino, struct_to_dict(fi))
def fuse_setattr(self, req, ino, attr, to_set, fi):
attr_dict = stat_to_dict(attr)
to_set_list = setattr_mask_to_list(to_set)
fi_dict = struct_to_dict(fi)
self.setattr(req, ino, attr_dict, to_set_list, fi_dict)
def fuse_open(self, req, ino, fi):
self.open(req, ino, struct_to_dict(fi))
def fuse_read(self, req, ino, size, off, fi):
self.read(req, ino, size, off, fi)
def fuse_write(self, req, ino, buf, size, off, fi):
buf_str = string_at(buf, size)
fi_dict = struct_to_dict(fi)
self.write(req, ino, buf_str, off, fi_dict)
def fuse_flush(self, req, ino, fi):
self.flush(req, ino, struct_to_dict(fi))
def fuse_release(self, req, ino, fi):
self.release(req, ino, struct_to_dict(fi))
def fuse_fsync(self, req, ino, datasync, fi):
self.fsyncdir(req, ino, datasync, struct_to_dict(fi))
def fuse_opendir(self, req, ino, fi):
self.opendir(req, ino, struct_to_dict(fi))
def fuse_readdir(self, req, ino, size, off, fi):
self.readdir(req, ino, size, off, struct_to_dict(fi))
def fuse_releasedir(self, req, ino, fi):
self.releasedir(req, ino, struct_to_dict(fi))
def fuse_fsyncdir(self, req, ino, datasync, fi):
self.fsyncdir(req, ino, datasync, struct_to_dict(fi))
# Utility methods
def req_ctx(self, req):
ctx = self.libfuse.fuse_req_ctx(req)
return struct_to_dict(ctx)
# Methods to be overridden in subclasses.
# Reply with the self.reply_* methods.
def init(self, userdata, conn):
"""Initialize filesystem
There's no reply to this method
"""
pass
def destroy(self, userdata):
"""Clean up filesystem
There's no reply to this method
"""
pass
def lookup(self, req, parent, name):
"""Look up a directory entry by name and get its attributes.
Valid replies:
reply_entry
reply_err
"""
self.reply_err(req, ENOENT)
def forget(self, req, ino, nlookup):
"""Forget about an inode
Valid replies:
reply_none
"""
self.reply_none(req)
def getattr(self, req, ino, fi):
"""Get file attributes
Valid replies:
reply_attr
reply_err
"""
if ino == 1:
attr = {'st_ino': 1, 'st_mode': S_IFDIR | 0755, 'st_nlink': 2}
self.reply_attr(req, attr, 1.0)
else:
self.reply_err(req, ENOENT)
def setattr(self, req, ino, attr, to_set, fi):
"""Set file attributes
Valid replies:
reply_attr
reply_err
"""
self.reply_err(req, EROFS)
def readlink(self, req, ino):
"""Read symbolic link
Valid replies:
reply_readlink
reply_err
"""
self.reply_err(req, ENOENT)
def mknod(self, req, parent, name, mode, rdev):
"""Create file node
Valid replies:
reply_entry
reply_err
"""
self.reply_err(req, EROFS)
def mkdir(self, req, parent, name, mode):
"""Create a directory
Valid replies:
reply_entry
reply_err
"""
self.reply_err(req, EROFS)
def unlink(self, req, parent, name):
"""Remove a file
Valid replies:
reply_err
"""
self.reply_err(req, EROFS)
def rmdir(self, req, parent, name):
"""Remove a directory
Valid replies:
reply_err
"""
self.reply_err(req, EROFS)
def symlink(self, req, link, parent, name):
"""Create a symbolic link
Valid replies:
reply_entry
reply_err
"""
self.reply_err(req, EROFS)
def rename(self, req, parent, name, newparent, newname):
"""Rename a file
Valid replies:
reply_err
"""
self.reply_err(req, EROFS)
def link(self, req, ino, newparent, newname):
"""Create a hard link
Valid replies:
reply_entry
reply_err
"""
self.reply_err(req, EROFS)
def open(self, req, ino, fi):
"""Open a file
Valid replies:
reply_open
reply_err
"""
self.reply_open(req, fi)
def read(self, req, ino, size, off, fi):
"""Read data
Valid replies:
reply_buf
reply_err
"""
self.reply_err(req, EIO)
def write(self, req, ino, buf, off, fi):
"""Write data
Valid replies:
reply_write
reply_err
"""
self.reply_err(req, EROFS)
def flush(self, req, ino, fi):
"""Flush method
Valid replies:
reply_err
"""
self.reply_err(req, 0)
def release(self, req, ino, fi):
"""Release an open file
Valid replies:
reply_err
"""
self.reply_err(req, 0)
def fsync(self, req, ino, datasync, fi):
"""Synchronize file contents
Valid replies:
reply_err
"""
self.reply_err(req, 0)
def opendir(self, req, ino, fi):
"""Open a directory
Valid replies:
reply_open
reply_err
"""
self.reply_open(req, fi)
def readdir(self, req, ino, size, off, fi):
"""Read directory
Valid replies:
reply_readdir
reply_err
"""
if ino == 1:
attr = {'st_ino': 1, 'st_mode': S_IFDIR}
entries = [('.', attr), ('..', attr)]
self.reply_readdir(req, size, off, entries)
else:
self.reply_err(req, ENOENT)
def releasedir(self, req, ino, fi):
"""Release an open directory
Valid replies:
reply_err
"""
self.reply_err(req, 0)
def fsyncdir(self, req, ino, datasync, fi):
"""Synchronize directory contents
Valid replies:
reply_err
"""
self.reply_err(req, 0)

98
lib/python/fusepy/loopback.py Executable file
View File

@ -0,0 +1,98 @@
#!/usr/bin/env python
from __future__ import with_statement
from errno import EACCES
from os.path import realpath
from sys import argv, exit
from threading import Lock
import os
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn
class Loopback(LoggingMixIn, Operations):
def __init__(self, root):
self.root = realpath(root)
self.rwlock = Lock()
def __call__(self, op, path, *args):
return super(Loopback, self).__call__(op, self.root + path, *args)
def access(self, path, mode):
if not os.access(path, mode):
raise FuseOSError(EACCES)
chmod = os.chmod
chown = os.chown
def create(self, path, mode):
return os.open(path, os.O_WRONLY | os.O_CREAT, mode)
def flush(self, path, fh):
return os.fsync(fh)
def fsync(self, path, datasync, fh):
return os.fsync(fh)
def getattr(self, path, fh=None):
st = os.lstat(path)
return dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))
getxattr = None
def link(self, target, source):
return os.link(source, target)
listxattr = None
mkdir = os.mkdir
mknod = os.mknod
open = os.open
def read(self, path, size, offset, fh):
with self.rwlock:
os.lseek(fh, offset, 0)
return os.read(fh, size)
def readdir(self, path, fh):
return ['.', '..'] + os.listdir(path)
readlink = os.readlink
def release(self, path, fh):
return os.close(fh)
def rename(self, old, new):
return os.rename(old, self.root + new)
rmdir = os.rmdir
def statfs(self, path):
stv = os.statvfs(path)
return dict((key, getattr(stv, key)) for key in ('f_bavail', 'f_bfree',
'f_blocks', 'f_bsize', 'f_favail', 'f_ffree', 'f_files', 'f_flag',
'f_frsize', 'f_namemax'))
def symlink(self, target, source):
return os.symlink(source, target)
def truncate(self, path, length, fh=None):
with open(path, 'r+') as f:
f.truncate(length)
unlink = os.unlink
utimens = os.utime
def write(self, path, data, offset, fh):
with self.rwlock:
os.lseek(fh, offset, 0)
return os.write(fh, data)
if __name__ == "__main__":
if len(argv) != 3:
print 'usage: %s <root> <mountpoint>' % argv[0]
exit(1)
fuse = FUSE(Loopback(argv[1]), argv[2], foreground=True)

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>llfuse</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.python.pydev.PyDevBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.python.pydev.pythonNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?>
<pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/llfuse</path>
</pydev_pathproperty>
</pydev_project>

View File

@ -0,0 +1,22 @@
Note that the low-level API needs to generate the Python interface
to the local FUSE library before it can be used. For that,
you have to have both the FUSE headers and the GCC-XML
(http://www.gccxml.org) compiler installed.
The interface is generated by running
# python setup.py build_ctypes
this will create the file llfuse/ctypes_api.py
Please keep in mind that it's probably not wise to ship this file
with your application, because it has been generated for your
system only.
Note that the fuse_daemonize() function is deliberately not exported
by this module. If you want to daemonize a Python process, you have to
do so from within Python or you will get into trouble. See
- http://bugs.python.org/issue7931
- http://www.python.org/dev/peps/pep-3143/

Binary file not shown.

View File

@ -0,0 +1,10 @@
/* Necessary to prevent gccxml from complaining about
* an undefined type */
#define __builtin_va_arg_pack_len int
#define FUSE_USE_VERSION 28
#include <fuse_lowlevel.h>
#include <attr/xattr.h>
#include <errno.h>

View File

@ -0,0 +1,24 @@
'''
$Id: __init__.py 47 2010-01-29 17:11:23Z nikratio $
Copyright (c) 2010, Nikolaus Rath <Nikolaus@rath.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the main author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
from __future__ import division, print_function, absolute_import
__all__ = [ 'ctypes_api', 'interface', 'operations' ]
# Wildcard imports desired
#pylint: disable-msg=W0401
from llfuse.operations import *
from llfuse.interface import *

View File

@ -0,0 +1,897 @@
'''
$Id: interface.py 54 2010-02-22 02:33:10Z nikratio $
Copyright (c) 2010, Nikolaus Rath <Nikolaus@rath.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the main author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This module defines the interface between the FUSE C and Python API. The actual file system
is implemented as an `Operations` instance whose methods will
be called by the fuse library.
Note that all "string-like" quantities (e.g. file names, extended attribute names & values) are
represented as bytes, since POSIX doesn't require any of them to be valid unicode strings.
Exception Handling
------------------
Since Python exceptions cannot be forwarded to the FUSE kernel module,
the FUSE Python API catches all exceptions that are generated during
request processing.
If the exception is of type `FUSEError`, the appropriate errno is returned
to the kernel module and the exception is discarded.
For any other exceptions, a warning is logged and a generic error signaled
to the kernel module. Then the `handle_exc` method of the `Operations`
instance is called, so that the file system itself has a chance to react
to the problem (e.g. by marking the file system as needing a check).
The return value and any raised exceptions of `handle_exc` are ignored.
'''
# Since we are using ctype Structures, we often have to
# access attributes that are not defined in __init__
# (since they are defined in _fields_ instead)
#pylint: disable-msg=W0212
# We need globals
#pylint: disable-msg=W0603
from __future__ import division, print_function, absolute_import
# Using .. as libfuse makes PyDev really unhappy.
from . import ctypes_api
libfuse = ctypes_api
from ctypes import c_char_p, sizeof, create_string_buffer, addressof, string_at, POINTER, c_char, cast
from functools import partial
import errno
import logging
import sys
__all__ = [ 'FUSEError', 'ENOATTR', 'ENOTSUP', 'init', 'main', 'close',
'fuse_version' ]
# These should really be defined in the errno module, but
# unfortunately they are missing
ENOATTR = libfuse.ENOATTR
ENOTSUP = libfuse.ENOTSUP
log = logging.getLogger("fuse")
# Init globals
operations = None
fuse_ops = None
mountpoint = None
session = None
channel = None
class DiscardedRequest(Exception):
'''Request was interrupted and reply discarded.
'''
pass
class ReplyError(Exception):
'''Unable to send reply to fuse kernel module.
'''
pass
class FUSEError(Exception):
'''Wrapped errno value to be returned to the fuse kernel module
This exception can store only an errno. Request handlers should raise
to return a specific errno to the fuse kernel module.
'''
__slots__ = [ 'errno' ]
def __init__(self, errno_):
super(FUSEError, self).__init__()
self.errno = errno_
def __str__(self):
# errno may not have strings for all error codes
return errno.errorcode.get(self.errno, str(self.errno))
def check_reply_result(result, func, *args):
'''Check result of a call to a fuse_reply_* foreign function
If `result` is 0, it is assumed that the call succeeded and the function does nothing.
If result is `-errno.ENOENT`, this means that the request has been discarded and `DiscardedRequest`
is raised.
In all other cases, `ReplyError` is raised.
(We do not try to call `fuse_reply_err` or any other reply method as well, because the first reply
function may have already invalidated the `req` object and it seems better to (possibly) let the
request pend than to crash the server application.)
'''
if result == 0:
return None
elif result == -errno.ENOENT:
raise DiscardedRequest()
elif result > 0:
raise ReplyError('Foreign function %s returned unexpected value %d'
% (func.name, result))
elif result < 0:
raise ReplyError('Foreign function %s returned error %s'
% (func.name, errno.errorcode.get(-result, str(-result))))
#
# Set return checker for common ctypes calls
#
reply_functions = [ 'fuse_reply_err', 'fuse_reply_entry',
'fuse_reply_create', 'fuse_reply_readlink', 'fuse_reply_open',
'fuse_reply_write', 'fuse_reply_attr', 'fuse_reply_buf',
'fuse_reply_iov', 'fuse_reply_statfs', 'fuse_reply_xattr',
'fuse_reply_lock' ]
for fname in reply_functions:
getattr(libfuse, fname).errcheck = check_reply_result
# Name isn't stored by ctypes
getattr(libfuse, fname).name = fname
def dict_to_entry(attr):
'''Convert dict to fuse_entry_param'''
entry = libfuse.fuse_entry_param()
entry.ino = attr['st_ino']
entry.generation = attr.pop('generation')
entry.entry_timeout = attr.pop('entry_timeout')
entry.attr_timeout = attr.pop('attr_timeout')
entry.attr = dict_to_stat(attr)
return entry
def dict_to_stat(attr):
'''Convert dict to struct stat'''
stat = libfuse.stat()
# Determine correct way to store times
if hasattr(stat, 'st_atim'): # Linux
get_timespec_key = lambda key: key[:-1]
elif hasattr(stat, 'st_atimespec'): # FreeBSD
get_timespec_key = lambda key: key + 'spec'
else:
get_timespec_key = False
# Raises exception if there are any unknown keys
for (key, val) in attr.iteritems():
if val is None: # do not set undefined items
continue
if get_timespec_key and key in ('st_atime', 'st_mtime', 'st_ctime'):
key = get_timespec_key(key)
spec = libfuse.timespec()
spec.tv_sec = int(val)
spec.tv_nsec = int((val - int(val)) * 10 ** 9)
val = spec
setattr(stat, key, val)
return stat
def stat_to_dict(stat):
'''Convert ``struct stat`` to dict'''
attr = dict()
for (name, dummy) in libfuse.stat._fields_:
if name.startswith('__'):
continue
if name in ('st_atim', 'st_mtim', 'st_ctim'):
key = name + 'e'
attr[key] = getattr(stat, name).tv_sec + getattr(stat, name).tv_nsec / 10 ** 9
elif name in ('st_atimespec', 'st_mtimespec', 'st_ctimespec'):
key = name[:-4]
attr[key] = getattr(stat, name).tv_sec + getattr(stat, name).tv_nsec / 10 ** 9
else:
attr[name] = getattr(stat, name)
return attr
def op_wrapper(func, req, *args):
'''Catch all exceptions and call fuse_reply_err instead'''
try:
func(req, *args)
except FUSEError as e:
log.debug('op_wrapper caught FUSEError, calling fuse_reply_err(%s)',
errno.errorcode.get(e.errno, str(e.errno)))
try:
libfuse.fuse_reply_err(req, e.errno)
except DiscardedRequest:
pass
except Exception as exc:
log.exception('FUSE handler raised exception.')
# Report error to filesystem
if hasattr(operations, 'handle_exc'):
try:
operations.handle_exc(exc)
except:
pass
# Send error reply, unless the error occured when replying
if not isinstance(exc, ReplyError):
log.debug('Calling fuse_reply_err(EIO)')
libfuse.fuse_reply_err(req, errno.EIO)
def fuse_version():
'''Return version of loaded fuse library'''
return libfuse.fuse_version()
def init(operations_, mountpoint_, args):
'''Initialize and mount FUSE file system
`operations_` has to be an instance of the `Operations` class (or another
class defining the same methods).
`args` has to be a list of strings. Valid options are listed in struct fuse_opt fuse_mount_opts[]
(mount.c:68) and struct fuse_opt fuse_ll_opts[] (fuse_lowlevel_c:1526).
'''
log.debug('Initializing llfuse')
global operations
global fuse_ops
global mountpoint
global session
global channel
# Give operations instance a chance to check and change
# the FUSE options
operations_.check_args(args)
mountpoint = mountpoint_
operations = operations_
fuse_ops = libfuse.fuse_lowlevel_ops()
fuse_args = make_fuse_args(args)
# Init fuse_ops
module = globals()
for (name, prototype) in libfuse.fuse_lowlevel_ops._fields_:
if hasattr(operations, name):
method = partial(op_wrapper, module['fuse_' + name])
setattr(fuse_ops, name, prototype(method))
log.debug('Calling fuse_mount')
channel = libfuse.fuse_mount(mountpoint, fuse_args)
if not channel:
raise RuntimeError('fuse_mount failed')
try:
log.debug('Calling fuse_lowlevel_new')
session = libfuse.fuse_lowlevel_new(fuse_args, fuse_ops, sizeof(fuse_ops), None)
if not session:
raise RuntimeError("fuse_lowlevel_new() failed")
try:
log.debug('Calling fuse_set_signal_handlers')
if libfuse.fuse_set_signal_handlers(session) == -1:
raise RuntimeError("fuse_set_signal_handlers() failed")
try:
log.debug('Calling fuse_session_add_chan')
libfuse.fuse_session_add_chan(session, channel)
session = session
channel = channel
return
except:
log.debug('Calling fuse_remove_signal_handlers')
libfuse.fuse_remove_signal_handlers(session)
raise
except:
log.debug('Calling fuse_session_destroy')
libfuse.fuse_session_destroy(session)
raise
except:
log.debug('Calling fuse_unmount')
libfuse.fuse_unmount(mountpoint, channel)
raise
def make_fuse_args(args):
'''Create fuse_args Structure for given mount options'''
args1 = [ sys.argv[0] ]
for opt in args:
args1.append(b'-o')
args1.append(opt)
# Init fuse_args struct
fuse_args = libfuse.fuse_args()
fuse_args.allocated = 0
fuse_args.argc = len(args1)
fuse_args.argv = (POINTER(c_char) * len(args1))(*[cast(c_char_p(x), POINTER(c_char))
for x in args1])
return fuse_args
def main(single=False):
'''Run FUSE main loop'''
if not session:
raise RuntimeError('Need to call init() before main()')
if single:
log.debug('Calling fuse_session_loop')
if libfuse.fuse_session_loop(session) != 0:
raise RuntimeError("fuse_session_loop() failed")
else:
log.debug('Calling fuse_session_loop_mt')
if libfuse.fuse_session_loop_mt(session) != 0:
raise RuntimeError("fuse_session_loop_mt() failed")
def close():
'''Unmount file system and clean up'''
global operations
global fuse_ops
global mountpoint
global session
global channel
log.debug('Calling fuse_session_remove_chan')
libfuse.fuse_session_remove_chan(channel)
log.debug('Calling fuse_remove_signal_handlers')
libfuse.fuse_remove_signal_handlers(session)
log.debug('Calling fuse_session_destroy')
libfuse.fuse_session_destroy(session)
log.debug('Calling fuse_unmount')
libfuse.fuse_unmount(mountpoint, channel)
operations = None
fuse_ops = None
mountpoint = None
session = None
channel = None
def fuse_lookup(req, parent_inode, name):
'''Look up a directory entry by name and get its attributes'''
log.debug('Handling lookup(%d, %s)', parent_inode, string_at(name))
attr = operations.lookup(parent_inode, string_at(name))
entry = dict_to_entry(attr)
log.debug('Calling fuse_reply_entry')
try:
libfuse.fuse_reply_entry(req, entry)
except DiscardedRequest:
pass
def fuse_init(userdata_p, conn_info_p):
'''Initialize Operations'''
operations.init()
def fuse_destroy(userdata_p):
'''Cleanup Operations'''
operations.destroy()
def fuse_getattr(req, ino, _unused):
'''Get attributes for `ino`'''
log.debug('Handling getattr(%d)', ino)
attr = operations.getattr(ino)
attr_timeout = attr.pop('attr_timeout')
stat = dict_to_stat(attr)
log.debug('Calling fuse_reply_attr')
try:
libfuse.fuse_reply_attr(req, stat, attr_timeout)
except DiscardedRequest:
pass
def fuse_access(req, ino, mask):
'''Check if calling user has `mask` rights for `ino`'''
log.debug('Handling access(%d, %o)', ino, mask)
# Get UID
ctx = libfuse.fuse_req_ctx(req).contents
# Define a function that returns a list of the GIDs
def get_gids():
# Get GID list if FUSE supports it
# Weird syntax to prevent PyDev from complaining
getgroups = getattr(libfuse, "fuse_req_getgroups")
gid_t = getattr(libfuse, 'gid_t')
no = 10
buf = (gid_t * no)(range(no))
ret = getgroups(req, no, buf)
if ret > no:
no = ret
buf = (gid_t * no)(range(no))
ret = getgroups(req, no, buf)
return [ buf[i].value for i in range(ret) ]
ret = operations.access(ino, mask, ctx, get_gids)
log.debug('Calling fuse_reply_err')
try:
if ret:
libfuse.fuse_reply_err(req, 0)
else:
libfuse.fuse_reply_err(req, errno.EPERM)
except DiscardedRequest:
pass
def fuse_create(req, ino_parent, name, mode, fi):
'''Create and open a file'''
log.debug('Handling create(%d, %s, %o)', ino_parent, string_at(name), mode)
(fh, attr) = operations.create(ino_parent, string_at(name), mode,
libfuse.fuse_req_ctx(req).contents)
fi.contents.fh = fh
fi.contents.keep_cache = 1
entry = dict_to_entry(attr)
log.debug('Calling fuse_reply_create')
try:
libfuse.fuse_reply_create(req, entry, fi)
except DiscardedRequest:
operations.release(fh)
def fuse_flush(req, ino, fi):
'''Handle close() system call
May be called multiple times for the same open file.
'''
log.debug('Handling flush(%d)', fi.contents.fh)
operations.flush(fi.contents.fh)
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_fsync(req, ino, datasync, fi):
'''Flush buffers for `ino`
If the datasync parameter is non-zero, then only the user data
is flushed (and not the meta data).
'''
log.debug('Handling fsync(%d, %s)', fi.contents.fh, datasync != 0)
operations.fsync(fi.contents.fh, datasync != 0)
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_fsyncdir(req, ino, datasync, fi):
'''Synchronize directory contents
If the datasync parameter is non-zero, then only the directory contents
are flushed (and not the meta data about the directory itself).
'''
log.debug('Handling fsyncdir(%d, %s)', fi.contents.fh, datasync != 0)
operations.fsyncdir(fi.contents.fh, datasync != 0)
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_getxattr(req, ino, name, size):
'''Get an extended attribute.
'''
log.debug('Handling getxattr(%d, %r, %d)', ino, string_at(name), size)
val = operations.getxattr(ino, string_at(name))
if not isinstance(val, bytes):
raise TypeError("getxattr return value must be of type bytes")
try:
if size == 0:
log.debug('Calling fuse_reply_xattr')
libfuse.fuse_reply_xattr(req, len(val))
elif size >= len(val):
log.debug('Calling fuse_reply_buf')
libfuse.fuse_reply_buf(req, val, len(val))
else:
raise FUSEError(errno.ERANGE)
except DiscardedRequest:
pass
def fuse_link(req, ino, new_parent_ino, new_name):
'''Create a hard link'''
log.debug('Handling fuse_link(%d, %d, %s)', ino, new_parent_ino, string_at(new_name))
attr = operations.link(ino, new_parent_ino, string_at(new_name))
entry = dict_to_entry(attr)
log.debug('Calling fuse_reply_entry')
try:
libfuse.fuse_reply_entry(req, entry)
except DiscardedRequest:
pass
def fuse_listxattr(req, inode, size):
'''List extended attributes for `inode`'''
log.debug('Handling listxattr(%d)', inode)
names = operations.listxattr(inode)
if not all([ isinstance(name, bytes) for name in names]):
raise TypeError("listxattr return value must be list of bytes")
# Size of the \0 separated buffer
act_size = (len(names) - 1) + sum([ len(name) for name in names ])
if size == 0:
try:
log.debug('Calling fuse_reply_xattr')
libfuse.fuse_reply_xattr(req, len(names))
except DiscardedRequest:
pass
elif act_size > size:
raise FUSEError(errno.ERANGE)
else:
try:
log.debug('Calling fuse_reply_buf')
libfuse.fuse_reply_buf(req, b'\0'.join(names), act_size)
except DiscardedRequest:
pass
def fuse_mkdir(req, inode_parent, name, mode):
'''Create directory'''
log.debug('Handling mkdir(%d, %s, %o)', inode_parent, string_at(name), mode)
attr = operations.mkdir(inode_parent, string_at(name), mode,
libfuse.fuse_req_ctx(req).contents)
entry = dict_to_entry(attr)
log.debug('Calling fuse_reply_entry')
try:
libfuse.fuse_reply_entry(req, entry)
except DiscardedRequest:
pass
def fuse_mknod(req, inode_parent, name, mode, rdev):
'''Create (possibly special) file'''
log.debug('Handling mknod(%d, %s, %o, %d)', inode_parent, string_at(name),
mode, rdev)
attr = operations.mknod(inode_parent, string_at(name), mode, rdev,
libfuse.fuse_req_ctx(req).contents)
entry = dict_to_entry(attr)
log.debug('Calling fuse_reply_entry')
try:
libfuse.fuse_reply_entry(req, entry)
except DiscardedRequest:
pass
def fuse_open(req, inode, fi):
'''Open a file'''
log.debug('Handling open(%d, %d)', inode, fi.contents.flags)
fi.contents.fh = operations.open(inode, fi.contents.flags)
fi.contents.keep_cache = 1
log.debug('Calling fuse_reply_open')
try:
libfuse.fuse_reply_open(req, fi)
except DiscardedRequest:
operations.release(inode, fi.contents.fh)
def fuse_opendir(req, inode, fi):
'''Open a directory'''
log.debug('Handling opendir(%d)', inode)
fi.contents.fh = operations.opendir(inode)
log.debug('Calling fuse_reply_open')
try:
libfuse.fuse_reply_open(req, fi)
except DiscardedRequest:
operations.releasedir(fi.contents.fh)
def fuse_read(req, ino, size, off, fi):
'''Read data from file'''
log.debug('Handling read(ino=%d, off=%d, size=%d)', fi.contents.fh, off, size)
data = operations.read(fi.contents.fh, off, size)
if not isinstance(data, bytes):
raise TypeError("read() must return bytes")
if len(data) > size:
raise ValueError('read() must not return more than `size` bytes')
log.debug('Calling fuse_reply_buf')
try:
libfuse.fuse_reply_buf(req, data, len(data))
except DiscardedRequest:
pass
def fuse_readlink(req, inode):
'''Read target of symbolic link'''
log.debug('Handling readlink(%d)', inode)
target = operations.readlink(inode)
log.debug('Calling fuse_reply_readlink')
try:
libfuse.fuse_reply_readlink(req, target)
except DiscardedRequest:
pass
def fuse_readdir(req, ino, bufsize, off, fi):
'''Read directory entries'''
log.debug('Handling readdir(%d, %d, %d, %d)', ino, bufsize, off, fi.contents.fh)
# Collect as much entries as we can return
entries = list()
size = 0
for (name, attr) in operations.readdir(fi.contents.fh, off):
if not isinstance(name, bytes):
raise TypeError("readdir() must return entry names as bytes")
stat = dict_to_stat(attr)
entry_size = libfuse.fuse_add_direntry(req, None, 0, name, stat, 0)
if size + entry_size > bufsize:
break
entries.append((name, stat))
size += entry_size
log.debug('Gathered %d entries, total size %d', len(entries), size)
# If there are no entries left, return empty buffer
if not entries:
try:
log.debug('Calling fuse_reply_buf')
libfuse.fuse_reply_buf(req, None, 0)
except DiscardedRequest:
pass
return
# Create and fill buffer
log.debug('Adding entries to buffer')
buf = create_string_buffer(size)
next_ = off
addr_off = 0
for (name, stat) in entries:
next_ += 1
addr_off += libfuse.fuse_add_direntry(req, cast(addressof(buf) + addr_off, POINTER(c_char)),
bufsize, name, stat, next_)
# Return buffer
log.debug('Calling fuse_reply_buf')
try:
libfuse.fuse_reply_buf(req, buf, size)
except DiscardedRequest:
pass
def fuse_release(req, inode, fi):
'''Release open file'''
log.debug('Handling release(%d)', fi.contents.fh)
operations.release(fi.contents.fh)
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_releasedir(req, inode, fi):
'''Release open directory'''
log.debug('Handling releasedir(%d)', fi.contents.fh)
operations.releasedir(fi.contents.fh)
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_removexattr(req, inode, name):
'''Remove extended attribute'''
log.debug('Handling removexattr(%d, %s)', inode, string_at(name))
operations.removexattr(inode, string_at(name))
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_rename(req, parent_inode_old, name_old, parent_inode_new, name_new):
'''Rename a directory entry'''
log.debug('Handling rename(%d, %r, %d, %r)', parent_inode_old, string_at(name_old),
parent_inode_new, string_at(name_new))
operations.rename(parent_inode_old, string_at(name_old), parent_inode_new,
string_at(name_new))
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_rmdir(req, inode_parent, name):
'''Remove a directory'''
log.debug('Handling rmdir(%d, %r)', inode_parent, string_at(name))
operations.rmdir(inode_parent, string_at(name))
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_setattr(req, inode, stat, to_set, fi):
'''Change directory entry attributes'''
log.debug('Handling fuse_setattr(%d)', inode)
# Note: We can't check if we know all possible flags,
# because the part of to_set that is not "covered"
# by flags seems to be undefined rather than zero.
attr_all = stat_to_dict(stat.contents)
attr = dict()
if (to_set & libfuse.FUSE_SET_ATTR_MTIME) != 0:
attr['st_mtime'] = attr_all['st_mtime']
if (to_set & libfuse.FUSE_SET_ATTR_ATIME) != 0:
attr['st_atime'] = attr_all['st_atime']
if (to_set & libfuse.FUSE_SET_ATTR_MODE) != 0:
attr['st_mode'] = attr_all['st_mode']
if (to_set & libfuse.FUSE_SET_ATTR_UID) != 0:
attr['st_uid'] = attr_all['st_uid']
if (to_set & libfuse.FUSE_SET_ATTR_GID) != 0:
attr['st_gid'] = attr_all['st_gid']
if (to_set & libfuse.FUSE_SET_ATTR_SIZE) != 0:
attr['st_size'] = attr_all['st_size']
attr = operations.setattr(inode, attr)
attr_timeout = attr.pop('attr_timeout')
stat = dict_to_stat(attr)
log.debug('Calling fuse_reply_attr')
try:
libfuse.fuse_reply_attr(req, stat, attr_timeout)
except DiscardedRequest:
pass
def fuse_setxattr(req, inode, name, val, size, flags):
'''Set an extended attribute'''
log.debug('Handling setxattr(%d, %r, %r, %d)', inode, string_at(name),
string_at(val, size), flags)
# Make sure we know all the flags
if (flags & ~(libfuse.XATTR_CREATE | libfuse.XATTR_REPLACE)) != 0:
raise ValueError('unknown flag')
if (flags & libfuse.XATTR_CREATE) != 0:
try:
operations.getxattr(inode, string_at(name))
except FUSEError as e:
if e.errno == ENOATTR:
pass
raise
else:
raise FUSEError(errno.EEXIST)
elif (flags & libfuse.XATTR_REPLACE) != 0:
# Exception can be passed on if the attribute does not exist
operations.getxattr(inode, string_at(name))
operations.setxattr(inode, string_at(name), string_at(val, size))
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_statfs(req, inode):
'''Return filesystem statistics'''
log.debug('Handling statfs(%d)', inode)
attr = operations.statfs()
statfs = libfuse.statvfs()
for (key, val) in attr.iteritems():
setattr(statfs, key, val)
log.debug('Calling fuse_reply_statfs')
try:
libfuse.fuse_reply_statfs(req, statfs)
except DiscardedRequest:
pass
def fuse_symlink(req, target, parent_inode, name):
'''Create a symbolic link'''
log.debug('Handling symlink(%d, %r, %r)', parent_inode, string_at(name), string_at(target))
attr = operations.symlink(parent_inode, string_at(name), string_at(target),
libfuse.fuse_req_ctx(req).contents)
entry = dict_to_entry(attr)
log.debug('Calling fuse_reply_entry')
try:
libfuse.fuse_reply_entry(req, entry)
except DiscardedRequest:
pass
def fuse_unlink(req, parent_inode, name):
'''Delete a file'''
log.debug('Handling unlink(%d, %r)', parent_inode, string_at(name))
operations.unlink(parent_inode, string_at(name))
log.debug('Calling fuse_reply_err(0)')
try:
libfuse.fuse_reply_err(req, 0)
except DiscardedRequest:
pass
def fuse_write(req, inode, buf, size, off, fi):
'''Write into an open file handle'''
log.debug('Handling write(fh=%d, off=%d, size=%d)', fi.contents.fh, off, size)
written = operations.write(fi.contents.fh, off, string_at(buf, size))
log.debug('Calling fuse_reply_write')
try:
libfuse.fuse_reply_write(req, written)
except DiscardedRequest:
pass

View File

@ -0,0 +1,348 @@
'''
$Id: operations.py 47 2010-01-29 17:11:23Z nikratio $
Copyright (c) 2010, Nikolaus Rath <Nikolaus@rath.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the main author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
from __future__ import division, print_function, absolute_import
from .interface import FUSEError
import errno
class Operations(object):
'''
This is a dummy class that just documents the possible methods that
a file system may declare.
'''
# This is a dummy class, so all the methods could of course
# be functions
#pylint: disable-msg=R0201
def handle_exc(self, exc):
'''Handle exceptions that occured during request processing.
This method returns nothing and does not raise any exceptions itself.
'''
pass
def init(self):
'''Initialize operations
This function has to be called before any request has been received,
but after the mountpoint has been set up and the process has
daemonized.
'''
pass
def destroy(self):
'''Clean up operations.
This method has to be called after the last request has been
received, when the file system is about to be unmounted.
'''
pass
def check_args(self, fuse_args):
'''Review FUSE arguments
This method checks if the FUSE options `fuse_args` are compatible
with the way that the file system operations are implemented.
It raises an exception if incompatible options are encountered and
silently adds required options if they are missing.
'''
pass
def readdir(self, fh, off):
'''Read directory entries
This method returns an iterator over the contents of directory `fh`,
starting at entry `off`. The iterator yields tuples of the form
``(name, attr)``, where ``attr` is a dict with keys corresponding to
the elements of ``struct stat``.
Iteration may be stopped as soon as enough elements have been
retrieved and does not have to be continued until `StopIteration`
is raised.
'''
raise FUSEError(errno.ENOSYS)
def read(self, fh, off, size):
'''Read `size` bytes from `fh` at position `off`
Unless the file has been opened in direct_io mode or EOF is reached,
this function returns exactly `size` bytes.
'''
raise FUSEError(errno.ENOSYS)
def link(self, inode, new_parent_inode, new_name):
'''Create a hard link.
Returns a dict with the attributes of the newly created directory
entry. The keys are the same as for `lookup`.
'''
raise FUSEError(errno.ENOSYS)
def open(self, inode, flags):
'''Open a file.
Returns an (integer) file handle. `flags` is a bitwise or of the open flags
described in open(2) and defined in the `os` module (with the exception of
``O_CREAT``, ``O_EXCL``, ``O_NOCTTY`` and ``O_TRUNC``)
'''
raise FUSEError(errno.ENOSYS)
def opendir(self, inode):
'''Open a directory.
Returns an (integer) file handle.
'''
raise FUSEError(errno.ENOSYS)
def mkdir(self, parent_inode, name, mode, ctx):
'''Create a directory
`ctx` must be a context object that contains pid, uid and
primary gid of the requesting process.
Returns a dict with the attributes of the newly created directory
entry. The keys are the same as for `lookup`.
'''
raise FUSEError(errno.ENOSYS)
def mknod(self, parent_inode, name, mode, rdev, ctx):
'''Create (possibly special) file
`ctx` must be a context object that contains pid, uid and
primary gid of the requesting process.
Returns a dict with the attributes of the newly created directory
entry. The keys are the same as for `lookup`.
'''
raise FUSEError(errno.ENOSYS)
def lookup(self, parent_inode, name):
'''Look up a directory entry by name and get its attributes.
Returns a dict with keys corresponding to the elements in
``struct stat`` and the following additional keys:
:generation: The inode generation number
:attr_timeout: Validity timeout (in seconds) for the attributes
:entry_timeout: Validity timeout (in seconds) for the name
Note also that the ``st_Xtime`` entries support floating point numbers
to allow for nano second resolution.
The returned dict can be modified at will by the caller without
influencing the internal state of the file system.
If the entry does not exist, raises `FUSEError(errno.ENOENT)`.
'''
raise FUSEError(errno.ENOSYS)
def listxattr(self, inode):
'''Get list of extended attribute names'''
raise FUSEError(errno.ENOSYS)
def getattr(self, inode):
'''Get attributes for `inode`
Returns a dict with keys corresponding to the elements in
``struct stat`` and the following additional keys:
:attr_timeout: Validity timeout (in seconds) for the attributes
The returned dict can be modified at will by the caller without
influencing the internal state of the file system.
Note that the ``st_Xtime`` entries support floating point numbers
to allow for nano second resolution.
'''
raise FUSEError(errno.ENOSYS)
def getxattr(self, inode, name):
'''Return extended attribute value
If the attribute does not exist, raises `FUSEError(ENOATTR)`
'''
raise FUSEError(errno.ENOSYS)
def access(self, inode, mode, ctx, get_sup_gids):
'''Check if requesting process has `mode` rights on `inode`.
Returns a boolean value. `get_sup_gids` must be a function that
returns a list of the supplementary group ids of the requester.
`ctx` must be a context object that contains pid, uid and
primary gid of the requesting process.
'''
raise FUSEError(errno.ENOSYS)
def create(self, inode_parent, name, mode, ctx):
'''Create a file and open it
`ctx` must be a context object that contains pid, uid and
primary gid of the requesting process.
Returns a tuple of the form ``(fh, attr)``. `fh` is
integer file handle that is used to identify the open file and
`attr` is a dict similar to the one returned by `lookup`.
'''
raise FUSEError(errno.ENOSYS)
def flush(self, fh):
'''Handle close() syscall.
May be called multiple times for the same open file (e.g. if the file handle
has been duplicated).
If the filesystem supports file locking operations, all locks belonging
to the file handle's owner are cleared.
'''
raise FUSEError(errno.ENOSYS)
def fsync(self, fh, datasync):
'''Flush buffers for file `fh`
If `datasync` is true, only the user data is flushed (and no meta data).
'''
raise FUSEError(errno.ENOSYS)
def fsyncdir(self, fh, datasync):
'''Flush buffers for directory `fh`
If the `datasync` is true, then only the directory contents
are flushed (and not the meta data about the directory itself).
'''
raise FUSEError(errno.ENOSYS)
def readlink(self, inode):
'''Return target of symbolic link'''
raise FUSEError(errno.ENOSYS)
def release(self, fh):
'''Release open file
This method must be called exactly once for each `open` call.
'''
raise FUSEError(errno.ENOSYS)
def releasedir(self, fh):
'''Release open directory
This method must be called exactly once for each `opendir` call.
'''
raise FUSEError(errno.ENOSYS)
def removexattr(self, inode, name):
'''Remove extended attribute
If the attribute does not exist, raises FUSEError(ENOATTR)
'''
raise FUSEError(errno.ENOSYS)
def rename(self, inode_parent_old, name_old, inode_parent_new, name_new):
'''Rename a directory entry'''
raise FUSEError(errno.ENOSYS)
def rmdir(self, inode_parent, name):
'''Remove a directory'''
raise FUSEError(errno.ENOSYS)
def setattr(self, inode, attr):
'''Change directory entry attributes
`attr` must be a dict with keys corresponding to the attributes of
``struct stat``. `attr` may also include a new value for ``st_size`` which
means that the file should be truncated or extended.
Returns a dict with the new attributs of the directory entry,
similar to the one returned by `getattr()`
'''
raise FUSEError(errno.ENOSYS)
def setxattr(self, inode, name, value):
'''Set an extended attribute.
The attribute may or may not exist already.
'''
raise FUSEError(errno.ENOSYS)
def statfs(self):
'''Get file system statistics
Returns a `dict` with keys corresponding to the attributes of
``struct statfs``.
'''
raise FUSEError(errno.ENOSYS)
def symlink(self, inode_parent, name, target, ctx):
'''Create a symbolic link
`ctx` must be a context object that contains pid, uid and
primary gid of the requesting process.
Returns a dict with the attributes of the newly created directory
entry. The keys are the same as for `lookup`.
'''
raise FUSEError(errno.ENOSYS)
def unlink(self, parent_inode, name):
'''Remove a (possibly special) file'''
raise FUSEError(errno.ENOSYS)
def write(self, fh, off, data):
'''Write data into an open file
Returns the number of bytes written.
Unless the file was opened in ``direct_io`` mode, this is always equal to
`len(data)`.
'''
raise FUSEError(errno.ENOSYS)

View File

@ -0,0 +1,115 @@
#!/usr/bin/env python
'''
$Id: llfuse_example.py 46 2010-01-29 17:10:10Z nikratio $
Copyright (c) 2010, Nikolaus Rath <Nikolaus@rath.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the main author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
from __future__ import division, print_function, absolute_import
import llfuse
import errno
import stat
import sys
class Operations(llfuse.Operations):
'''A very simple example filesystem'''
def __init__(self):
super(Operations, self).__init__()
self.entries = [
# name, attr
(b'.', { 'st_ino': 1,
'st_mode': stat.S_IFDIR | 0755,
'st_nlink': 2}),
(b'..', { 'st_ino': 1,
'st_mode': stat.S_IFDIR | 0755,
'st_nlink': 2}),
(b'file1', { 'st_ino': 2, 'st_nlink': 1,
'st_mode': stat.S_IFREG | 0644 }),
(b'file2', { 'st_ino': 3, 'st_nlink': 1,
'st_mode': stat.S_IFREG | 0644 }) ]
self.contents = { # Inode: Contents
2: b'Hello, World\n',
3: b'Some more file contents\n'
}
self.by_inode = dict()
self.by_name = dict()
for entry in self.entries:
(name, attr) = entry
if attr['st_ino'] in self.contents:
attr['st_size'] = len(self.contents[attr['st_ino']])
self.by_inode[attr['st_ino']] = attr
self.by_name[name] = attr
def lookup(self, parent_inode, name):
try:
attr = self.by_name[name].copy()
except KeyError:
raise llfuse.FUSEError(errno.ENOENT)
attr['attr_timeout'] = 1
attr['entry_timeout'] = 1
attr['generation'] = 1
return attr
def getattr(self, inode):
attr = self.by_inode[inode].copy()
attr['attr_timeout'] = 1
return attr
def readdir(self, fh, off):
for entry in self.entries:
if off > 0:
off -= 1
continue
yield entry
def read(self, fh, off, size):
return self.contents[fh][off:off+size]
def open(self, inode, flags):
if inode in self.contents:
return inode
else:
raise RuntimeError('Attempted to open() a directory')
def opendir(self, inode):
return inode
def access(self, inode, mode, ctx, get_sup_gids):
return True
if __name__ == '__main__':
if len(sys.argv) != 2:
raise SystemExit('Usage: %s <mountpoint>' % sys.argv[0])
mountpoint = sys.argv[1]
operations = Operations()
llfuse.init(operations, mountpoint, [ b"nonempty", b'fsname=llfuses_xmp' ])
llfuse.main()

View File

@ -0,0 +1,127 @@
#!/usr/bin/env python
'''
$Id: setup.py 53 2010-02-22 01:48:45Z nikratio $
Copyright (c) 2010, Nikolaus Rath <Nikolaus@rath.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the main author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
from __future__ import division, print_function
from distutils.core import setup, Command
import distutils.command.build
import sys
import os
import tempfile
import subprocess
import re
import logging
import ctypes.util
# These are the definitions that we need
fuse_export_regex = ['^FUSE_SET_.*', '^XATTR_.*', 'fuse_reply_.*' ]
fuse_export_symbols = ['fuse_mount', 'fuse_lowlevel_new', 'fuse_add_direntry',
'fuse_set_signal_handlers', 'fuse_session_add_chan',
'fuse_session_loop_mt', 'fuse_session_remove_chan',
'fuse_remove_signal_handlers', 'fuse_session_destroy',
'fuse_unmount', 'fuse_req_ctx', 'fuse_lowlevel_ops',
'fuse_session_loop', 'ENOATTR', 'ENOTSUP',
'fuse_version' ]
class build_ctypes(Command):
description = "Build ctypes interfaces"
user_options = []
boolean_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
'''Create ctypes API to local FUSE headers'''
# Import ctypeslib
basedir = os.path.abspath(os.path.dirname(sys.argv[0]))
sys.path.insert(0, os.path.join(basedir, 'ctypeslib.zip'))
from ctypeslib import h2xml, xml2py
from ctypeslib.codegen import codegenerator as ctypeslib
print('Creating ctypes API from local fuse headers...')
cflags = self.get_cflags()
print('Using cflags: %s' % ' '.join(cflags))
fuse_path = 'fuse'
if not ctypes.util.find_library(fuse_path):
print('Could not find fuse library', file=sys.stderr)
sys.exit(1)
# Create temporary XML file
tmp_fh = tempfile.NamedTemporaryFile()
tmp_name = tmp_fh.name
print('Calling h2xml...')
argv = [ 'h2xml.py', '-o', tmp_name, '-c', '-q', '-I', basedir, 'fuse_ctypes.h' ]
argv += cflags
ctypeslib.ASSUME_STRINGS = False
ctypeslib.CDLL_SET_ERRNO = False
ctypeslib.PREFIX = ('# Code autogenerated by ctypeslib. Any changes will be lost!\n\n'
'#pylint: disable-all\n'
'#@PydevCodeAnalysisIgnore\n\n')
h2xml.main(argv)
print('Calling xml2py...')
api_file = os.path.join(basedir, 'llfuse', 'ctypes_api.py')
argv = [ 'xml2py.py', tmp_name, '-o', api_file, '-l', fuse_path ]
for el in fuse_export_regex:
argv.append('-r')
argv.append(el)
for el in fuse_export_symbols:
argv.append('-s')
argv.append(el)
xml2py.main(argv)
# Delete temporary XML file
tmp_fh.close()
print('Code generation complete.')
def get_cflags(self):
'''Get cflags required to compile with fuse library'''
proc = subprocess.Popen(['pkg-config', 'fuse', '--cflags'], stdout=subprocess.PIPE)
cflags = proc.stdout.readline().rstrip()
proc.stdout.close()
if proc.wait() != 0:
sys.stderr.write('Failed to execute pkg-config. Exit code: %d.\n'
% proc.returncode)
sys.stderr.write('Check that the FUSE development package been installed properly.\n')
sys.exit(1)
return cflags.split()
# Add as subcommand of build
distutils.command.build.build.sub_commands.insert(0, ('build_ctypes', None))
setup(name='llfuse_example',
version='1.0',
author='Nikolaus Rath',
author_email='Nikolaus@rath.org',
url='http://code.google.com/p/fusepy/',
packages=[ 'llfuse' ],
provides=['llfuse'],
cmdclass={ 'build_ctypes': build_ctypes}
)

123
lib/python/fusepy/memory.py Executable file
View File

@ -0,0 +1,123 @@
#!/usr/bin/env python
from collections import defaultdict
from errno import ENOENT
from stat import S_IFDIR, S_IFLNK, S_IFREG
from sys import argv, exit
from time import time
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn
class Memory(LoggingMixIn, Operations):
"""Example memory filesystem. Supports only one level of files."""
def __init__(self):
self.files = {}
self.data = defaultdict(str)
self.fd = 0
now = time()
self.files['/'] = dict(st_mode=(S_IFDIR | 0755), st_ctime=now,
st_mtime=now, st_atime=now, st_nlink=2)
def chmod(self, path, mode):
self.files[path]['st_mode'] &= 0770000
self.files[path]['st_mode'] |= mode
return 0
def chown(self, path, uid, gid):
self.files[path]['st_uid'] = uid
self.files[path]['st_gid'] = gid
def create(self, path, mode):
self.files[path] = dict(st_mode=(S_IFREG | mode), st_nlink=1,
st_size=0, st_ctime=time(), st_mtime=time(), st_atime=time())
self.fd += 1
return self.fd
def getattr(self, path, fh=None):
if path not in self.files:
raise FuseOSError(ENOENT)
st = self.files[path]
return st
def getxattr(self, path, name, position=0):
attrs = self.files[path].get('attrs', {})
try:
return attrs[name]
except KeyError:
return '' # Should return ENOATTR
def listxattr(self, path):
attrs = self.files[path].get('attrs', {})
return attrs.keys()
def mkdir(self, path, mode):
self.files[path] = dict(st_mode=(S_IFDIR | mode), st_nlink=2,
st_size=0, st_ctime=time(), st_mtime=time(), st_atime=time())
self.files['/']['st_nlink'] += 1
def open(self, path, flags):
self.fd += 1
return self.fd
def read(self, path, size, offset, fh):
return self.data[path][offset:offset + size]
def readdir(self, path, fh):
return ['.', '..'] + [x[1:] for x in self.files if x != '/']
def readlink(self, path):
return self.data[path]
def removexattr(self, path, name):
attrs = self.files[path].get('attrs', {})
try:
del attrs[name]
except KeyError:
pass # Should return ENOATTR
def rename(self, old, new):
self.files[new] = self.files.pop(old)
def rmdir(self, path):
self.files.pop(path)
self.files['/']['st_nlink'] -= 1
def setxattr(self, path, name, value, options, position=0):
# Ignore options
attrs = self.files[path].setdefault('attrs', {})
attrs[name] = value
def statfs(self, path):
return dict(f_bsize=512, f_blocks=4096, f_bavail=2048)
def symlink(self, target, source):
self.files[target] = dict(st_mode=(S_IFLNK | 0777), st_nlink=1,
st_size=len(source))
self.data[target] = source
def truncate(self, path, length, fh=None):
self.data[path] = self.data[path][:length]
self.files[path]['st_size'] = length
def unlink(self, path):
self.files.pop(path)
def utimens(self, path, times=None):
now = time()
atime, mtime = times if times else (now, now)
self.files[path]['st_atime'] = atime
self.files[path]['st_mtime'] = mtime
def write(self, path, data, offset, fh):
self.data[path] = self.data[path][:offset] + data
self.files[path]['st_size'] = len(self.data[path])
return len(data)
if __name__ == "__main__":
if len(argv) != 2:
print 'usage: %s <mountpoint>' % argv[0]
exit(1)
fuse = FUSE(Memory(), argv[1], foreground=True)

128
lib/python/fusepy/memory3.py Executable file
View File

@ -0,0 +1,128 @@
#!/usr/bin/env python
from fuse3 import FUSE, Operations, LoggingMixIn
from collections import defaultdict
from errno import ENOENT
from stat import S_IFDIR, S_IFLNK, S_IFREG
from sys import argv, exit
from time import time
import logging
class Memory(LoggingMixIn, Operations):
"""Example memory filesystem. Supports only one level of files."""
def __init__(self):
self.files = {}
self.data = defaultdict(bytearray)
self.fd = 0
now = time()
self.files['/'] = dict(st_mode=(S_IFDIR | 0o755), st_ctime=now,
st_mtime=now, st_atime=now, st_nlink=2)
def chmod(self, path, mode):
self.files[path]['st_mode'] &= 0o770000
self.files[path]['st_mode'] |= mode
return 0
def chown(self, path, uid, gid):
self.files[path]['st_uid'] = uid
self.files[path]['st_gid'] = gid
def create(self, path, mode):
self.files[path] = dict(st_mode=(S_IFREG | mode), st_nlink=1,
st_size=0, st_ctime=time(), st_mtime=time(), st_atime=time())
self.fd += 1
return self.fd
def getattr(self, path, fh=None):
if path not in self.files:
raise OSError(ENOENT, '')
st = self.files[path]
return st
def getxattr(self, path, name, position=0):
attrs = self.files[path].get('attrs', {})
try:
return attrs[name]
except KeyError:
return '' # Should return ENOATTR
def listxattr(self, path):
attrs = self.files[path].get('attrs', {})
return attrs.keys()
def mkdir(self, path, mode):
self.files[path] = dict(st_mode=(S_IFDIR | mode), st_nlink=2,
st_size=0, st_ctime=time(), st_mtime=time(), st_atime=time())
self.files['/']['st_nlink'] += 1
def open(self, path, flags):
self.fd += 1
return self.fd
def read(self, path, size, offset, fh):
return bytes(self.data[path][offset:offset + size])
def readdir(self, path, fh):
return ['.', '..'] + [x[1:] for x in self.files if x != '/']
def readlink(self, path):
return self.data[path].decode('utf-8')
def removexattr(self, path, name):
attrs = self.files[path].get('attrs', {})
try:
del attrs[name]
except KeyError:
pass # Should return ENOATTR
def rename(self, old, new):
self.files[new] = self.files.pop(old)
def rmdir(self, path):
self.files.pop(path)
self.files['/']['st_nlink'] -= 1
def setxattr(self, path, name, value, options, position=0):
# Ignore options
attrs = self.files[path].setdefault('attrs', {})
attrs[name] = value
def statfs(self, path):
return dict(f_bsize=512, f_blocks=4096, f_bavail=2048)
def symlink(self, target, source):
source = source.encode('utf-8')
self.files[target] = dict(st_mode=(S_IFLNK | 0o777), st_nlink=1,
st_size=len(source))
self.data[target] = bytearray(source)
def truncate(self, path, length, fh=None):
del self.data[path][length:]
self.files[path]['st_size'] = length
def unlink(self, path):
self.files.pop(path)
def utimens(self, path, times=None):
now = time()
atime, mtime = times if times else (now, now)
self.files[path]['st_atime'] = atime
self.files[path]['st_mtime'] = mtime
def write(self, path, data, offset, fh):
del self.data[path][offset:]
self.data[path].extend(data)
self.files[path]['st_size'] = len(self.data[path])
return len(data)
if __name__ == "__main__":
if len(argv) != 2:
print('usage: %s <mountpoint>' % argv[0])
exit(1)
logging.getLogger().setLevel(logging.DEBUG)
fuse = FUSE(Memory(), argv[1], foreground=True)

142
lib/python/fusepy/memoryll.py Executable file
View File

@ -0,0 +1,142 @@
#!/usr/bin/env python
from collections import defaultdict
from errno import ENOENT, EROFS
from stat import S_IFMT, S_IMODE, S_IFDIR, S_IFREG
from sys import argv, exit
from time import time
from fusell import FUSELL
class Memory(FUSELL):
def create_ino(self):
self.ino += 1
return self.ino
def init(self, userdata, conn):
self.ino = 1
self.attr = defaultdict(dict)
self.data = defaultdict(str)
self.parent = {}
self.children = defaultdict(dict)
self.attr[1] = {'st_ino': 1, 'st_mode': S_IFDIR | 0777, 'st_nlink': 2}
self.parent[1] = 1
forget = None
def getattr(self, req, ino, fi):
print 'getattr:', ino
attr = self.attr[ino]
if attr:
self.reply_attr(req, attr, 1.0)
else:
self.reply_err(req, ENOENT)
def lookup(self, req, parent, name):
print 'lookup:', parent, name
children = self.children[parent]
ino = children.get(name, 0)
attr = self.attr[ino]
if attr:
entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0}
self.reply_entry(req, entry)
else:
self.reply_err(req, ENOENT)
def mkdir(self, req, parent, name, mode):
print 'mkdir:', parent, name
ino = self.create_ino()
ctx = self.req_ctx(req)
now = time()
attr = {
'st_ino': ino,
'st_mode': S_IFDIR | mode,
'st_nlink': 2,
'st_uid': ctx['uid'],
'st_gid': ctx['gid'],
'st_atime': now,
'st_mtime': now,
'st_ctime': now}
self.attr[ino] = attr
self.attr[parent]['st_nlink'] += 1
self.parent[ino] = parent
self.children[parent][name] = ino
entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0}
self.reply_entry(req, entry)
def mknod(self, req, parent, name, mode, rdev):
print 'mknod:', parent, name
ino = self.create_ino()
ctx = self.req_ctx(req)
now = time()
attr = {
'st_ino': ino,
'st_mode': mode,
'st_nlink': 1,
'st_uid': ctx['uid'],
'st_gid': ctx['gid'],
'st_rdev': rdev,
'st_atime': now,
'st_mtime': now,
'st_ctime': now}
self.attr[ino] = attr
self.attr[parent]['st_nlink'] += 1
self.children[parent][name] = ino
entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0}
self.reply_entry(req, entry)
def open(self, req, ino, fi):
print 'open:', ino
self.reply_open(req, fi)
def read(self, req, ino, size, off, fi):
print 'read:', ino, size, off
buf = self.data[ino][off:(off + size)]
self.reply_buf(req, buf)
def readdir(self, req, ino, size, off, fi):
print 'readdir:', ino
parent = self.parent[ino]
entries = [('.', {'st_ino': ino, 'st_mode': S_IFDIR}),
('..', {'st_ino': parent, 'st_mode': S_IFDIR})]
for name, child in self.children[ino].items():
entries.append((name, self.attr[child]))
self.reply_readdir(req, size, off, entries)
def rename(self, req, parent, name, newparent, newname):
print 'rename:', parent, name, newparent, newname
ino = self.children[parent].pop(name)
self.children[newparent][newname] = ino
self.parent[ino] = newparent
self.reply_err(req, 0)
def setattr(self, req, ino, attr, to_set, fi):
print 'setattr:', ino, to_set
a = self.attr[ino]
for key in to_set:
if key == 'st_mode':
# Keep the old file type bit fields
a['st_mode'] = S_IFMT(a['st_mode']) | S_IMODE(attr['st_mode'])
else:
a[key] = attr[key]
self.attr[ino] = a
self.reply_attr(req, a, 1.0)
def write(self, req, ino, buf, off, fi):
print 'write:', ino, off, len(buf)
self.data[ino] = self.data[ino][:off] + buf
self.attr[ino]['st_size'] = len(self.data[ino])
self.reply_write(req, len(buf))
if __name__ == '__main__':
if len(argv) != 2:
print 'usage: %s <mountpoint>' % argv[0]
exit(1)
fuse = Memory(argv[1])

106
lib/python/fusepy/sftp.py Executable file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env python
from sys import argv, exit
from time import time
from paramiko import SSHClient
from fuse import FUSE, Operations
class SFTP(Operations):
"""A simple SFTP filesystem. Requires paramiko:
http://www.lag.net/paramiko/
You need to be able to login to remote host without entering a password.
"""
def __init__(self, host, path='.'):
self.client = SSHClient()
self.client.load_system_host_keys()
self.client.connect(host)
self.sftp = self.client.open_sftp()
self.root = path
def __del__(self):
self.sftp.close()
self.client.close()
def __call__(self, op, path, *args):
print '->', op, path, args[0] if args else ''
ret = '[Unhandled Exception]'
try:
ret = getattr(self, op)(self.root + path, *args)
return ret
except OSError, e:
ret = str(e)
raise
except IOError, e:
ret = str(e)
raise OSError(*e.args)
finally:
print '<-', op
def chmod(self, path, mode):
return self.sftp.chmod(path, mode)
def chown(self, path, uid, gid):
return self.sftp.chown(path, uid, gid)
def create(self, path, mode):
f = self.sftp.open(path, 'w')
f.chmod(mode)
f.close()
return 0
def getattr(self, path, fh=None):
st = self.sftp.lstat(path)
return dict((key, getattr(st, key)) for key in ('st_atime', 'st_gid',
'st_mode', 'st_mtime', 'st_size', 'st_uid'))
def mkdir(self, path, mode):
return self.sftp.mkdir(path, mode)
def read(self, path, size, offset, fh):
f = self.sftp.open(path)
f.seek(offset, 0)
buf = f.read(size)
f.close()
return buf
def readdir(self, path, fh):
return ['.', '..'] + [name.encode('utf-8') for name in self.sftp.listdir(path)]
def readlink(self, path):
return self.sftp.readlink(path)
def rename(self, old, new):
return self.sftp.rename(old, self.root + new)
def rmdir(self, path):
return self.sftp.rmdir(path)
def symlink(self, target, source):
return self.sftp.symlink(source, target)
def truncate(self, path, length, fh=None):
return self.sftp.truncate(path, length)
def unlink(self, path):
return self.sftp.unlink(path)
def utimens(self, path, times=None):
return self.sftp.utime(path, times)
def write(self, path, data, offset, fh):
f = self.sftp.open(path, 'r+')
f.seek(offset, 0)
f.write(data)
f.close()
return len(data)
if __name__ == "__main__":
if len(argv) != 3:
print 'usage: %s <host> <mountpoint>' % argv[0]
exit(1)
fuse = FUSE(SFTP(argv[1]), argv[2], foreground=True, nothreads=True)