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