]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - contrib/fuse/impl_b/pyfuse/handler.py
46643e26d8cafea48a06592b86922e985f176510
[tahoe-lafs/tahoe-lafs.git] / contrib / fuse / impl_b / pyfuse / handler.py
1 from kernel import *
2 import os, errno, sys
3
4 def fuse_mount(mountpoint, opts=None):
5     if not isinstance(mountpoint, str):
6         raise TypeError
7     if opts is not None and not isinstance(opts, str):
8         raise TypeError
9     import dl
10     try:
11         fuse = dl.open('libfuse.so')
12     except dl.error:
13         fuse = dl.open('libfuse.so.2')
14     if fuse.sym('fuse_mount_compat22'):
15         fnname = 'fuse_mount_compat22'
16     else:
17         fnname = 'fuse_mount'     # older versions of libfuse.so
18     return fuse.call(fnname, mountpoint, opts)
19
20 class Handler(object):
21     __system = os.system
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
26
27     def __init__(self, mountpoint, filesystem, logfile='STDERR', **opts1):
28         opts = getattr(filesystem, 'MOUNT_OPTIONS', {}).copy()
29         opts.update(opts1)
30         if opts:
31             opts = opts.items()
32             opts.sort()
33             opts = ' '.join(['%s=%s' % item for item in opts])
34         else:
35             opts = None
36         fd = fuse_mount(mountpoint, opts)
37         if fd < 0:
38             raise IOError("mount failed")
39         self.fd = fd
40         if logfile == 'STDERR':
41             logfile = sys.stderr
42         self.logfile = logfile
43         if self.logfile:
44             print >> self.logfile, '* mounted at', mountpoint
45         self.mountpoint = mountpoint
46         self.filesystem = filesystem
47         self.handles = {}
48         self.nexth = 1
49
50     def __del__(self):
51         if self.fd is not None:
52             os.close(self.fd)
53             self.fd = None
54         if self.mountpoint:
55             cmd = "fusermount -u '%s'" % self.mountpoint.replace("'", r"'\''")
56             self.mountpoint = None
57             if self.logfile:
58                 print >> self.logfile, '*', cmd
59             self.__system(cmd)
60
61     close = __del__
62
63     def loop_forever(self):
64         while True:
65             try:
66                 msg = os.read(self.fd, FUSE_MAX_IN)
67             except OSError, ose:
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")
71                     return
72             if not msg:
73                 #raise EOFError("out-kernel connection closed")
74                 return
75             self.handle_message(msg)
76
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)
81         name = req.opcode
82         try:
83             try:
84                 name = fuse_opcode2name[req.opcode]
85                 meth = getattr(self, name)
86             except (IndexError, AttributeError):
87                 raise NotImplementedError
88             #if self.logfile:
89             #    print >> self.logfile, '%s(%d)' % (name, req.nodeid)
90             reply = meth(req, msg[headersize:])
91             #if self.logfile:
92             #    print >> self.logfile, '   >>', repr(reply)
93         except NotImplementedError:
94             if self.logfile:
95                 print >> self.logfile, '%s: not implemented' % (name,)
96             self.send_reply(req, err=errno.ENOSYS)
97         except EnvironmentError, e:
98             if self.logfile:
99                 print >> self.logfile, '%s: %s' % (name, e)
100             self.send_reply(req, err = e.errno or errno.ESTALE)
101         except NoReply:
102             pass
103         else:
104             self.send_reply(req, reply)
105
106     def send_reply(self, req, reply=None, err=0):
107         assert 0 <= err < 1000
108         if reply is None:
109             reply = ''
110         elif not isinstance(reply, str):
111             reply = reply.pack()
112         f = fuse_out_header(unique = req.unique,
113                             error  = -err,
114                             len    = self.__out_header_size + len(reply))
115         data = f.pack() + reply
116         while data:
117             count = os.write(self.fd, data)
118             if not count:
119                 raise EOFError("in-kernel connection closed")
120             data = data[count:]
121
122     def notsupp_or_ro(self):
123         if hasattr(self.filesystem, "modified"):
124             raise IOError(errno.ENOSYS, "not supported")
125         else:
126             raise IOError(errno.EROFS, "read-only file system")
127
128     # ____________________________________________________________
129
130     def FUSE_INIT(self, req, msg):
131         msg = fuse_init_in_out(msg[:8])
132         if self.logfile:
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)
136
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,
141                              attr = attr)
142
143     def FUSE_SETATTR(self, req, msg):
144         if not hasattr(self.filesystem, 'setattr'):
145             self.notsupp_or_ro()
146         msg = fuse_setattr_in(msg)
147         if msg.valid & FATTR_MODE:  mode = msg.attr.mode & 0777
148         else:                       mode = None
149         if msg.valid & FATTR_UID:   uid = msg.attr.uid
150         else:                       uid = None
151         if msg.valid & FATTR_GID:   gid = msg.attr.gid
152         else:                       gid = None
153         if msg.valid & FATTR_SIZE:  size = msg.attr.size
154         else:                       size = None
155         if msg.valid & FATTR_ATIME: atime = msg.attr.atime
156         else:                       atime = None
157         if msg.valid & FATTR_MTIME: mtime = msg.attr.mtime
158         else:                       mtime = None
159         node = self.filesystem.getnode(req.nodeid)
160         self.filesystem.setattr(node, mode, uid, gid,
161                                 size, atime, mtime)
162         attr, valid = self.filesystem.getattr(node)
163         return fuse_attr_out(attr_valid = valid,
164                              attr = attr)
165
166     def FUSE_RELEASE(self, req, msg):
167         msg = fuse_release_in(msg, truncate=True)
168         try:
169             del self.handles[msg.fh]
170         except KeyError:
171             raise IOError(errno.EBADF, msg.fh)
172     FUSE_RELEASEDIR = FUSE_RELEASE
173
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)
180         fh = self.nexth
181         self.nexth += 1
182         self.handles[fh] = True, '', node
183         return fuse_open_out(fh = fh)
184
185     def FUSE_READDIR(self, req, msg):
186         msg = fuse_read_in(msg)
187         try:
188             isdir, data, node = self.handles[msg.fh]
189             if not isdir:
190                 raise KeyError    # not a dir handle
191         except KeyError:
192             raise IOError(errno.EBADF, msg.fh)
193         if msg.offset == 0:
194             # start or rewind
195             d_entries = []
196             off = 0
197             for name, type in self.filesystem.listdir(node):
198                 off += fuse_dirent.calcsize(len(name))
199                 d_entry = fuse_dirent(ino  = INVALID_INO,
200                                       off  = off,
201                                       type = type,
202                                       name = name)
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]
207
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,
213                               attr_valid = valid2,
214                               attr = attr)
215
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))
220
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):
229             f, open_flags = f
230         else:
231             open_flags = 0
232         fh = self.nexth
233         self.nexth += 1
234         self.handles[fh] = False, f, node
235         return fuse_open_out(fh = fh, open_flags = open_flags)
236
237     def FUSE_READ(self, req, msg):
238         msg = fuse_read_in(msg)
239         try:
240             isdir, f, node = self.handles[msg.fh]
241             if isdir:
242                 raise KeyError
243         except KeyError:
244             raise IOError(errno.EBADF, msg.fh)
245         f.seek(msg.offset)
246         return f.read(msg.size)
247
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)
252         try:
253             isdir, f, node = self.handles[msg.fh]
254             if isdir:
255                 raise KeyError
256         except KeyError:
257             raise IOError(errno.EBADF, msg.fh)
258         f.seek(msg.offset)
259         f.write(data)
260         self.filesystem.modified(node)
261         return fuse_write_out(size = len(data))
262
263     def FUSE_MKNOD(self, req, msg):
264         if not hasattr(self.filesystem, 'mknod'):
265             self.notsupp_or_ro()
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))
269
270     def FUSE_MKDIR(self, req, msg):
271         if not hasattr(self.filesystem, 'mkdir'):
272             self.notsupp_or_ro()
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))
276
277     def FUSE_SYMLINK(self, req, msg):
278         if not hasattr(self.filesystem, 'symlink'):
279             self.notsupp_or_ro()
280         linkname, target = c2pystr2(msg)
281         node = self.filesystem.getnode(req.nodeid)
282         return self.replyentry(self.filesystem.symlink(node, linkname, target))
283
284     #def FUSE_LINK(self, req, msg):
285     #    ...
286
287     def FUSE_UNLINK(self, req, msg):
288         if not hasattr(self.filesystem, 'unlink'):
289             self.notsupp_or_ro()
290         filename = c2pystr(msg)
291         node = self.filesystem.getnode(req.nodeid)
292         self.filesystem.unlink(node, filename)
293
294     def FUSE_RMDIR(self, req, msg):
295         if not hasattr(self.filesystem, 'rmdir'):
296             self.notsupp_or_ro()
297         dirname = c2pystr(msg)
298         node = self.filesystem.getnode(req.nodeid)
299         self.filesystem.rmdir(node, dirname)
300
301     def FUSE_FORGET(self, req, msg):
302         if hasattr(self.filesystem, 'forget'):
303             self.filesystem.forget(req.nodeid)
304         raise NoReply
305
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)
311         return target
312
313     def FUSE_RENAME(self, req, msg):
314         if not hasattr(self.filesystem, 'rename'):
315             self.notsupp_or_ro()
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)
320
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)
326
327     def FUSE_LISTXATTR(self, req, msg):
328         names = self.getxattrs(req.nodeid).keys()
329         names = ['user.' + name for name in names]
330         totalsize = 0
331         for name in names:
332             totalsize += len(name)+1
333         msg = fuse_getxattr_in(msg)
334         if msg.size > 0:
335             if msg.size < totalsize:
336                 raise IOError(errno.ERANGE, "buffer too small")
337             names.append('')
338             return '\x00'.join(names)
339         else:
340             return fuse_getxattr_out(size=totalsize)
341
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, "
347                                          "got %r" % (name,))
348         name = name[5:]
349         try:
350             value = xattrs[name]
351         except KeyError:
352             raise IOError(errno.ENODATA, "no such xattr")    # == ENOATTR
353         value = str(value)
354         if msg.size > 0:
355             if msg.size < len(value):
356                 raise IOError(errno.ERANGE, "buffer too small")
357             return value
358         else:
359             return fuse_getxattr_out(size=len(value))
360
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")
368         name = name[5:]
369         try:
370             xattrs[name] = value
371         except KeyError:
372             raise IOError(errno.ENODATA, "cannot set xattr")    # == ENOATTR
373
374     def FUSE_REMOVEXATTR(self, req, msg):
375         xattrs = self.getxattrs(req.nodeid)
376         name = c2pystr(msg)
377         if not name.startswith('user.'):    # ENODATA == ENOATTR
378             raise IOError(errno.ENODATA, "only supports 'user.' xattrs")
379         name = name[5:]
380         try:
381             del xattrs[name]
382         except KeyError:
383             raise IOError(errno.ENODATA, "cannot delete xattr")   # == ENOATTR
384
385
386 class NoReply(Exception):
387     pass