Operating Systems 2018F: Tutorial 8: Difference between revisions
No edit summary |
No edit summary |
||
(4 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
In this tutorial you will be learning about [https://github.com/libfuse/libfuse filesystem in userspace (FUSE)] using an example from [https://github.com/terencehonles/fusepy fusepy]. You will also be tracing kernel-level behavior using [https://wkz.github.io/ply/ ply]. The exercises below have been tested in the class openstack VM. It should also work in the virtualbox VM (as long as you don't run out of disk space while installing/updating packages). It should also work on any ubuntu 18.04 installation. Other linux distributions are likely to work but there may be glitches. | In this tutorial you will be learning about [https://github.com/libfuse/libfuse filesystem in userspace (FUSE)] using an example from [https://github.com/terencehonles/fusepy fusepy]. You will also be tracing kernel-level behavior using [https://wkz.github.io/ply/ ply]. The exercises below have been tested in the class openstack VM. It should also work in the virtualbox VM (as long as you don't run out of disk space while installing/updating packages). It should also work on any ubuntu 18.04 installation. Other linux distributions are likely to work but there may be glitches. | ||
Line 69: | Line 67: | ||
After this, you should have ply installed in /usr/local/sbin/. | After this, you should have ply installed in /usr/local/sbin/. | ||
For documentation, check out [https://wkz.github.io/ply/ply.1.html ply's man page] and [https://github.com/iovisor/ply/blob/master/README.md ply's README]. | |||
==Tasks== | ==Tasks== | ||
Line 74: | Line 74: | ||
# When you make files in mnt, are they normal files? Can they be accessed by regular programs? | # When you make files in mnt, are they normal files? Can they be accessed by regular programs? | ||
# What permissions do you need to "mount" the filesystem? What about to "umount" it? | # What permissions do you need to "mount" the filesystem? What about to "umount" it? | ||
# What system calls are associated with which functions in memoryll.py? How close is the correspondence between system calls made and python function invocations? (Note that if you want to use strace you'll have to do everything as root.) | # What system calls are associated with which functions in memoryll.py? How close is the correspondence between system calls made and python function invocations? (Note that if you want to use strace you'll have to do everything as root.) | ||
# Modify the code so that file renames don't delete the old file but instead just create a new name for it. | # Modify the code so that file renames don't delete the old file but instead just create a new name for it. | ||
# Modify the code so that when read, files are converted to upper case. | # Modify the code so that when read, files are converted to upper case. | ||
# Download [https://homeostasis.scs.carleton.ca/~soma/os-2018f/code/tut8/opensnoop.ply opensnoop.ply] and run it with the command "sudo ply opensnoop.ply". Cancel the script by entering Ctrl-C. What does this ply script output? | |||
# Download [https://homeostasis.scs.carleton.ca/~soma/os-2018f/code/tut8/fusesnoop.ply fusesnoop.ply] and run it with the command "sudo ply fusesnoop.ply". What does this ply script output? | |||
# Create a ply script to monitor other functions in the Linux kernel. Try looking at functions in [https://elixir.bootlin.com/linux/latest/source/fs/fuse fs/fuse] and general filesystem operations such as [https://elixir.bootlin.com/linux/latest/source/fs/read_write.c fs/read_write.c]. | |||
==Code== | ==Code== |
Latest revision as of 22:19, 11 November 2018
In this tutorial you will be learning about filesystem in userspace (FUSE) using an example from fusepy. You will also be tracing kernel-level behavior using ply. The exercises below have been tested in the class openstack VM. It should also work in the virtualbox VM (as long as you don't run out of disk space while installing/updating packages). It should also work on any ubuntu 18.04 installation. Other linux distributions are likely to work but there may be glitches.
memoryll & fusell
The memoryll script (which depends on fusell) creates an in-memory filesystem. You can store what you want in this filesystem and the data will be recorded in python data structures. When the script terminates all data is lost.
The memoryll and fusell example code are 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:
wget http://homeostasis.scs.carleton.ca/~soma/os-2014f/code/fuse.zip unzip fuse.zip cd fuse mkdir mnt
Now to run the example program type:
python memoryll.py mnt
(You may get an error about /etc/fuse.conf is not readable; if so don't worry about it, this happens when the user is not in the fuse group.)
After you do this, a new filesystem will be mounted on mnt (in the current directory). NOTE: all files created in this directory disappear when the script is terminated!
To get the script to properly exit, run:
fusermount -u mnt
(This will unmount the filesystem.) Note that if any process is using files in the mounted filesystem, this command will fail.
To forcibly terminate the script, type Control-Z then type "kill %1" (kill first job).
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)
We suggest you leave the memoryll.py script running in one terminal window while doing the following exercises (using GUI programs or commands in another terminal window).
ply
ply is a tool for tracing and gathering stats on the currently running Linux kernel. We will be using it today to see what is happening on the kernel side when we use memoryll.
First, install the dependencies:
apt install build-essential autoconf flex bison git apt clean
Next, download the source code:
wget https://homeostasis.scs.carleton.ca/~soma/os-2018f/code/tut8/ply-src.zip unzip ply-src.zip
or
git clone https://github.com/iovisor/ply.git
Then, build and install:
cd ply ./autogen.sh ./configure make sudo make install
After this, you should have ply installed in /usr/local/sbin/.
For documentation, check out ply's man page and ply's README.
Tasks
- Make a few directories in mnt and populate it with several files. Do you see any errors? What messages does memoryll.py output while doing this?
- When you make files in mnt, are they normal files? Can they be accessed by regular programs?
- What permissions do you need to "mount" the filesystem? What about to "umount" it?
- What system calls are associated with which functions in memoryll.py? How close is the correspondence between system calls made and python function invocations? (Note that if you want to use strace you'll have to do everything as root.)
- Modify the code so that file renames don't delete the old file but instead just create a new name for it.
- Modify the code so that when read, files are converted to upper case.
- Download opensnoop.ply and run it with the command "sudo ply opensnoop.ply". Cancel the script by entering Ctrl-C. What does this ply script output?
- Download fusesnoop.ply and run it with the command "sudo ply fusesnoop.ply". What does this ply script output?
- Create a ply script to monitor other functions in the Linux kernel. Try looking at functions in fs/fuse and general filesystem operations such as fs/read_write.c.
Code
opensnoop.ply
#!/usr/bin/env ply
kprobe:do_sys_open
{
printf("%16s(%5d): %s\n", comm(), pid(), mem(arg(1), "128s"));
}
fusesnoop.ply
#!/usr/bin/env ply
kprobe:fuse_do_open
{
printf("%16s(%5d): %5d\n", comm(), pid(), arg(1));
}
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)