Split pithos components in separate packages
[pithos] / snf-pithos-lib / pithos / lib / fuse.py
1 # Copyright (c) 2008 Giorgos Verigakis <verigak@gmail.com>
2
3 # Permission to use, copy, modify, and distribute this software for any
4 # purpose with or without fee is hereby granted, provided that the above
5 # copyright notice and this permission notice appear in all copies.
6
7 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15 from __future__ import division
16
17 from ctypes import *
18 from ctypes.util import find_library
19 from errno import *
20 from functools import partial
21 from os import strerror
22 from platform import machine, system
23 from stat import S_IFDIR
24 from traceback import print_exc
25
26
27 class c_timespec(Structure):
28     _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)]
29
30 class c_utimbuf(Structure):
31     _fields_ = [('actime', c_timespec), ('modtime', c_timespec)]
32
33 class c_stat(Structure):
34     pass    # Platform dependent
35
36 _system = system()
37 if _system in ('Darwin', 'FreeBSD'):
38     _libiconv = CDLL(find_library("iconv"), RTLD_GLOBAL)     # libfuse dependency
39     ENOTSUP = 45
40     c_dev_t = c_int32
41     c_fsblkcnt_t = c_ulong
42     c_fsfilcnt_t = c_ulong
43     c_gid_t = c_uint32
44     c_mode_t = c_uint16
45     c_off_t = c_int64
46     c_pid_t = c_int32
47     c_uid_t = c_uint32
48     setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
49         c_size_t, c_int, c_uint32)
50     getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
51         c_size_t, c_uint32)
52     c_stat._fields_ = [
53         ('st_dev', c_dev_t),
54         ('st_ino', c_uint32),
55         ('st_mode', c_mode_t),
56         ('st_nlink', c_uint16),
57         ('st_uid', c_uid_t),
58         ('st_gid', c_gid_t),
59         ('st_rdev', c_dev_t),
60         ('st_atimespec', c_timespec),
61         ('st_mtimespec', c_timespec),
62         ('st_ctimespec', c_timespec),
63         ('st_size', c_off_t),
64         ('st_blocks', c_int64),
65         ('st_blksize', c_int32)]
66 elif _system == 'Linux':
67     ENOTSUP = 95
68     c_dev_t = c_ulonglong
69     c_fsblkcnt_t = c_ulonglong
70     c_fsfilcnt_t = c_ulonglong
71     c_gid_t = c_uint
72     c_mode_t = c_uint
73     c_off_t = c_longlong
74     c_pid_t = c_int
75     c_uid_t = c_uint
76     setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t, c_int)
77     getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t)
78     
79     _machine = machine()
80     if _machine == 'x86_64':
81         c_stat._fields_ = [
82             ('st_dev', c_dev_t),
83             ('st_ino', c_ulong),
84             ('st_nlink', c_ulong),
85             ('st_mode', c_mode_t),
86             ('st_uid', c_uid_t),
87             ('st_gid', c_gid_t),
88             ('__pad0', c_int),
89             ('st_rdev', c_dev_t),
90             ('st_size', c_off_t),
91             ('st_blksize', c_long),
92             ('st_blocks', c_long),
93             ('st_atimespec', c_timespec),
94             ('st_mtimespec', c_timespec),
95             ('st_ctimespec', c_timespec)]
96     elif _machine == 'ppc':
97         c_stat._fields_ = [
98             ('st_dev', c_dev_t),
99             ('st_ino', c_ulonglong),
100             ('st_mode', c_mode_t),
101             ('st_nlink', c_uint),
102             ('st_uid', c_uid_t),
103             ('st_gid', c_gid_t),
104             ('st_rdev', c_dev_t),
105             ('__pad2', c_ushort),
106             ('st_size', c_off_t),
107             ('st_blksize', c_long),
108             ('st_blocks', c_longlong),
109             ('st_atimespec', c_timespec),
110             ('st_mtimespec', c_timespec),
111             ('st_ctimespec', c_timespec)]
112     else:
113         # i686, use as fallback for everything else
114         c_stat._fields_ = [
115             ('st_dev', c_dev_t),
116             ('__pad1', c_ushort),
117             ('__st_ino', c_ulong),
118             ('st_mode', c_mode_t),
119             ('st_nlink', c_uint),
120             ('st_uid', c_uid_t),
121             ('st_gid', c_gid_t),
122             ('st_rdev', c_dev_t),
123             ('__pad2', c_ushort),
124             ('st_size', c_off_t),
125             ('st_blksize', c_long),
126             ('st_blocks', c_longlong),
127             ('st_atimespec', c_timespec),
128             ('st_mtimespec', c_timespec),
129             ('st_ctimespec', c_timespec),
130             ('st_ino', c_ulonglong)]
131 else:
132     raise NotImplementedError('%s is not supported.' % _system)
133
134
135 class c_statvfs(Structure):
136     _fields_ = [
137         ('f_bsize', c_ulong),
138         ('f_frsize', c_ulong),
139         ('f_blocks', c_fsblkcnt_t),
140         ('f_bfree', c_fsblkcnt_t),
141         ('f_bavail', c_fsblkcnt_t),
142         ('f_files', c_fsfilcnt_t),
143         ('f_ffree', c_fsfilcnt_t),
144         ('f_favail', c_fsfilcnt_t)]
145
146 if _system == 'FreeBSD':
147     c_fsblkcnt_t = c_uint64
148     c_fsfilcnt_t = c_uint64
149     setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t, c_int)
150     getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t)
151     class c_statvfs(Structure):
152         _fields_ = [
153             ('f_bavail', c_fsblkcnt_t),
154             ('f_bfree', c_fsblkcnt_t),
155             ('f_blocks', c_fsblkcnt_t),
156             ('f_favail', c_fsfilcnt_t),
157             ('f_ffree', c_fsfilcnt_t),
158             ('f_files', c_fsfilcnt_t),
159             ('f_bsize', c_ulong),
160             ('f_flag', c_ulong),
161             ('f_frsize', c_ulong)]
162
163 class fuse_file_info(Structure):
164     _fields_ = [
165         ('flags', c_int),
166         ('fh_old', c_ulong),
167         ('writepage', c_int),
168         ('direct_io', c_uint, 1),
169         ('keep_cache', c_uint, 1),
170         ('flush', c_uint, 1),
171         ('padding', c_uint, 29),
172         ('fh', c_uint64),
173         ('lock_owner', c_uint64)]
174
175 class fuse_context(Structure):
176     _fields_ = [
177         ('fuse', c_voidp),
178         ('uid', c_uid_t),
179         ('gid', c_gid_t),
180         ('pid', c_pid_t),
181         ('private_data', c_voidp)]
182
183 class fuse_operations(Structure):
184     _fields_ = [
185         ('getattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat))),
186         ('readlink', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)),
187         ('getdir', c_voidp),    # Deprecated, use readdir
188         ('mknod', CFUNCTYPE(c_int, c_char_p, c_mode_t, c_dev_t)),
189         ('mkdir', CFUNCTYPE(c_int, c_char_p, c_mode_t)),
190         ('unlink', CFUNCTYPE(c_int, c_char_p)),
191         ('rmdir', CFUNCTYPE(c_int, c_char_p)),
192         ('symlink', CFUNCTYPE(c_int, c_char_p, c_char_p)),
193         ('rename', CFUNCTYPE(c_int, c_char_p, c_char_p)),
194         ('link', CFUNCTYPE(c_int, c_char_p, c_char_p)),
195         ('chmod', CFUNCTYPE(c_int, c_char_p, c_mode_t)),
196         ('chown', CFUNCTYPE(c_int, c_char_p, c_uid_t, c_gid_t)),
197         ('truncate', CFUNCTYPE(c_int, c_char_p, c_off_t)),
198         ('utime', c_voidp),     # Deprecated, use utimens
199         ('open', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
200         ('read', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, c_off_t,
201             POINTER(fuse_file_info))),
202         ('write', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, c_off_t,
203             POINTER(fuse_file_info))),
204         ('statfs', CFUNCTYPE(c_int, c_char_p, POINTER(c_statvfs))),
205         ('flush', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
206         ('release', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
207         ('fsync', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))),
208         ('setxattr', setxattr_t),
209         ('getxattr', getxattr_t),
210         ('listxattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)),
211         ('removexattr', CFUNCTYPE(c_int, c_char_p, c_char_p)),
212         ('opendir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
213         ('readdir', CFUNCTYPE(c_int, c_char_p, c_voidp, CFUNCTYPE(c_int, c_voidp,
214             c_char_p, POINTER(c_stat), c_off_t), c_off_t, POINTER(fuse_file_info))),
215         ('releasedir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
216         ('fsyncdir', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))),
217         ('init', CFUNCTYPE(c_voidp, c_voidp)),
218         ('destroy', CFUNCTYPE(c_voidp, c_voidp)),
219         ('access', CFUNCTYPE(c_int, c_char_p, c_int)),
220         ('create', CFUNCTYPE(c_int, c_char_p, c_mode_t, POINTER(fuse_file_info))),
221         ('ftruncate', CFUNCTYPE(c_int, c_char_p, c_off_t, POINTER(fuse_file_info))),
222         ('fgetattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat),
223             POINTER(fuse_file_info))),
224         ('lock', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info), c_int, c_voidp)),
225         ('utimens', CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf))),
226         ('bmap', CFUNCTYPE(c_int, c_char_p, c_size_t, POINTER(c_ulonglong)))]
227
228
229 def time_of_timespec(ts):
230     return ts.tv_sec + ts.tv_nsec / 10 ** 9
231
232 def set_st_attrs(st, attrs):
233     for key, val in attrs.items():
234         if key in ('st_atime', 'st_mtime', 'st_ctime'):
235             timespec = getattr(st, key + 'spec')
236             timespec.tv_sec = int(val)
237             timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9)
238         elif hasattr(st, key):
239             setattr(st, key, val)
240
241
242 _libfuse_path = find_library('fuse')
243 if not _libfuse_path:
244     raise EnvironmentError('Unable to find libfuse')
245 _libfuse = CDLL(_libfuse_path)
246 _libfuse.fuse_get_context.restype = POINTER(fuse_context)
247
248
249 def fuse_get_context():
250     """Returns a (uid, gid, pid) tuple"""
251     ctxp = _libfuse.fuse_get_context()
252     ctx = ctxp.contents
253     return ctx.uid, ctx.gid, ctx.pid
254
255
256 class FuseOSError(OSError):
257     def __init__(self, errno):
258         super(FuseOSError, self).__init__(errno, strerror(errno))
259
260
261 class FUSE(object):
262     """This class is the lower level interface and should not be subclassed
263        under normal use. Its methods are called by fuse.
264        Assumes API version 2.6 or later."""
265     
266     def __init__(self, operations, mountpoint, raw_fi=False, **kwargs):
267         """Setting raw_fi to True will cause FUSE to pass the fuse_file_info
268            class as is to Operations, instead of just the fh field.
269            This gives you access to direct_io, keep_cache, etc."""
270         
271         self.operations = operations
272         self.raw_fi = raw_fi
273         args = ['fuse']
274         if kwargs.pop('foreground', False):
275             args.append('-f')
276         if kwargs.pop('debug', False):
277             args.append('-d')
278         if kwargs.pop('nothreads', False):
279             args.append('-s')
280         kwargs.setdefault('fsname', operations.__class__.__name__)
281         args.append('-o')
282         args.append(','.join(key if val == True else '%s=%s' % (key, val)
283             for key, val in kwargs.items()))
284         args.append(mountpoint)
285         argv = (c_char_p * len(args))(*args)
286         
287         fuse_ops = fuse_operations()
288         for name, prototype in fuse_operations._fields_:
289             if prototype != c_voidp and getattr(operations, name, None):
290                 op = partial(self._wrapper_, getattr(self, name))
291                 setattr(fuse_ops, name, prototype(op))
292         err = _libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops),
293             sizeof(fuse_ops), None)            
294         del self.operations     # Invoke the destructor
295         if err:
296             raise RuntimeError(err)
297     
298     def _wrapper_(self, func, *args, **kwargs):
299         """Decorator for the methods that follow"""
300         try:
301             return func(*args, **kwargs) or 0
302         except OSError, e:
303             return -(e.errno or EFAULT)
304         except:
305             print_exc()
306             return -EFAULT
307     
308     def getattr(self, path, buf):
309         return self.fgetattr(path, buf, None)
310     
311     def readlink(self, path, buf, bufsize):
312         ret = self.operations('readlink', path)
313         data = create_string_buffer(ret[:bufsize - 1])
314         memmove(buf, data, len(data))
315         return 0
316     
317     def mknod(self, path, mode, dev):
318         return self.operations('mknod', path, mode, dev)
319     
320     def mkdir(self, path, mode):
321         return self.operations('mkdir', path, mode)
322     
323     def unlink(self, path):
324         return self.operations('unlink', path)
325     
326     def rmdir(self, path):
327         return self.operations('rmdir', path)
328     
329     def symlink(self, source, target):
330         return self.operations('symlink', target, source)
331     
332     def rename(self, old, new):
333         return self.operations('rename', old, new)
334     
335     def link(self, source, target):
336         return self.operations('link', target, source)
337     
338     def chmod(self, path, mode):
339         return self.operations('chmod', path, mode)
340     
341     def chown(self, path, uid, gid):
342         # Check if any of the arguments is a -1 that has overflowed
343         if c_uid_t(uid + 1).value == 0:
344             uid = -1
345         if c_gid_t(gid + 1).value == 0:
346             gid = -1
347         return self.operations('chown', path, uid, gid)
348     
349     def truncate(self, path, length):
350         return self.operations('truncate', path, length)
351     
352     def open(self, path, fip):
353         fi = fip.contents
354         if self.raw_fi:
355             return self.operations('open', path, fi)
356         else:
357             fi.fh = self.operations('open', path, fi.flags)
358             return 0
359     
360     def read(self, path, buf, size, offset, fip):
361         fh = fip.contents if self.raw_fi else fip.contents.fh
362         ret = self.operations('read', path, size, offset, fh)
363         if not ret:
364             return 0
365         data = create_string_buffer(ret[:size], size)
366         memmove(buf, data, size)
367         return size
368     
369     def write(self, path, buf, size, offset, fip):
370         data = string_at(buf, size)
371         fh = fip.contents if self.raw_fi else fip.contents.fh
372         return self.operations('write', path, data, offset, fh)
373     
374     def statfs(self, path, buf):
375         stv = buf.contents
376         attrs = self.operations('statfs', path)
377         for key, val in attrs.items():
378             if hasattr(stv, key):
379                 setattr(stv, key, val)
380         return 0
381     
382     def flush(self, path, fip):
383         fh = fip.contents if self.raw_fi else fip.contents.fh
384         return self.operations('flush', path, fh)
385     
386     def release(self, path, fip):
387         fh = fip.contents if self.raw_fi else fip.contents.fh
388         return self.operations('release', path, fh)
389     
390     def fsync(self, path, datasync, fip):
391         fh = fip.contents if self.raw_fi else fip.contents.fh
392         return self.operations('fsync', path, datasync, fh)
393     
394     def setxattr(self, path, name, value, size, options, *args):
395         data = string_at(value, size)
396         return self.operations('setxattr', path, name, data, options, *args)
397     
398     def getxattr(self, path, name, value, size, *args):
399         ret = self.operations('getxattr', path, name, *args)
400         retsize = len(ret)
401         buf = create_string_buffer(ret, retsize)    # Does not add trailing 0
402         if bool(value):
403             if retsize > size:
404                 return -ERANGE
405             memmove(value, buf, retsize)
406         return retsize
407     
408     def listxattr(self, path, namebuf, size):
409         ret = self.operations('listxattr', path)
410         buf = create_string_buffer('\x00'.join(ret)) if ret else ''
411         bufsize = len(buf)
412         if bool(namebuf):
413             if bufsize > size:
414                 return -ERANGE
415             memmove(namebuf, buf, bufsize)
416         return bufsize
417     
418     def removexattr(self, path, name):
419         return self.operations('removexattr', path, name)
420     
421     def opendir(self, path, fip):
422         # Ignore raw_fi
423         fip.contents.fh = self.operations('opendir', path)
424         return 0
425     
426     def readdir(self, path, buf, filler, offset, fip):
427         # Ignore raw_fi
428         for item in self.operations('readdir', path, fip.contents.fh):
429             if isinstance(item, (str, unicode)):
430                 name, st, offset = item, None, 0
431             else:
432                 name, attrs, offset = item
433                 if attrs:
434                     st = c_stat()
435                     set_st_attrs(st, attrs)
436                 else:
437                     st = None
438             if filler(buf, name, st, offset) != 0:
439                 break
440         return 0
441     
442     def releasedir(self, path, fip):
443         # Ignore raw_fi
444         return self.operations('releasedir', path, fip.contents.fh)
445     
446     def fsyncdir(self, path, datasync, fip):
447         # Ignore raw_fi
448         return self.operations('fsyncdir', path, datasync, fip.contents.fh)
449     
450     def init(self, conn):
451         return self.operations('init', '/')
452     
453     def destroy(self, private_data):
454         return self.operations('destroy', '/')
455     
456     def access(self, path, amode):
457         return self.operations('access', path, amode)
458     
459     def create(self, path, mode, fip):
460         fi = fip.contents
461         if self.raw_fi:
462             return self.operations('create', path, mode, fi)
463         else:
464             fi.fh = self.operations('create', path, mode)
465             return 0
466     
467     def ftruncate(self, path, length, fip):
468         fh = fip.contents if self.raw_fi else fip.contents.fh
469         return self.operations('truncate', path, length, fh)
470     
471     def fgetattr(self, path, buf, fip):
472         memset(buf, 0, sizeof(c_stat))
473         st = buf.contents
474         fh = fip and (fip.contents if self.raw_fi else fip.contents.fh)
475         attrs = self.operations('getattr', path, fh)
476         set_st_attrs(st, attrs)
477         return 0
478     
479     def lock(self, path, fip, cmd, lock):
480         fh = fip.contents if self.raw_fi else fip.contents.fh
481         return self.operations('lock', path, fh, cmd, lock)
482     
483     def utimens(self, path, buf):
484         if buf:
485             atime = time_of_timespec(buf.contents.actime)
486             mtime = time_of_timespec(buf.contents.modtime)
487             times = (atime, mtime)
488         else:
489             times = None
490         return self.operations('utimens', path, times)
491     
492     def bmap(self, path, blocksize, idx):
493         return self.operations('bmap', path, blocksize, idx)
494
495
496 class Operations(object):
497     """This class should be subclassed and passed as an argument to FUSE on
498        initialization. All operations should raise a FuseOSError exception
499        on error.
500        
501        When in doubt of what an operation should do, check the FUSE header
502        file or the corresponding system call man page."""
503     
504     def __call__(self, op, *args):
505         if not hasattr(self, op):
506             raise FuseOSError(EFAULT)
507         return getattr(self, op)(*args)
508         
509     def access(self, path, amode):
510         return 0
511     
512     bmap = None
513     
514     def chmod(self, path, mode):
515         raise FuseOSError(EROFS)
516     
517     def chown(self, path, uid, gid):
518         raise FuseOSError(EROFS)
519     
520     def create(self, path, mode, fi=None):
521         """When raw_fi is False (default case), fi is None and create should
522            return a numerical file handle.
523            When raw_fi is True the file handle should be set directly by create
524            and return 0."""
525         raise FuseOSError(EROFS)
526     
527     def destroy(self, path):
528         """Called on filesystem destruction. Path is always /"""
529         pass
530     
531     def flush(self, path, fh):
532         return 0
533     
534     def fsync(self, path, datasync, fh):
535         return 0
536     
537     def fsyncdir(self, path, datasync, fh):
538         return 0
539     
540     def getattr(self, path, fh=None):
541         """Returns a dictionary with keys identical to the stat C structure
542            of stat(2).
543            st_atime, st_mtime and st_ctime should be floats.
544            NOTE: There is an incombatibility between Linux and Mac OS X concerning
545            st_nlink of directories. Mac OS X counts all files inside the directory,
546            while Linux counts only the subdirectories."""
547         
548         if path != '/':
549             raise FuseOSError(ENOENT)
550         return dict(st_mode=(S_IFDIR | 0755), st_nlink=2)
551     
552     def getxattr(self, path, name, position=0):
553         raise FuseOSError(ENOTSUP)
554     
555     def init(self, path):
556         """Called on filesystem initialization. Path is always /
557            Use it instead of __init__ if you start threads on initialization."""
558         pass
559     
560     def link(self, target, source):
561         raise FuseOSError(EROFS)
562     
563     def listxattr(self, path):
564         return []
565         
566     lock = None
567     
568     def mkdir(self, path, mode):
569         raise FuseOSError(EROFS)
570     
571     def mknod(self, path, mode, dev):
572         raise FuseOSError(EROFS)
573     
574     def open(self, path, flags):
575         """When raw_fi is False (default case), open should return a numerical
576            file handle.
577            When raw_fi is True the signature of open becomes:
578                open(self, path, fi)
579            and the file handle should be set directly."""
580         return 0
581     
582     def opendir(self, path):
583         """Returns a numerical file handle."""
584         return 0
585     
586     def read(self, path, size, offset, fh):
587         """Returns a string containing the data requested."""
588         raise FuseOSError(EIO)
589     
590     def readdir(self, path, fh):
591         """Can return either a list of names, or a list of (name, attrs, offset)
592            tuples. attrs is a dict as in getattr."""
593         return ['.', '..']
594     
595     def readlink(self, path):
596         raise FuseOSError(ENOENT)
597     
598     def release(self, path, fh):
599         return 0
600     
601     def releasedir(self, path, fh):
602         return 0
603     
604     def removexattr(self, path, name):
605         raise FuseOSError(ENOTSUP)
606     
607     def rename(self, old, new):
608         raise FuseOSError(EROFS)
609     
610     def rmdir(self, path):
611         raise FuseOSError(EROFS)
612     
613     def setxattr(self, path, name, value, options, position=0):
614         raise FuseOSError(ENOTSUP)
615     
616     def statfs(self, path):
617         """Returns a dictionary with keys identical to the statvfs C structure
618            of statvfs(3).
619            On Mac OS X f_bsize and f_frsize must be a power of 2 (minimum 512)."""
620         return {}
621     
622     def symlink(self, target, source):
623         raise FuseOSError(EROFS)
624     
625     def truncate(self, path, length, fh=None):
626         raise FuseOSError(EROFS)
627     
628     def unlink(self, path):
629         raise FuseOSError(EROFS)
630     
631     def utimens(self, path, times=None):
632         """Times is a (atime, mtime) tuple. If None use current time."""
633         return 0
634     
635     def write(self, path, data, offset, fh):
636         raise FuseOSError(EROFS)
637
638
639 class LoggingMixIn:
640     def __call__(self, op, path, *args):
641         print '->', op, path, repr(args)
642         ret = '[Unhandled Exception]'
643         try:
644             ret = getattr(self, op)(path, *args)
645             return ret
646         except OSError, e:
647             ret = str(e)
648             raise
649         finally:
650             print '<-', op, repr(ret)