mirror of https://github.com/perkeep/perkeep.git
adds fusepy library
This commit is contained in:
parent
43d058ae77
commit
87be928fa9
|
@ -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 *
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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))
|
|
@ -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)
|
|
@ -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)
|
|
@ -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>
|
|
@ -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>
|
|
@ -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.
|
@ -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>
|
||||
|
|
@ -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 *
|
||||
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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}
|
||||
)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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])
|
|
@ -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)
|
Loading…
Reference in New Issue