tahoe-lafs/contrib/fuse/impl_b/pyfuse/handler.py
robk-tahoe 97229238b0 fuse/impl_b: tweaks from testing on hardy
from testing on linux (specifically ubuntu hardy) the libfuse dll has a
different name, specifically libfuse.so.2. this patch tries libfuse.so
and then falls back to trying .2 if the former fails.

it also changes the unmount behaviour, to simply return from the handler's
loop_forever() loop upon being unmounted, rather than raising an EOFError,
since none of the client code I looked at actually handled that exception,
but did seem to expect to fall off of main() when loop_forever() returned.
Additionally, from my testing unmount typically led to an OSError from the
fuse fd read, rather than an empty read, as the code seemed to expect.

also removed a spurious import pyflakes quibbled about.
2008-09-24 11:07:38 -07:00

388 lines
14 KiB
Python

from kernel import *
import os, errno, sys
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
try:
fuse = dl.open('libfuse.so')
except dl.error:
fuse = dl.open('libfuse.so.2')
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:
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
if not msg:
#raise EOFError("out-kernel connection closed")
return
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