2008-06-07 05:22:36 +00:00
|
|
|
from kernel import *
|
2008-09-24 18:07:38 +00:00
|
|
|
import os, errno, sys
|
2008-06-07 05:22:36 +00:00
|
|
|
|
|
|
|
def fuse_mount(mountpoint, opts=None):
|
|
|
|
if not isinstance(mountpoint, str):
|
|
|
|
raise TypeError
|
|
|
|
if opts is not None and not isinstance(opts, str):
|
|
|
|
raise TypeError
|
|
|
|
import dl
|
2008-09-24 18:07:38 +00:00
|
|
|
try:
|
|
|
|
fuse = dl.open('libfuse.so')
|
|
|
|
except dl.error:
|
|
|
|
fuse = dl.open('libfuse.so.2')
|
2008-06-07 05:22:36 +00:00
|
|
|
if fuse.sym('fuse_mount_compat22'):
|
|
|
|
fnname = 'fuse_mount_compat22'
|
|
|
|
else:
|
|
|
|
fnname = 'fuse_mount' # older versions of libfuse.so
|
|
|
|
return fuse.call(fnname, mountpoint, opts)
|
|
|
|
|
|
|
|
class Handler(object):
|
|
|
|
__system = os.system
|
|
|
|
mountpoint = fd = None
|
|
|
|
__in_header_size = fuse_in_header.calcsize()
|
|
|
|
__out_header_size = fuse_out_header.calcsize()
|
|
|
|
MAX_READ = FUSE_MAX_IN
|
|
|
|
|
|
|
|
def __init__(self, mountpoint, filesystem, logfile='STDERR', **opts1):
|
|
|
|
opts = getattr(filesystem, 'MOUNT_OPTIONS', {}).copy()
|
|
|
|
opts.update(opts1)
|
|
|
|
if opts:
|
|
|
|
opts = opts.items()
|
|
|
|
opts.sort()
|
|
|
|
opts = ' '.join(['%s=%s' % item for item in opts])
|
|
|
|
else:
|
|
|
|
opts = None
|
|
|
|
fd = fuse_mount(mountpoint, opts)
|
|
|
|
if fd < 0:
|
|
|
|
raise IOError("mount failed")
|
|
|
|
self.fd = fd
|
|
|
|
if logfile == 'STDERR':
|
|
|
|
logfile = sys.stderr
|
|
|
|
self.logfile = logfile
|
|
|
|
if self.logfile:
|
|
|
|
print >> self.logfile, '* mounted at', mountpoint
|
|
|
|
self.mountpoint = mountpoint
|
|
|
|
self.filesystem = filesystem
|
|
|
|
self.handles = {}
|
|
|
|
self.nexth = 1
|
|
|
|
|
|
|
|
def __del__(self):
|
|
|
|
if self.fd is not None:
|
|
|
|
os.close(self.fd)
|
|
|
|
self.fd = None
|
|
|
|
if self.mountpoint:
|
|
|
|
cmd = "fusermount -u '%s'" % self.mountpoint.replace("'", r"'\''")
|
|
|
|
self.mountpoint = None
|
|
|
|
if self.logfile:
|
|
|
|
print >> self.logfile, '*', cmd
|
|
|
|
self.__system(cmd)
|
|
|
|
|
|
|
|
close = __del__
|
|
|
|
|
|
|
|
def loop_forever(self):
|
|
|
|
while True:
|
2008-09-24 18:07:38 +00:00
|
|
|
try:
|
|
|
|
msg = os.read(self.fd, FUSE_MAX_IN)
|
|
|
|
except OSError, ose:
|
|
|
|
if ose.errno == errno.ENODEV:
|
|
|
|
# on hardy, at least, this is what happens upon fusermount -u
|
|
|
|
#raise EOFError("out-kernel connection closed")
|
|
|
|
return
|
2008-06-07 05:22:36 +00:00
|
|
|
if not msg:
|
2008-09-24 18:07:38 +00:00
|
|
|
#raise EOFError("out-kernel connection closed")
|
|
|
|
return
|
2008-06-07 05:22:36 +00:00
|
|
|
self.handle_message(msg)
|
|
|
|
|
|
|
|
def handle_message(self, msg):
|
|
|
|
headersize = self.__in_header_size
|
|
|
|
req = fuse_in_header(msg[:headersize])
|
|
|
|
assert req.len == len(msg)
|
|
|
|
name = req.opcode
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
name = fuse_opcode2name[req.opcode]
|
|
|
|
meth = getattr(self, name)
|
|
|
|
except (IndexError, AttributeError):
|
|
|
|
raise NotImplementedError
|
|
|
|
#if self.logfile:
|
|
|
|
# print >> self.logfile, '%s(%d)' % (name, req.nodeid)
|
|
|
|
reply = meth(req, msg[headersize:])
|
|
|
|
#if self.logfile:
|
|
|
|
# print >> self.logfile, ' >>', repr(reply)
|
|
|
|
except NotImplementedError:
|
|
|
|
if self.logfile:
|
|
|
|
print >> self.logfile, '%s: not implemented' % (name,)
|
|
|
|
self.send_reply(req, err=errno.ENOSYS)
|
|
|
|
except EnvironmentError, e:
|
|
|
|
if self.logfile:
|
|
|
|
print >> self.logfile, '%s: %s' % (name, e)
|
|
|
|
self.send_reply(req, err = e.errno or errno.ESTALE)
|
|
|
|
except NoReply:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
self.send_reply(req, reply)
|
|
|
|
|
|
|
|
def send_reply(self, req, reply=None, err=0):
|
|
|
|
assert 0 <= err < 1000
|
|
|
|
if reply is None:
|
|
|
|
reply = ''
|
|
|
|
elif not isinstance(reply, str):
|
|
|
|
reply = reply.pack()
|
|
|
|
f = fuse_out_header(unique = req.unique,
|
|
|
|
error = -err,
|
|
|
|
len = self.__out_header_size + len(reply))
|
|
|
|
data = f.pack() + reply
|
|
|
|
while data:
|
|
|
|
count = os.write(self.fd, data)
|
|
|
|
if not count:
|
|
|
|
raise EOFError("in-kernel connection closed")
|
|
|
|
data = data[count:]
|
|
|
|
|
|
|
|
def notsupp_or_ro(self):
|
|
|
|
if hasattr(self.filesystem, "modified"):
|
|
|
|
raise IOError(errno.ENOSYS, "not supported")
|
|
|
|
else:
|
|
|
|
raise IOError(errno.EROFS, "read-only file system")
|
|
|
|
|
|
|
|
# ____________________________________________________________
|
|
|
|
|
|
|
|
def FUSE_INIT(self, req, msg):
|
|
|
|
msg = fuse_init_in_out(msg[:8])
|
|
|
|
if self.logfile:
|
|
|
|
print >> self.logfile, 'INIT: %d.%d' % (msg.major, msg.minor)
|
|
|
|
return fuse_init_in_out(major = FUSE_KERNEL_VERSION,
|
|
|
|
minor = FUSE_KERNEL_MINOR_VERSION)
|
|
|
|
|
|
|
|
def FUSE_GETATTR(self, req, msg):
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
attr, valid = self.filesystem.getattr(node)
|
|
|
|
return fuse_attr_out(attr_valid = valid,
|
|
|
|
attr = attr)
|
|
|
|
|
|
|
|
def FUSE_SETATTR(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'setattr'):
|
|
|
|
self.notsupp_or_ro()
|
|
|
|
msg = fuse_setattr_in(msg)
|
|
|
|
if msg.valid & FATTR_MODE: mode = msg.attr.mode & 0777
|
|
|
|
else: mode = None
|
|
|
|
if msg.valid & FATTR_UID: uid = msg.attr.uid
|
|
|
|
else: uid = None
|
|
|
|
if msg.valid & FATTR_GID: gid = msg.attr.gid
|
|
|
|
else: gid = None
|
|
|
|
if msg.valid & FATTR_SIZE: size = msg.attr.size
|
|
|
|
else: size = None
|
|
|
|
if msg.valid & FATTR_ATIME: atime = msg.attr.atime
|
|
|
|
else: atime = None
|
|
|
|
if msg.valid & FATTR_MTIME: mtime = msg.attr.mtime
|
|
|
|
else: mtime = None
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
self.filesystem.setattr(node, mode, uid, gid,
|
|
|
|
size, atime, mtime)
|
|
|
|
attr, valid = self.filesystem.getattr(node)
|
|
|
|
return fuse_attr_out(attr_valid = valid,
|
|
|
|
attr = attr)
|
|
|
|
|
|
|
|
def FUSE_RELEASE(self, req, msg):
|
|
|
|
msg = fuse_release_in(msg, truncate=True)
|
|
|
|
try:
|
|
|
|
del self.handles[msg.fh]
|
|
|
|
except KeyError:
|
|
|
|
raise IOError(errno.EBADF, msg.fh)
|
|
|
|
FUSE_RELEASEDIR = FUSE_RELEASE
|
|
|
|
|
|
|
|
def FUSE_OPENDIR(self, req, msg):
|
|
|
|
#msg = fuse_open_in(msg)
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
attr, valid = self.filesystem.getattr(node)
|
|
|
|
if mode2type(attr.mode) != TYPE_DIR:
|
|
|
|
raise IOError(errno.ENOTDIR, node)
|
|
|
|
fh = self.nexth
|
|
|
|
self.nexth += 1
|
|
|
|
self.handles[fh] = True, '', node
|
|
|
|
return fuse_open_out(fh = fh)
|
|
|
|
|
|
|
|
def FUSE_READDIR(self, req, msg):
|
|
|
|
msg = fuse_read_in(msg)
|
|
|
|
try:
|
|
|
|
isdir, data, node = self.handles[msg.fh]
|
|
|
|
if not isdir:
|
|
|
|
raise KeyError # not a dir handle
|
|
|
|
except KeyError:
|
|
|
|
raise IOError(errno.EBADF, msg.fh)
|
|
|
|
if msg.offset == 0:
|
|
|
|
# start or rewind
|
|
|
|
d_entries = []
|
|
|
|
off = 0
|
|
|
|
for name, type in self.filesystem.listdir(node):
|
|
|
|
off += fuse_dirent.calcsize(len(name))
|
|
|
|
d_entry = fuse_dirent(ino = INVALID_INO,
|
|
|
|
off = off,
|
|
|
|
type = type,
|
|
|
|
name = name)
|
|
|
|
d_entries.append(d_entry)
|
|
|
|
data = ''.join([d.pack() for d in d_entries])
|
|
|
|
self.handles[msg.fh] = True, data, node
|
|
|
|
return data[msg.offset:msg.offset+msg.size]
|
|
|
|
|
|
|
|
def replyentry(self, (subnodeid, valid1)):
|
|
|
|
subnode = self.filesystem.getnode(subnodeid)
|
|
|
|
attr, valid2 = self.filesystem.getattr(subnode)
|
|
|
|
return fuse_entry_out(nodeid = subnodeid,
|
|
|
|
entry_valid = valid1,
|
|
|
|
attr_valid = valid2,
|
|
|
|
attr = attr)
|
|
|
|
|
|
|
|
def FUSE_LOOKUP(self, req, msg):
|
|
|
|
filename = c2pystr(msg)
|
|
|
|
dirnode = self.filesystem.getnode(req.nodeid)
|
|
|
|
return self.replyentry(self.filesystem.lookup(dirnode, filename))
|
|
|
|
|
|
|
|
def FUSE_OPEN(self, req, msg, mask=os.O_RDONLY|os.O_WRONLY|os.O_RDWR):
|
|
|
|
msg = fuse_open_in(msg)
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
attr, valid = self.filesystem.getattr(node)
|
|
|
|
if mode2type(attr.mode) != TYPE_REG:
|
|
|
|
raise IOError(errno.EPERM, node)
|
|
|
|
f = self.filesystem.open(node, msg.flags & mask)
|
|
|
|
if isinstance(f, tuple):
|
|
|
|
f, open_flags = f
|
|
|
|
else:
|
|
|
|
open_flags = 0
|
|
|
|
fh = self.nexth
|
|
|
|
self.nexth += 1
|
|
|
|
self.handles[fh] = False, f, node
|
|
|
|
return fuse_open_out(fh = fh, open_flags = open_flags)
|
|
|
|
|
|
|
|
def FUSE_READ(self, req, msg):
|
|
|
|
msg = fuse_read_in(msg)
|
|
|
|
try:
|
|
|
|
isdir, f, node = self.handles[msg.fh]
|
|
|
|
if isdir:
|
|
|
|
raise KeyError
|
|
|
|
except KeyError:
|
|
|
|
raise IOError(errno.EBADF, msg.fh)
|
|
|
|
f.seek(msg.offset)
|
|
|
|
return f.read(msg.size)
|
|
|
|
|
|
|
|
def FUSE_WRITE(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'modified'):
|
|
|
|
raise IOError(errno.EROFS, "read-only file system")
|
|
|
|
msg, data = fuse_write_in.from_head(msg)
|
|
|
|
try:
|
|
|
|
isdir, f, node = self.handles[msg.fh]
|
|
|
|
if isdir:
|
|
|
|
raise KeyError
|
|
|
|
except KeyError:
|
|
|
|
raise IOError(errno.EBADF, msg.fh)
|
|
|
|
f.seek(msg.offset)
|
|
|
|
f.write(data)
|
|
|
|
self.filesystem.modified(node)
|
|
|
|
return fuse_write_out(size = len(data))
|
|
|
|
|
|
|
|
def FUSE_MKNOD(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'mknod'):
|
|
|
|
self.notsupp_or_ro()
|
|
|
|
msg, filename = fuse_mknod_in.from_param(msg)
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
return self.replyentry(self.filesystem.mknod(node, filename, msg.mode))
|
|
|
|
|
|
|
|
def FUSE_MKDIR(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'mkdir'):
|
|
|
|
self.notsupp_or_ro()
|
|
|
|
msg, filename = fuse_mkdir_in.from_param(msg)
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
return self.replyentry(self.filesystem.mkdir(node, filename, msg.mode))
|
|
|
|
|
|
|
|
def FUSE_SYMLINK(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'symlink'):
|
|
|
|
self.notsupp_or_ro()
|
|
|
|
linkname, target = c2pystr2(msg)
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
return self.replyentry(self.filesystem.symlink(node, linkname, target))
|
|
|
|
|
|
|
|
#def FUSE_LINK(self, req, msg):
|
|
|
|
# ...
|
|
|
|
|
|
|
|
def FUSE_UNLINK(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'unlink'):
|
|
|
|
self.notsupp_or_ro()
|
|
|
|
filename = c2pystr(msg)
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
self.filesystem.unlink(node, filename)
|
|
|
|
|
|
|
|
def FUSE_RMDIR(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'rmdir'):
|
|
|
|
self.notsupp_or_ro()
|
|
|
|
dirname = c2pystr(msg)
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
self.filesystem.rmdir(node, dirname)
|
|
|
|
|
|
|
|
def FUSE_FORGET(self, req, msg):
|
|
|
|
if hasattr(self.filesystem, 'forget'):
|
|
|
|
self.filesystem.forget(req.nodeid)
|
|
|
|
raise NoReply
|
|
|
|
|
|
|
|
def FUSE_READLINK(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'readlink'):
|
|
|
|
raise IOError(errno.ENOSYS, "readlink not supported")
|
|
|
|
node = self.filesystem.getnode(req.nodeid)
|
|
|
|
target = self.filesystem.readlink(node)
|
|
|
|
return target
|
|
|
|
|
|
|
|
def FUSE_RENAME(self, req, msg):
|
|
|
|
if not hasattr(self.filesystem, 'rename'):
|
|
|
|
self.notsupp_or_ro()
|
|
|
|
msg, oldname, newname = fuse_rename_in.from_param2(msg)
|
|
|
|
oldnode = self.filesystem.getnode(req.nodeid)
|
|
|
|
newnode = self.filesystem.getnode(msg.newdir)
|
|
|
|
self.filesystem.rename(oldnode, oldname, newnode, newname)
|
|
|
|
|
|
|
|
def getxattrs(self, nodeid):
|
|
|
|
if not hasattr(self.filesystem, 'getxattrs'):
|
|
|
|
raise IOError(errno.ENOSYS, "xattrs not supported")
|
|
|
|
node = self.filesystem.getnode(nodeid)
|
|
|
|
return self.filesystem.getxattrs(node)
|
|
|
|
|
|
|
|
def FUSE_LISTXATTR(self, req, msg):
|
|
|
|
names = self.getxattrs(req.nodeid).keys()
|
|
|
|
names = ['user.' + name for name in names]
|
|
|
|
totalsize = 0
|
|
|
|
for name in names:
|
|
|
|
totalsize += len(name)+1
|
|
|
|
msg = fuse_getxattr_in(msg)
|
|
|
|
if msg.size > 0:
|
|
|
|
if msg.size < totalsize:
|
|
|
|
raise IOError(errno.ERANGE, "buffer too small")
|
|
|
|
names.append('')
|
|
|
|
return '\x00'.join(names)
|
|
|
|
else:
|
|
|
|
return fuse_getxattr_out(size=totalsize)
|
|
|
|
|
|
|
|
def FUSE_GETXATTR(self, req, msg):
|
|
|
|
xattrs = self.getxattrs(req.nodeid)
|
|
|
|
msg, name = fuse_getxattr_in.from_param(msg)
|
|
|
|
if not name.startswith('user.'): # ENODATA == ENOATTR
|
|
|
|
raise IOError(errno.ENODATA, "only supports 'user.' xattrs, "
|
|
|
|
"got %r" % (name,))
|
|
|
|
name = name[5:]
|
|
|
|
try:
|
|
|
|
value = xattrs[name]
|
|
|
|
except KeyError:
|
|
|
|
raise IOError(errno.ENODATA, "no such xattr") # == ENOATTR
|
|
|
|
value = str(value)
|
|
|
|
if msg.size > 0:
|
|
|
|
if msg.size < len(value):
|
|
|
|
raise IOError(errno.ERANGE, "buffer too small")
|
|
|
|
return value
|
|
|
|
else:
|
|
|
|
return fuse_getxattr_out(size=len(value))
|
|
|
|
|
|
|
|
def FUSE_SETXATTR(self, req, msg):
|
|
|
|
xattrs = self.getxattrs(req.nodeid)
|
|
|
|
msg, name, value = fuse_setxattr_in.from_param_head(msg)
|
|
|
|
assert len(value) == msg.size
|
|
|
|
# XXX msg.flags ignored
|
|
|
|
if not name.startswith('user.'): # ENODATA == ENOATTR
|
|
|
|
raise IOError(errno.ENODATA, "only supports 'user.' xattrs")
|
|
|
|
name = name[5:]
|
|
|
|
try:
|
|
|
|
xattrs[name] = value
|
|
|
|
except KeyError:
|
|
|
|
raise IOError(errno.ENODATA, "cannot set xattr") # == ENOATTR
|
|
|
|
|
|
|
|
def FUSE_REMOVEXATTR(self, req, msg):
|
|
|
|
xattrs = self.getxattrs(req.nodeid)
|
|
|
|
name = c2pystr(msg)
|
|
|
|
if not name.startswith('user.'): # ENODATA == ENOATTR
|
|
|
|
raise IOError(errno.ENODATA, "only supports 'user.' xattrs")
|
|
|
|
name = name[5:]
|
|
|
|
try:
|
|
|
|
del xattrs[name]
|
|
|
|
except KeyError:
|
|
|
|
raise IOError(errno.ENODATA, "cannot delete xattr") # == ENOATTR
|
|
|
|
|
|
|
|
|
|
|
|
class NoReply(Exception):
|
|
|
|
pass
|