mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-23 06:42:26 +00:00
97229238b0
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.
388 lines
14 KiB
Python
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
|