Operating Systems 2014F: Tutorial 8

From Soma-notes
Revision as of 12:19, 21 November 2014 by Soma (talk | contribs)

This tutorial is not yet finalized.

In this tutorial you will be learning about filesystem in userspace (FUSE) using an example from fusepy. Note the example code is written in Python, not C. The code should be readable even if you are not familiar with Python. When you make changes, keep in mind the following:

  • Indentation, not curly braces, is used to denote block structure. Thus indentation matters! Pay particular attention to spaces versus tabs. (When in doubt, don't use tabs.)
  • To create a local variable, just assign to it. Python will make sure it is only defined within that function.
  • If you just type "python" on the command line you get a read-eval-print loop where you can try things out.
  • Python is heavily object oriented, so expect to use methods more than straight functions.

To get going, first download the fuse example code in a zip file. Unpack it and change into the directory in a terminal. You should see two files: fusell.py and memoryll.py (both listed below). We will be running and modifying memoryll.py. For it to work, however, fusell.py has to be in the same directory as memoryll.py.

Specifically, to get started run the following commands:

Now to run the example program type:

  • python memoryll.py mnt

(You should get an error about /etc/fuse.conf is not readable; this is normal and is because regular Ubuntu users aren't in the fuse group.)

After you do this, a new filesystem will be mounted on /mnt. If you terminate the script and re-run it, you may get errors. To fix them, do the following:

  • Make sure no processes are using the mnt directory (e.g., being in that directory in another terminal window).
  • sudo umount mnt (while in the fuse directory)


memoryll.py

#!/usr/bin/env python

from collections import defaultdict
from errno import ENOENT, EROFS
from stat import S_IFMT, S_IMODE, S_IFDIR, S_IFREG
from sys import argv, exit
from time import time

from fusell import FUSELL


class Memory(FUSELL):
    def create_ino(self):
        self.ino += 1
        return self.ino
    
    def init(self, userdata, conn):
        self.ino = 1
        self.attr = defaultdict(dict)
        self.data = defaultdict(str)
        self.parent = {}
        self.children = defaultdict(dict)
        
        self.attr[1] = {'st_ino': 1, 'st_mode': S_IFDIR | 0777, 'st_nlink': 2}
        self.parent[1] = 1
    
    forget = None
    
    def getattr(self, req, ino, fi):
        print 'getattr:', ino
        attr = self.attr[ino]
        if attr:
            self.reply_attr(req, attr, 1.0)
        else:
            self.reply_err(req, ENOENT)
    
    def lookup(self, req, parent, name):
        print 'lookup:', parent, name
        children = self.children[parent]
        ino = children.get(name, 0)
        attr = self.attr[ino]
        
        if attr:
            entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0}
            self.reply_entry(req, entry)
        else:
            self.reply_err(req, ENOENT)
    
    def mkdir(self, req, parent, name, mode):
        print 'mkdir:', parent, name
        ino = self.create_ino()
        ctx = self.req_ctx(req)
        now = time()
        attr = {
            'st_ino': ino,
            'st_mode': S_IFDIR | mode,
            'st_nlink': 2,
            'st_uid': ctx['uid'],
            'st_gid': ctx['gid'],
            'st_atime': now,
            'st_mtime': now,
            'st_ctime': now}
        
        self.attr[ino] = attr
        self.attr[parent]['st_nlink'] += 1
        self.parent[ino] = parent
        self.children[parent][name] = ino
        
        entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0}
        self.reply_entry(req, entry)
    
    def mknod(self, req, parent, name, mode, rdev):
        print 'mknod:', parent, name
        ino = self.create_ino()
        ctx = self.req_ctx(req)
        now = time()
        attr = {
            'st_ino': ino,
            'st_mode': mode,
            'st_nlink': 1,
            'st_uid': ctx['uid'],
            'st_gid': ctx['gid'],
            'st_rdev': rdev,
            'st_atime': now,
            'st_mtime': now,
            'st_ctime': now}
        
        self.attr[ino] = attr
        self.attr[parent]['st_nlink'] += 1
        self.children[parent][name] = ino
        
        entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0}
        self.reply_entry(req, entry)
    
    def open(self, req, ino, fi):
        print 'open:', ino
        self.reply_open(req, fi)

    def read(self, req, ino, size, off, fi):
        print 'read:', ino, size, off
        buf = self.data[ino][off:(off + size)]
        self.reply_buf(req, buf)
    
    def readdir(self, req, ino, size, off, fi):
        print 'readdir:', ino
        parent = self.parent[ino]
        entries = [('.', {'st_ino': ino, 'st_mode': S_IFDIR}),
            ('..', {'st_ino': parent, 'st_mode': S_IFDIR})]
        for name, child in self.children[ino].items():
            entries.append((name, self.attr[child]))
        self.reply_readdir(req, size, off, entries)        
    
    def rename(self, req, parent, name, newparent, newname):
        print 'rename:', parent, name, newparent, newname
        ino = self.children[parent].pop(name)
        self.children[newparent][newname] = ino
        self.parent[ino] = newparent
        self.reply_err(req, 0)
    
    def setattr(self, req, ino, attr, to_set, fi):
        print 'setattr:', ino, to_set
        a = self.attr[ino]
        for key in to_set:
            if key == 'st_mode':
                # Keep the old file type bit fields
                a['st_mode'] = S_IFMT(a['st_mode']) | S_IMODE(attr['st_mode'])
            else:
                a[key] = attr[key]
        self.attr[ino] = a
        self.reply_attr(req, a, 1.0)
    
    def write(self, req, ino, buf, off, fi):
        print 'write:', ino, off, len(buf)
        self.data[ino] = self.data[ino][:off] + buf
        self.attr[ino]['st_size'] = len(self.data[ino])
        self.reply_write(req, len(buf))

if __name__ == '__main__':
    if len(argv) != 2:
        print 'usage: %s <mountpoint>' % argv[0]
        exit(1)   
    fuse = Memory(argv[1])


fusell.py

# Copyright (c) 2010 Giorgos Verigakis <verigak@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
# 
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

from __future__ import division

from ctypes import *
from ctypes.util import find_library
from errno import *
from functools import partial, wraps
from inspect import getmembers, ismethod
from platform import machine, system
from stat import S_IFDIR, S_IFREG


_system = system()
_machine = machine()

class LibFUSE(CDLL):
    def __init__(self):
        if _system == 'Darwin':
            self.libiconv = CDLL(find_library('iconv'), RTLD_GLOBAL)
        super(LibFUSE, self).__init__(find_library('fuse'))
        
        self.fuse_mount.argtypes = (c_char_p, POINTER(fuse_args))
        self.fuse_mount.restype = c_void_p
        self.fuse_lowlevel_new.argtypes = (POINTER(fuse_args), POINTER(fuse_lowlevel_ops),
                                            c_size_t, c_void_p)
        self.fuse_lowlevel_new.restype = c_void_p
        self.fuse_set_signal_handlers.argtypes = (c_void_p,)
        self.fuse_session_add_chan.argtypes = (c_void_p, c_void_p)
        self.fuse_session_loop.argtypes = (c_void_p,)
        self.fuse_remove_signal_handlers.argtypes = (c_void_p,)
        self.fuse_session_remove_chan.argtypes = (c_void_p,)
        self.fuse_session_destroy.argtypes = (c_void_p,)
        self.fuse_unmount.argtypes = (c_char_p, c_void_p)
        
        self.fuse_req_ctx.restype = POINTER(fuse_ctx)
        self.fuse_req_ctx.argtypes = (fuse_req_t,)
        
        self.fuse_reply_err.argtypes = (fuse_req_t, c_int)
        self.fuse_reply_attr.argtypes = (fuse_req_t, c_void_p, c_double)
        self.fuse_reply_entry.argtypes = (fuse_req_t, c_void_p)
        self.fuse_reply_open.argtypes = (fuse_req_t, c_void_p)
        self.fuse_reply_buf.argtypes = (fuse_req_t, c_char_p, c_size_t)
        self.fuse_reply_write.argtypes = (fuse_req_t, c_size_t)
        
        self.fuse_add_direntry.argtypes = (c_void_p, c_char_p, c_size_t, c_char_p,
                                            c_stat_p, c_off_t)

class fuse_args(Structure):
    _fields_ = [('argc', c_int), ('argv', POINTER(c_char_p)), ('allocated', c_int)]

class c_timespec(Structure):
    _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)]

class c_stat(Structure):
    pass    # Platform dependent

if _system == 'Darwin':
    ENOTSUP = 45
    c_dev_t = c_int32
    c_fsblkcnt_t = c_ulong
    c_fsfilcnt_t = c_ulong
    c_gid_t = c_uint32
    c_mode_t = c_uint16
    c_off_t = c_int64
    c_pid_t = c_int32
    c_uid_t = c_uint32
    c_stat._fields_ = [
        ('st_dev', c_dev_t),
        ('st_ino', c_uint32),
        ('st_mode', c_mode_t),
        ('st_nlink', c_uint16),
        ('st_uid', c_uid_t),
        ('st_gid', c_gid_t),
        ('st_rdev', c_dev_t),
        ('st_atimespec', c_timespec),
        ('st_mtimespec', c_timespec),
        ('st_ctimespec', c_timespec),
        ('st_size', c_off_t),
        ('st_blocks', c_int64),
        ('st_blksize', c_int32)]
elif _system == 'Linux':
    ENOTSUP = 95
    c_dev_t = c_ulonglong
    c_fsblkcnt_t = c_ulonglong
    c_fsfilcnt_t = c_ulonglong
    c_gid_t = c_uint
    c_mode_t = c_uint
    c_off_t = c_longlong
    c_pid_t = c_int
    c_uid_t = c_uint
    
    if _machine == 'x86_64':
        c_stat._fields_ = [
            ('st_dev', c_dev_t),
            ('st_ino', c_ulong),
            ('st_nlink', c_ulong),
            ('st_mode', c_mode_t),
            ('st_uid', c_uid_t),
            ('st_gid', c_gid_t),
            ('__pad0', c_int),
            ('st_rdev', c_dev_t),
            ('st_size', c_off_t),
            ('st_blksize', c_long),
            ('st_blocks', c_long),
            ('st_atimespec', c_timespec),
            ('st_mtimespec', c_timespec),
            ('st_ctimespec', c_timespec)]
    elif _machine == 'ppc':
        c_stat._fields_ = [
            ('st_dev', c_dev_t),
            ('st_ino', c_ulonglong),
            ('st_mode', c_mode_t),
            ('st_nlink', c_uint),
            ('st_uid', c_uid_t),
            ('st_gid', c_gid_t),
            ('st_rdev', c_dev_t),
            ('__pad2', c_ushort),
            ('st_size', c_off_t),
            ('st_blksize', c_long),
            ('st_blocks', c_longlong),
            ('st_atimespec', c_timespec),
            ('st_mtimespec', c_timespec),
            ('st_ctimespec', c_timespec)]
    else:
        # i686, use as fallback for everything else
        c_stat._fields_ = [
            ('st_dev', c_dev_t),
            ('__pad1', c_ushort),
            ('__st_ino', c_ulong),
            ('st_mode', c_mode_t),
            ('st_nlink', c_uint),
            ('st_uid', c_uid_t),
            ('st_gid', c_gid_t),
            ('st_rdev', c_dev_t),
            ('__pad2', c_ushort),
            ('st_size', c_off_t),
            ('st_blksize', c_long),
            ('st_blocks', c_longlong),
            ('st_atimespec', c_timespec),
            ('st_mtimespec', c_timespec),
            ('st_ctimespec', c_timespec),
            ('st_ino', c_ulonglong)]
else:
    raise NotImplementedError('%s is not supported.' % _system)

class c_statvfs(Structure):
    _fields_ = [
        ('f_bsize', c_ulong),
        ('f_frsize', c_ulong),
        ('f_blocks', c_fsblkcnt_t),
        ('f_bfree', c_fsblkcnt_t),
        ('f_bavail', c_fsblkcnt_t),
        ('f_files', c_fsfilcnt_t),
        ('f_ffree', c_fsfilcnt_t),
        ('f_favail', c_fsfilcnt_t)]

class fuse_file_info(Structure):
    _fields_ = [
        ('flags', c_int),
        ('fh_old', c_ulong),
        ('writepage', c_int),
        ('direct_io', c_uint, 1),
        ('keep_cache', c_uint, 1),
        ('flush', c_uint, 1),
        ('padding', c_uint, 29),
        ('fh', c_uint64),
        ('lock_owner', c_uint64)]

class fuse_ctx(Structure):
    _fields_ = [('uid', c_uid_t), ('gid', c_gid_t), ('pid', c_pid_t)]

fuse_ino_t = c_ulong
fuse_req_t = c_void_p
c_stat_p = POINTER(c_stat)
fuse_file_info_p = POINTER(fuse_file_info)

FUSE_SET_ATTR = ('st_mode', 'st_uid', 'st_gid', 'st_size', 'st_atime', 'st_mtime')

class fuse_entry_param(Structure):
    _fields_ = [
        ('ino', fuse_ino_t),
        ('generation', c_ulong),
        ('attr', c_stat),
        ('attr_timeout', c_double),
        ('entry_timeout', c_double)]

class fuse_lowlevel_ops(Structure):
    _fields_ = [
        ('init', CFUNCTYPE(None, c_void_p, c_void_p)),
        ('destroy', CFUNCTYPE(None, c_void_p)),
        ('lookup', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)),
        ('forget', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_ulong)),
        ('getattr', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
        ('setattr', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_stat_p, c_int, fuse_file_info_p)),
        ('readlink', CFUNCTYPE(None, fuse_req_t, fuse_ino_t)),
        ('mknod', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_mode_t, c_dev_t)),
        ('mkdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_mode_t)),
        ('unlink', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)),
        ('rmdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)),
        ('symlink', CFUNCTYPE(None, fuse_req_t, c_char_p, fuse_ino_t, c_char_p)),
        ('rename', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, fuse_ino_t, c_char_p)),
        ('link', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_ino_t, c_char_p)),
        ('open', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
        ('read', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_size_t, c_off_t, fuse_file_info_p)),
        ('write', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_size_t, c_off_t,
                                fuse_file_info_p)),
        ('flush', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
        ('release', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
        ('fsync', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_int, fuse_file_info_p)),
        ('opendir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
        ('readdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_size_t, c_off_t, fuse_file_info_p)),
        ('releasedir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
        ('fsyncdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_int, fuse_file_info_p))]


def struct_to_dict(p):
    try:
        x = p.contents
        return dict((key, getattr(x, key)) for key, type in x._fields_)
    except ValueError:
        return {}

def stat_to_dict(p):
    try:
        d = {}
        x = p.contents
        for key, type in x._fields_:
            if key in ('st_atimespec', 'st_mtimespec', 'st_ctimespec'):
                ts = getattr(x, key)
                key = key[:-4]      # Lose the "spec"
                d[key] = ts.tv_sec + ts.tv_nsec / 10 ** 9
            else:
                d[key] = getattr(x, key)
        return d
    except ValueError:
        return {}

def dict_to_stat(d):
    for key in ('st_atime', 'st_mtime', 'st_ctime'):
        if key in d:
            val = d[key]
            sec = int(val)
            nsec = int((val - sec) * 10 ** 9)
            d[key + 'spec'] = c_timespec(sec, nsec)
    return c_stat(**d)

def setattr_mask_to_list(mask):
    return [FUSE_SET_ATTR[i] for i in range(len(FUSE_SET_ATTR)) if mask & (1 << i)]

class FUSELL(object):
    def __init__(self, mountpoint):
        self.libfuse = LibFUSE()       
        
        fuse_ops = fuse_lowlevel_ops()
        
        for name, prototype in fuse_lowlevel_ops._fields_:
            method = getattr(self, 'fuse_' + name, None) or getattr(self, name, None)
            if method:
                setattr(fuse_ops, name, prototype(method))
        
        args = ['fuse']
        argv = fuse_args(len(args), (c_char_p * len(args))(*args), 0)
        
        # TODO: handle initialization errors
        
        chan = self.libfuse.fuse_mount(mountpoint, argv)
        assert chan
        
        session = self.libfuse.fuse_lowlevel_new(argv, byref(fuse_ops), sizeof(fuse_ops), None)
        assert session
        
        err = self.libfuse.fuse_set_signal_handlers(session)
        assert err == 0
        
        self.libfuse.fuse_session_add_chan(session, chan)
        
        err = self.libfuse.fuse_session_loop(session)
        assert err == 0
        
        err = self.libfuse.fuse_remove_signal_handlers(session)
        assert err == 0
        
        self.libfuse.fuse_session_remove_chan(chan)
        self.libfuse.fuse_session_destroy(session)
        self.libfuse.fuse_unmount(mountpoint, chan)
    
    def reply_err(self, req, err):
        return self.libfuse.fuse_reply_err(req, err)
    
    def reply_none(self, req):
        self.libfuse.fuse_reply_none(req)
    
    def reply_entry(self, req, entry):
        entry['attr'] = c_stat(**entry['attr'])
        e = fuse_entry_param(**entry)
        self.libfuse.fuse_reply_entry(req, byref(e))
    
    def reply_create(self, req, *args):
        pass    # XXX
    
    def reply_attr(self, req, attr, attr_timeout):
        st = dict_to_stat(attr)
        return self.libfuse.fuse_reply_attr(req, byref(st), c_double(attr_timeout))
    
    def reply_readlink(self, req, *args):
        pass    # XXX
    
    def reply_open(self, req, d):
        fi = fuse_file_info(**d)
        return self.libfuse.fuse_reply_open(req, byref(fi))
    
    def reply_write(self, req, count):
        return self.libfuse.fuse_reply_write(req, count)
    
    def reply_buf(self, req, buf):
        return self.libfuse.fuse_reply_buf(req, buf, len(buf))
    
    def reply_readdir(self, req, size, off, entries):
        bufsize = 0
        sized_entries = []
        for name, attr in entries:
            entsize = self.libfuse.fuse_add_direntry(req, None, 0, name, None, 0)
            sized_entries.append((name, attr, entsize))
            bufsize += entsize

        next = 0
        buf = create_string_buffer(bufsize)
        for name, attr, entsize in sized_entries:
            entbuf = cast(addressof(buf) + next, c_char_p)
            st = c_stat(**attr)
            next += entsize
            self.libfuse.fuse_add_direntry(req, entbuf, entsize, name, byref(st), next)

        if off < bufsize:
            buf = cast(addressof(buf) + off, c_char_p) if off else buf
            return self.libfuse.fuse_reply_buf(req, buf, min(bufsize - off, size))
        else:
            return self.libfuse.fuse_reply_buf(req, None, 0)
    
    
    # If you override the following methods you should reply directly
    # with the self.libfuse.fuse_reply_* methods.
    
    def fuse_getattr(self, req, ino, fi):
        self.getattr(req, ino, struct_to_dict(fi))
    
    def fuse_setattr(self, req, ino, attr, to_set, fi):
        attr_dict = stat_to_dict(attr)
        to_set_list = setattr_mask_to_list(to_set)
        fi_dict = struct_to_dict(fi)
        self.setattr(req, ino, attr_dict, to_set_list, fi_dict)
        
    def fuse_open(self, req, ino, fi):
        self.open(req, ino, struct_to_dict(fi))
    
    def fuse_read(self, req, ino, size, off, fi):
        self.read(req, ino, size, off, fi)
    
    def fuse_write(self, req, ino, buf, size, off, fi):
        buf_str = string_at(buf, size)
        fi_dict = struct_to_dict(fi)
        self.write(req, ino, buf_str, off, fi_dict)

    def fuse_flush(self, req, ino, fi):
        self.flush(req, ino, struct_to_dict(fi))
    
    def fuse_release(self, req, ino, fi):
        self.release(req, ino, struct_to_dict(fi))
    
    def fuse_fsync(self, req, ino, datasync, fi):
        self.fsyncdir(req, ino, datasync, struct_to_dict(fi))
    
    def fuse_opendir(self, req, ino, fi):
        self.opendir(req, ino, struct_to_dict(fi))
    
    def fuse_readdir(self, req, ino, size, off, fi):
        self.readdir(req, ino, size, off, struct_to_dict(fi))
    
    def fuse_releasedir(self, req, ino, fi):
        self.releasedir(req, ino, struct_to_dict(fi))
    
    def fuse_fsyncdir(self, req, ino, datasync, fi):
        self.fsyncdir(req, ino, datasync, struct_to_dict(fi))
    
    
    # Utility methods
    
    def req_ctx(self, req):
        ctx = self.libfuse.fuse_req_ctx(req)
        return struct_to_dict(ctx)
    
    
    # Methods to be overridden in subclasses.
    # Reply with the self.reply_* methods.
    
    def init(self, userdata, conn):
        """Initialize filesystem
        
        There's no reply to this method
        """
        pass

    def destroy(self, userdata):
        """Clean up filesystem
        
        There's no reply to this method
        """
        pass

    def lookup(self, req, parent, name):
        """Look up a directory entry by name and get its attributes.
        
        Valid replies:
            reply_entry
            reply_err
        """
        self.reply_err(req, ENOENT)
    
    def forget(self, req, ino, nlookup):
        """Forget about an inode
        
        Valid replies:
            reply_none
        """
        self.reply_none(req)

    def getattr(self, req, ino, fi):
        """Get file attributes
        
        Valid replies:
            reply_attr
            reply_err
        """
        if ino == 1:
            attr = {'st_ino': 1, 'st_mode': S_IFDIR | 0755, 'st_nlink': 2}
            self.reply_attr(req, attr, 1.0)
        else:
            self.reply_err(req, ENOENT)        
    
    def setattr(self, req, ino, attr, to_set, fi):
        """Set file attributes
        
        Valid replies:
            reply_attr
            reply_err
        """
        self.reply_err(req, EROFS)
        
    def readlink(self, req, ino):
        """Read symbolic link
        
        Valid replies:
            reply_readlink
            reply_err
        """
        self.reply_err(req, ENOENT)
    
    def mknod(self, req, parent, name, mode, rdev):
        """Create file node
        
        Valid replies:
            reply_entry
            reply_err
        """
        self.reply_err(req, EROFS)
    
    def mkdir(self, req, parent, name, mode):
        """Create a directory
        
        Valid replies:
            reply_entry
            reply_err
        """
        self.reply_err(req, EROFS)

    def unlink(self, req, parent, name):
        """Remove a file
        
        Valid replies:
            reply_err
        """
        self.reply_err(req, EROFS)
    
    def rmdir(self, req, parent, name):
        """Remove a directory
        
        Valid replies:
            reply_err
        """
        self.reply_err(req, EROFS)
    
    def symlink(self, req, link, parent, name):
        """Create a symbolic link
        
        Valid replies:
            reply_entry
            reply_err
        """
        self.reply_err(req, EROFS)
    
    def rename(self, req, parent, name, newparent, newname):
        """Rename a file
        
        Valid replies:
            reply_err
        """
        self.reply_err(req, EROFS)
    
    def link(self, req, ino, newparent, newname):
        """Create a hard link
        
        Valid replies:
            reply_entry
            reply_err
        """
        self.reply_err(req, EROFS)
    
    def open(self, req, ino, fi):
        """Open a file
        
        Valid replies:
            reply_open
            reply_err
        """
        self.reply_open(req, fi)
    
    def read(self, req, ino, size, off, fi):
        """Read data
        
        Valid replies:
            reply_buf
            reply_err
        """
        self.reply_err(req, EIO)
        
    def write(self, req, ino, buf, off, fi):
        """Write data
        
        Valid replies:
            reply_write
            reply_err
        """
        self.reply_err(req, EROFS)
    
    def flush(self, req, ino, fi):
        """Flush method
        
        Valid replies:
            reply_err
        """
        self.reply_err(req, 0)
    
    def release(self, req, ino, fi):
        """Release an open file
        
        Valid replies:
            reply_err
        """
        self.reply_err(req, 0)
    
    def fsync(self, req, ino, datasync, fi):
        """Synchronize file contents
        
        Valid replies:
            reply_err
        """
        self.reply_err(req, 0)

    def opendir(self, req, ino, fi):
        """Open a directory
        
        Valid replies:
            reply_open
            reply_err
        """
        self.reply_open(req, fi)
    
    def readdir(self, req, ino, size, off, fi):
        """Read directory
        
        Valid replies:
            reply_readdir
            reply_err
        """
        if ino == 1:
            attr = {'st_ino': 1, 'st_mode': S_IFDIR}
            entries = [('.', attr), ('..', attr)]
            self.reply_readdir(req, size, off, entries)
        else:
            self.reply_err(req, ENOENT)
    
    def releasedir(self, req, ino, fi):
        """Release an open directory
        
        Valid replies:
            reply_err
        """
        self.reply_err(req, 0)

    def fsyncdir(self, req, ino, datasync, fi):
        """Synchronize directory contents
        
        Valid replies:
            reply_err
        """
        self.reply_err(req, 0)