4 def fuse_mount(mountpoint, opts=None):
5 if not isinstance(mountpoint, str):
7 if opts is not None and not isinstance(opts, str):
11 fuse = dl.open('libfuse.so')
13 fuse = dl.open('libfuse.so.2')
14 if fuse.sym('fuse_mount_compat22'):
15 fnname = 'fuse_mount_compat22'
17 fnname = 'fuse_mount' # older versions of libfuse.so
18 return fuse.call(fnname, mountpoint, opts)
20 class Handler(object):
22 mountpoint = fd = None
23 __in_header_size = fuse_in_header.calcsize()
24 __out_header_size = fuse_out_header.calcsize()
25 MAX_READ = FUSE_MAX_IN
27 def __init__(self, mountpoint, filesystem, logfile='STDERR', **opts1):
28 opts = getattr(filesystem, 'MOUNT_OPTIONS', {}).copy()
33 opts = ' '.join(['%s=%s' % item for item in opts])
36 fd = fuse_mount(mountpoint, opts)
38 raise IOError("mount failed")
40 if logfile == 'STDERR':
42 self.logfile = logfile
44 print >> self.logfile, '* mounted at', mountpoint
45 self.mountpoint = mountpoint
46 self.filesystem = filesystem
51 if self.fd is not None:
55 cmd = "fusermount -u '%s'" % self.mountpoint.replace("'", r"'\''")
56 self.mountpoint = None
58 print >> self.logfile, '*', cmd
63 def loop_forever(self):
66 msg = os.read(self.fd, FUSE_MAX_IN)
68 if ose.errno == errno.ENODEV:
69 # on hardy, at least, this is what happens upon fusermount -u
70 #raise EOFError("out-kernel connection closed")
73 #raise EOFError("out-kernel connection closed")
75 self.handle_message(msg)
77 def handle_message(self, msg):
78 headersize = self.__in_header_size
79 req = fuse_in_header(msg[:headersize])
80 assert req.len == len(msg)
84 name = fuse_opcode2name[req.opcode]
85 meth = getattr(self, name)
86 except (IndexError, AttributeError):
87 raise NotImplementedError
89 # print >> self.logfile, '%s(%d)' % (name, req.nodeid)
90 reply = meth(req, msg[headersize:])
92 # print >> self.logfile, ' >>', repr(reply)
93 except NotImplementedError:
95 print >> self.logfile, '%s: not implemented' % (name,)
96 self.send_reply(req, err=errno.ENOSYS)
97 except EnvironmentError, e:
99 print >> self.logfile, '%s: %s' % (name, e)
100 self.send_reply(req, err = e.errno or errno.ESTALE)
104 self.send_reply(req, reply)
106 def send_reply(self, req, reply=None, err=0):
107 assert 0 <= err < 1000
110 elif not isinstance(reply, str):
112 f = fuse_out_header(unique = req.unique,
114 len = self.__out_header_size + len(reply))
115 data = f.pack() + reply
117 count = os.write(self.fd, data)
119 raise EOFError("in-kernel connection closed")
122 def notsupp_or_ro(self):
123 if hasattr(self.filesystem, "modified"):
124 raise IOError(errno.ENOSYS, "not supported")
126 raise IOError(errno.EROFS, "read-only file system")
128 # ____________________________________________________________
130 def FUSE_INIT(self, req, msg):
131 msg = fuse_init_in_out(msg[:8])
133 print >> self.logfile, 'INIT: %d.%d' % (msg.major, msg.minor)
134 return fuse_init_in_out(major = FUSE_KERNEL_VERSION,
135 minor = FUSE_KERNEL_MINOR_VERSION)
137 def FUSE_GETATTR(self, req, msg):
138 node = self.filesystem.getnode(req.nodeid)
139 attr, valid = self.filesystem.getattr(node)
140 return fuse_attr_out(attr_valid = valid,
143 def FUSE_SETATTR(self, req, msg):
144 if not hasattr(self.filesystem, 'setattr'):
146 msg = fuse_setattr_in(msg)
147 if msg.valid & FATTR_MODE: mode = msg.attr.mode & 0777
149 if msg.valid & FATTR_UID: uid = msg.attr.uid
151 if msg.valid & FATTR_GID: gid = msg.attr.gid
153 if msg.valid & FATTR_SIZE: size = msg.attr.size
155 if msg.valid & FATTR_ATIME: atime = msg.attr.atime
157 if msg.valid & FATTR_MTIME: mtime = msg.attr.mtime
159 node = self.filesystem.getnode(req.nodeid)
160 self.filesystem.setattr(node, mode, uid, gid,
162 attr, valid = self.filesystem.getattr(node)
163 return fuse_attr_out(attr_valid = valid,
166 def FUSE_RELEASE(self, req, msg):
167 msg = fuse_release_in(msg, truncate=True)
169 del self.handles[msg.fh]
171 raise IOError(errno.EBADF, msg.fh)
172 FUSE_RELEASEDIR = FUSE_RELEASE
174 def FUSE_OPENDIR(self, req, msg):
175 #msg = fuse_open_in(msg)
176 node = self.filesystem.getnode(req.nodeid)
177 attr, valid = self.filesystem.getattr(node)
178 if mode2type(attr.mode) != TYPE_DIR:
179 raise IOError(errno.ENOTDIR, node)
182 self.handles[fh] = True, '', node
183 return fuse_open_out(fh = fh)
185 def FUSE_READDIR(self, req, msg):
186 msg = fuse_read_in(msg)
188 isdir, data, node = self.handles[msg.fh]
190 raise KeyError # not a dir handle
192 raise IOError(errno.EBADF, msg.fh)
197 for name, type in self.filesystem.listdir(node):
198 off += fuse_dirent.calcsize(len(name))
199 d_entry = fuse_dirent(ino = INVALID_INO,
203 d_entries.append(d_entry)
204 data = ''.join([d.pack() for d in d_entries])
205 self.handles[msg.fh] = True, data, node
206 return data[msg.offset:msg.offset+msg.size]
208 def replyentry(self, (subnodeid, valid1)):
209 subnode = self.filesystem.getnode(subnodeid)
210 attr, valid2 = self.filesystem.getattr(subnode)
211 return fuse_entry_out(nodeid = subnodeid,
212 entry_valid = valid1,
216 def FUSE_LOOKUP(self, req, msg):
217 filename = c2pystr(msg)
218 dirnode = self.filesystem.getnode(req.nodeid)
219 return self.replyentry(self.filesystem.lookup(dirnode, filename))
221 def FUSE_OPEN(self, req, msg, mask=os.O_RDONLY|os.O_WRONLY|os.O_RDWR):
222 msg = fuse_open_in(msg)
223 node = self.filesystem.getnode(req.nodeid)
224 attr, valid = self.filesystem.getattr(node)
225 if mode2type(attr.mode) != TYPE_REG:
226 raise IOError(errno.EPERM, node)
227 f = self.filesystem.open(node, msg.flags & mask)
228 if isinstance(f, tuple):
234 self.handles[fh] = False, f, node
235 return fuse_open_out(fh = fh, open_flags = open_flags)
237 def FUSE_READ(self, req, msg):
238 msg = fuse_read_in(msg)
240 isdir, f, node = self.handles[msg.fh]
244 raise IOError(errno.EBADF, msg.fh)
246 return f.read(msg.size)
248 def FUSE_WRITE(self, req, msg):
249 if not hasattr(self.filesystem, 'modified'):
250 raise IOError(errno.EROFS, "read-only file system")
251 msg, data = fuse_write_in.from_head(msg)
253 isdir, f, node = self.handles[msg.fh]
257 raise IOError(errno.EBADF, msg.fh)
260 self.filesystem.modified(node)
261 return fuse_write_out(size = len(data))
263 def FUSE_MKNOD(self, req, msg):
264 if not hasattr(self.filesystem, 'mknod'):
266 msg, filename = fuse_mknod_in.from_param(msg)
267 node = self.filesystem.getnode(req.nodeid)
268 return self.replyentry(self.filesystem.mknod(node, filename, msg.mode))
270 def FUSE_MKDIR(self, req, msg):
271 if not hasattr(self.filesystem, 'mkdir'):
273 msg, filename = fuse_mkdir_in.from_param(msg)
274 node = self.filesystem.getnode(req.nodeid)
275 return self.replyentry(self.filesystem.mkdir(node, filename, msg.mode))
277 def FUSE_SYMLINK(self, req, msg):
278 if not hasattr(self.filesystem, 'symlink'):
280 linkname, target = c2pystr2(msg)
281 node = self.filesystem.getnode(req.nodeid)
282 return self.replyentry(self.filesystem.symlink(node, linkname, target))
284 #def FUSE_LINK(self, req, msg):
287 def FUSE_UNLINK(self, req, msg):
288 if not hasattr(self.filesystem, 'unlink'):
290 filename = c2pystr(msg)
291 node = self.filesystem.getnode(req.nodeid)
292 self.filesystem.unlink(node, filename)
294 def FUSE_RMDIR(self, req, msg):
295 if not hasattr(self.filesystem, 'rmdir'):
297 dirname = c2pystr(msg)
298 node = self.filesystem.getnode(req.nodeid)
299 self.filesystem.rmdir(node, dirname)
301 def FUSE_FORGET(self, req, msg):
302 if hasattr(self.filesystem, 'forget'):
303 self.filesystem.forget(req.nodeid)
306 def FUSE_READLINK(self, req, msg):
307 if not hasattr(self.filesystem, 'readlink'):
308 raise IOError(errno.ENOSYS, "readlink not supported")
309 node = self.filesystem.getnode(req.nodeid)
310 target = self.filesystem.readlink(node)
313 def FUSE_RENAME(self, req, msg):
314 if not hasattr(self.filesystem, 'rename'):
316 msg, oldname, newname = fuse_rename_in.from_param2(msg)
317 oldnode = self.filesystem.getnode(req.nodeid)
318 newnode = self.filesystem.getnode(msg.newdir)
319 self.filesystem.rename(oldnode, oldname, newnode, newname)
321 def getxattrs(self, nodeid):
322 if not hasattr(self.filesystem, 'getxattrs'):
323 raise IOError(errno.ENOSYS, "xattrs not supported")
324 node = self.filesystem.getnode(nodeid)
325 return self.filesystem.getxattrs(node)
327 def FUSE_LISTXATTR(self, req, msg):
328 names = self.getxattrs(req.nodeid).keys()
329 names = ['user.' + name for name in names]
332 totalsize += len(name)+1
333 msg = fuse_getxattr_in(msg)
335 if msg.size < totalsize:
336 raise IOError(errno.ERANGE, "buffer too small")
338 return '\x00'.join(names)
340 return fuse_getxattr_out(size=totalsize)
342 def FUSE_GETXATTR(self, req, msg):
343 xattrs = self.getxattrs(req.nodeid)
344 msg, name = fuse_getxattr_in.from_param(msg)
345 if not name.startswith('user.'): # ENODATA == ENOATTR
346 raise IOError(errno.ENODATA, "only supports 'user.' xattrs, "
352 raise IOError(errno.ENODATA, "no such xattr") # == ENOATTR
355 if msg.size < len(value):
356 raise IOError(errno.ERANGE, "buffer too small")
359 return fuse_getxattr_out(size=len(value))
361 def FUSE_SETXATTR(self, req, msg):
362 xattrs = self.getxattrs(req.nodeid)
363 msg, name, value = fuse_setxattr_in.from_param_head(msg)
364 assert len(value) == msg.size
365 # XXX msg.flags ignored
366 if not name.startswith('user.'): # ENODATA == ENOATTR
367 raise IOError(errno.ENODATA, "only supports 'user.' xattrs")
372 raise IOError(errno.ENODATA, "cannot set xattr") # == ENOATTR
374 def FUSE_REMOVEXATTR(self, req, msg):
375 xattrs = self.getxattrs(req.nodeid)
377 if not name.startswith('user.'): # ENODATA == ENOATTR
378 raise IOError(errno.ENODATA, "only supports 'user.' xattrs")
383 raise IOError(errno.ENODATA, "cannot delete xattr") # == ENOATTR
386 class NoReply(Exception):